View Javadoc

1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    * 
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   * 
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  package org.apache.commons.lang;
18  
19  import java.io.IOException;
20  import java.io.StringWriter;
21  import java.io.Writer;
22  
23  import org.apache.commons.lang.exception.NestableRuntimeException;
24  
25  /**
26   * <p>Escapes and unescapes <code>String</code>s for
27   * Java, Java Script, HTML, XML, and SQL.</p>
28   *
29   * @author Apache Jakarta Turbine
30   * @author Purple Technology
31   * @author <a href="mailto:alex@purpletech.com">Alexander Day Chaffee</a>
32   * @author Antony Riley
33   * @author Helge Tesgaard
34   * @author <a href="sean@boohai.com">Sean Brown</a>
35   * @author <a href="mailto:ggregory@seagullsw.com">Gary Gregory</a>
36   * @author Phil Steitz
37   * @author Pete Gieser
38   * @since 2.0
39   * @version $Id: StringEscapeUtils.java 612880 2008-01-17 17:34:43Z ggregory $
40   */
41  public class StringEscapeUtils {
42  
43      private static final char CSV_DELIMITER = ',';
44      private static final char CSV_QUOTE = '"';
45      private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE);
46      private static final char[] CSV_SEARCH_CHARS = new char[] {CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF};
47  
48      /**
49       * <p><code>StringEscapeUtils</code> instances should NOT be constructed in
50       * standard programming.</p>
51       *
52       * <p>Instead, the class should be used as:
53       * <pre>StringEscapeUtils.escapeJava("foo");</pre></p>
54       *
55       * <p>This constructor is public to permit tools that require a JavaBean
56       * instance to operate.</p>
57       */
58      public StringEscapeUtils() {
59        super();
60      }
61  
62      // Java and JavaScript
63      //--------------------------------------------------------------------------
64      /**
65       * <p>Escapes the characters in a <code>String</code> using Java String rules.</p>
66       *
67       * <p>Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
68       *
69       * <p>So a tab becomes the characters <code>'\\'</code> and
70       * <code>'t'</code>.</p>
71       *
72       * <p>The only difference between Java strings and JavaScript strings
73       * is that in JavaScript, a single quote must be escaped.</p>
74       *
75       * <p>Example:
76       * <pre>
77       * input string: He didn't say, "Stop!"
78       * output string: He didn't say, \"Stop!\"
79       * </pre>
80       * </p>
81       *
82       * @param str  String to escape values in, may be null
83       * @return String with escaped values, <code>null</code> if null string input
84       */
85      public static String escapeJava(String str) {
86          return escapeJavaStyleString(str, false);
87      }
88  
89      /**
90       * <p>Escapes the characters in a <code>String</code> using Java String rules to
91       * a <code>Writer</code>.</p>
92       * 
93       * <p>A <code>null</code> string input has no effect.</p>
94       * 
95       * @see #escapeJava(java.lang.String)
96       * @param out  Writer to write escaped string into
97       * @param str  String to escape values in, may be null
98       * @throws IllegalArgumentException if the Writer is <code>null</code>
99       * @throws IOException if error occurs on underlying Writer
100      */
101     public static void escapeJava(Writer out, String str) throws IOException {
102         escapeJavaStyleString(out, str, false);
103     }
104 
105     /**
106      * <p>Escapes the characters in a <code>String</code> using JavaScript String rules.</p>
107      * <p>Escapes any values it finds into their JavaScript String form.
108      * Deals correctly with quotes and control-chars (tab, backslash, cr, ff, etc.) </p>
109      *
110      * <p>So a tab becomes the characters <code>'\\'</code> and
111      * <code>'t'</code>.</p>
112      *
113      * <p>The only difference between Java strings and JavaScript strings
114      * is that in JavaScript, a single quote must be escaped.</p>
115      *
116      * <p>Example:
117      * <pre>
118      * input string: He didn't say, "Stop!"
119      * output string: He didn\'t say, \"Stop!\"
120      * </pre>
121      * </p>
122      *
123      * @param str  String to escape values in, may be null
124      * @return String with escaped values, <code>null</code> if null string input
125      */
126     public static String escapeJavaScript(String str) {
127         return escapeJavaStyleString(str, true);
128     }
129 
130     /**
131      * <p>Escapes the characters in a <code>String</code> using JavaScript String rules
132      * to a <code>Writer</code>.</p>
133      * 
134      * <p>A <code>null</code> string input has no effect.</p>
135      * 
136      * @see #escapeJavaScript(java.lang.String)
137      * @param out  Writer to write escaped string into
138      * @param str  String to escape values in, may be null
139      * @throws IllegalArgumentException if the Writer is <code>null</code>
140      * @throws IOException if error occurs on underlying Writer
141      **/
142     public static void escapeJavaScript(Writer out, String str) throws IOException {
143         escapeJavaStyleString(out, str, true);
144     }
145 
146     /**
147      * <p>Worker method for the {@link #escapeJavaScript(String)} method.</p>
148      * 
149      * @param str String to escape values in, may be null
150      * @param escapeSingleQuotes escapes single quotes if <code>true</code>
151      * @return the escaped string
152      */
153     private static String escapeJavaStyleString(String str, boolean escapeSingleQuotes) {
154         if (str == null) {
155             return null;
156         }
157         try {
158             StringWriter writer = new StringWriter(str.length() * 2);
159             escapeJavaStyleString(writer, str, escapeSingleQuotes);
160             return writer.toString();
161         } catch (IOException ioe) {
162             // this should never ever happen while writing to a StringWriter
163             ioe.printStackTrace();
164             return null;
165         }
166     }
167 
168     /**
169      * <p>Worker method for the {@link #escapeJavaScript(String)} method.</p>
170      * 
171      * @param out write to receieve the escaped string
172      * @param str String to escape values in, may be null
173      * @param escapeSingleQuote escapes single quotes if <code>true</code>
174      * @throws IOException if an IOException occurs
175      */
176     private static void escapeJavaStyleString(Writer out, String str, boolean escapeSingleQuote) throws IOException {
177         if (out == null) {
178             throw new IllegalArgumentException("The Writer must not be null");
179         }
180         if (str == null) {
181             return;
182         }
183         int sz;
184         sz = str.length();
185         for (int i = 0; i < sz; i++) {
186             char ch = str.charAt(i);
187 
188             // handle unicode
189             if (ch > 0xfff) {
190                 out.write("\\u" + hex(ch));
191             } else if (ch > 0xff) {
192                 out.write("\\u0" + hex(ch));
193             } else if (ch > 0x7f) {
194                 out.write("\\u00" + hex(ch));
195             } else if (ch < 32) {
196                 switch (ch) {
197                     case '\b':
198                         out.write('\\');
199                         out.write('b');
200                         break;
201                     case '\n':
202                         out.write('\\');
203                         out.write('n');
204                         break;
205                     case '\t':
206                         out.write('\\');
207                         out.write('t');
208                         break;
209                     case '\f':
210                         out.write('\\');
211                         out.write('f');
212                         break;
213                     case '\r':
214                         out.write('\\');
215                         out.write('r');
216                         break;
217                     default :
218                         if (ch > 0xf) {
219                             out.write("\\u00" + hex(ch));
220                         } else {
221                             out.write("\\u000" + hex(ch));
222                         }
223                         break;
224                 }
225             } else {
226                 switch (ch) {
227                     case '\'':
228                         if (escapeSingleQuote) {
229                           out.write('\\');
230                         }
231                         out.write('\'');
232                         break;
233                     case '"':
234                         out.write('\\');
235                         out.write('"');
236                         break;
237                     case '\\':
238                         out.write('\\');
239                         out.write('\\');
240                         break;
241                     case '/':
242                         out.write('\\');
243                         out.write('/');
244                         break;
245                     default :
246                         out.write(ch);
247                         break;
248                 }
249             }
250         }
251     }
252 
253     /**
254      * <p>Returns an upper case hexadecimal <code>String</code> for the given
255      * character.</p>
256      * 
257      * @param ch The character to convert.
258      * @return An upper case hexadecimal <code>String</code>
259      */
260     private static String hex(char ch) {
261         return Integer.toHexString(ch).toUpperCase();
262     }
263 
264     /**
265      * <p>Unescapes any Java literals found in the <code>String</code>.
266      * For example, it will turn a sequence of <code>'\'</code> and
267      * <code>'n'</code> into a newline character, unless the <code>'\'</code>
268      * is preceded by another <code>'\'</code>.</p>
269      * 
270      * @param str  the <code>String</code> to unescape, may be null
271      * @return a new unescaped <code>String</code>, <code>null</code> if null string input
272      */
273     public static String unescapeJava(String str) {
274         if (str == null) {
275             return null;
276         }
277         try {
278             StringWriter writer = new StringWriter(str.length());
279             unescapeJava(writer, str);
280             return writer.toString();
281         } catch (IOException ioe) {
282             // this should never ever happen while writing to a StringWriter
283             ioe.printStackTrace();
284             return null;
285         }
286     }
287 
288     /**
289      * <p>Unescapes any Java literals found in the <code>String</code> to a
290      * <code>Writer</code>.</p>
291      *
292      * <p>For example, it will turn a sequence of <code>'\'</code> and
293      * <code>'n'</code> into a newline character, unless the <code>'\'</code>
294      * is preceded by another <code>'\'</code>.</p>
295      * 
296      * <p>A <code>null</code> string input has no effect.</p>
297      * 
298      * @param out  the <code>Writer</code> used to output unescaped characters
299      * @param str  the <code>String</code> to unescape, may be null
300      * @throws IllegalArgumentException if the Writer is <code>null</code>
301      * @throws IOException if error occurs on underlying Writer
302      */
303     public static void unescapeJava(Writer out, String str) throws IOException {
304         if (out == null) {
305             throw new IllegalArgumentException("The Writer must not be null");
306         }
307         if (str == null) {
308             return;
309         }
310         int sz = str.length();
311         StringBuffer unicode = new StringBuffer(4);
312         boolean hadSlash = false;
313         boolean inUnicode = false;
314         for (int i = 0; i < sz; i++) {
315             char ch = str.charAt(i);
316             if (inUnicode) {
317                 // if in unicode, then we're reading unicode
318                 // values in somehow
319                 unicode.append(ch);
320                 if (unicode.length() == 4) {
321                     // unicode now contains the four hex digits
322                     // which represents our unicode character
323                     try {
324                         int value = Integer.parseInt(unicode.toString(), 16);
325                         out.write((char) value);
326                         unicode.setLength(0);
327                         inUnicode = false;
328                         hadSlash = false;
329                     } catch (NumberFormatException nfe) {
330                         throw new NestableRuntimeException("Unable to parse unicode value: " + unicode, nfe);
331                     }
332                 }
333                 continue;
334             }
335             if (hadSlash) {
336                 // handle an escaped value
337                 hadSlash = false;
338                 switch (ch) {
339                     case '\\':
340                         out.write('\\');
341                         break;
342                     case '\'':
343                         out.write('\'');
344                         break;
345                     case '\"':
346                         out.write('"');
347                         break;
348                     case 'r':
349                         out.write('\r');
350                         break;
351                     case 'f':
352                         out.write('\f');
353                         break;
354                     case 't':
355                         out.write('\t');
356                         break;
357                     case 'n':
358                         out.write('\n');
359                         break;
360                     case 'b':
361                         out.write('\b');
362                         break;
363                     case 'u':
364                         {
365                             // uh-oh, we're in unicode country....
366                             inUnicode = true;
367                             break;
368                         }
369                     default :
370                         out.write(ch);
371                         break;
372                 }
373                 continue;
374             } else if (ch == '\\') {
375                 hadSlash = true;
376                 continue;
377             }
378             out.write(ch);
379         }
380         if (hadSlash) {
381             // then we're in the weird case of a \ at the end of the
382             // string, let's output it anyway.
383             out.write('\\');
384         }
385     }
386 
387     /**
388      * <p>Unescapes any JavaScript literals found in the <code>String</code>.</p>
389      *
390      * <p>For example, it will turn a sequence of <code>'\'</code> and <code>'n'</code>
391      * into a newline character, unless the <code>'\'</code> is preceded by another
392      * <code>'\'</code>.</p>
393      *
394      * @see #unescapeJava(String)
395      * @param str  the <code>String</code> to unescape, may be null
396      * @return A new unescaped <code>String</code>, <code>null</code> if null string input
397      */
398     public static String unescapeJavaScript(String str) {
399         return unescapeJava(str);
400     }
401 
402     /**
403      * <p>Unescapes any JavaScript literals found in the <code>String</code> to a
404      * <code>Writer</code>.</p>
405      *
406      * <p>For example, it will turn a sequence of <code>'\'</code> and <code>'n'</code>
407      * into a newline character, unless the <code>'\'</code> is preceded by another
408      * <code>'\'</code>.</p>
409      *
410      * <p>A <code>null</code> string input has no effect.</p>
411      * 
412      * @see #unescapeJava(Writer,String)
413      * @param out  the <code>Writer</code> used to output unescaped characters
414      * @param str  the <code>String</code> to unescape, may be null
415      * @throws IllegalArgumentException if the Writer is <code>null</code>
416      * @throws IOException if error occurs on underlying Writer
417      */
418     public static void unescapeJavaScript(Writer out, String str) throws IOException {
419         unescapeJava(out, str);
420     }
421 
422     // HTML and XML
423     //--------------------------------------------------------------------------
424     /**
425      * <p>Escapes the characters in a <code>String</code> using HTML entities.</p>
426      *
427      * <p>
428      * For example:
429      * </p> 
430      * <p><code>"bread" & "butter"</code></p>
431      * becomes:
432      * <p>
433      * <code>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</code>.
434      * </p>
435      *
436      * <p>Supports all known HTML 4.0 entities, including funky accents.
437      * Note that the commonly used apostrophe escape character (&amp;apos;)
438      * is not a legal entity and so is not supported). </p>
439      *
440      * @param str  the <code>String</code> to escape, may be null
441      * @return a new escaped <code>String</code>, <code>null</code> if null string input
442      * 
443      * @see #unescapeHtml(String)
444      * @see <a href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO Entities</a>
445      * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character Entities for ISO Latin-1</a>
446      * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML 4.0 Character entity references</a>
447      * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 Character References</a>
448      * @see <a href="http://www.w3.org/TR/html401/charset.html#code-position">HTML 4.01 Code positions</a>
449      */
450     public static String escapeHtml(String str) {
451         if (str == null) {
452             return null;
453         }
454         try {
455             StringWriter writer = new StringWriter ((int)(str.length() * 1.5));
456             escapeHtml(writer, str);
457             return writer.toString();
458         } catch (IOException e) {
459             //assert false;
460             //should be impossible
461             e.printStackTrace();
462             return null;
463         }
464     }
465 
466     /**
467      * <p>Escapes the characters in a <code>String</code> using HTML entities and writes
468      * them to a <code>Writer</code>.</p>
469      *
470      * <p>
471      * For example:
472      * </p> 
473      * <code>"bread" & "butter"</code>
474      * <p>becomes:</p>
475      * <code>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</code>.
476      *
477      * <p>Supports all known HTML 4.0 entities, including funky accents.
478      * Note that the commonly used apostrophe escape character (&amp;apos;)
479      * is not a legal entity and so is not supported). </p>
480      *
481      * @param writer  the writer receiving the escaped string, not null
482      * @param string  the <code>String</code> to escape, may be null
483      * @throws IllegalArgumentException if the writer is null
484      * @throws IOException when <code>Writer</code> passed throws the exception from
485      *                                       calls to the {@link Writer#write(int)} methods.
486      * 
487      * @see #escapeHtml(String)
488      * @see #unescapeHtml(String)
489      * @see <a href="http://hotwired.lycos.com/webmonkey/reference/special_characters/">ISO Entities</a>
490      * @see <a href="http://www.w3.org/TR/REC-html32#latin1">HTML 3.2 Character Entities for ISO Latin-1</a>
491      * @see <a href="http://www.w3.org/TR/REC-html40/sgml/entities.html">HTML 4.0 Character entity references</a>
492      * @see <a href="http://www.w3.org/TR/html401/charset.html#h-5.3">HTML 4.01 Character References</a>
493      * @see <a href="http://www.w3.org/TR/html401/charset.html#code-position">HTML 4.01 Code positions</a>
494      */
495     public static void escapeHtml(Writer writer, String string) throws IOException {
496         if (writer == null ) {
497             throw new IllegalArgumentException ("The Writer must not be null.");
498         }
499         if (string == null) {
500             return;
501         }
502         Entities.HTML40.escape(writer, string);
503     }
504 
505     //-----------------------------------------------------------------------
506     /**
507      * <p>Unescapes a string containing entity escapes to a string
508      * containing the actual Unicode characters corresponding to the
509      * escapes. Supports HTML 4.0 entities.</p>
510      *
511      * <p>For example, the string "&amp;lt;Fran&amp;ccedil;ais&amp;gt;"
512      * will become "&lt;Fran&ccedil;ais&gt;"</p>
513      *
514      * <p>If an entity is unrecognized, it is left alone, and inserted
515      * verbatim into the result string. e.g. "&amp;gt;&amp;zzzz;x" will
516      * become "&gt;&amp;zzzz;x".</p>
517      *
518      * @param str  the <code>String</code> to unescape, may be null
519      * @return a new unescaped <code>String</code>, <code>null</code> if null string input
520      * @see #escapeHtml(Writer, String)
521      */
522     public static String unescapeHtml(String str) {
523         if (str == null) {
524             return null;
525         }
526         try {
527             StringWriter writer = new StringWriter ((int)(str.length() * 1.5));
528             unescapeHtml(writer, str);
529             return writer.toString();
530         } catch (IOException e) {
531             //assert false;
532             //should be impossible
533             e.printStackTrace();
534             return null;
535         }
536     }
537 
538     /**
539      * <p>Unescapes a string containing entity escapes to a string
540      * containing the actual Unicode characters corresponding to the
541      * escapes. Supports HTML 4.0 entities.</p>
542      *
543      * <p>For example, the string "&amp;lt;Fran&amp;ccedil;ais&amp;gt;"
544      * will become "&lt;Fran&ccedil;ais&gt;"</p>
545      *
546      * <p>If an entity is unrecognized, it is left alone, and inserted
547      * verbatim into the result string. e.g. "&amp;gt;&amp;zzzz;x" will
548      * become "&gt;&amp;zzzz;x".</p>
549      *
550      * @param writer  the writer receiving the unescaped string, not null
551      * @param string  the <code>String</code> to unescape, may be null
552      * @throws IllegalArgumentException if the writer is null
553      * @throws IOException if an IOException occurs
554      * @see #escapeHtml(String)
555      */
556     public static void unescapeHtml(Writer writer, String string) throws IOException {
557         if (writer == null ) {
558             throw new IllegalArgumentException ("The Writer must not be null.");
559         }
560         if (string == null) {
561             return;
562         }
563         Entities.HTML40.unescape(writer, string);
564     }
565 
566     //-----------------------------------------------------------------------
567     /**
568      * <p>Escapes the characters in a <code>String</code> using XML entities.</p>
569      *
570      * <p>For example: <tt>"bread" & "butter"</tt> =>
571      * <tt>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</tt>.
572      * </p>
573      *
574      * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
575      * Does not support DTDs or external entities.</p>
576      *
577      * <p>Note that unicode characters greater than 0x7f are currently escaped to 
578      *    their numerical \\u equivalent. This may change in future releases. </p>
579      *
580      * @param writer  the writer receiving the unescaped string, not null
581      * @param str  the <code>String</code> to escape, may be null
582      * @throws IllegalArgumentException if the writer is null
583      * @throws IOException if there is a problem writing
584      * @see #unescapeXml(java.lang.String)
585      */
586     public static void escapeXml(Writer writer, String str) throws IOException {
587         if (writer == null ) {
588             throw new IllegalArgumentException ("The Writer must not be null.");
589         }
590         if (str == null) {
591             return;
592         }
593         Entities.XML.escape(writer, str);
594     }
595 
596     /**
597      * <p>Escapes the characters in a <code>String</code> using XML entities.</p>
598      *
599      * <p>For example: <tt>"bread" & "butter"</tt> =>
600      * <tt>&amp;quot;bread&amp;quot; &amp;amp; &amp;quot;butter&amp;quot;</tt>.
601      * </p>
602      *
603      * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
604      * Does not support DTDs or external entities.</p>
605      *
606      * <p>Note that unicode characters greater than 0x7f are currently escaped to 
607      *    their numerical \\u equivalent. This may change in future releases. </p>
608      *
609      * @param str  the <code>String</code> to escape, may be null
610      * @return a new escaped <code>String</code>, <code>null</code> if null string input
611      * @see #unescapeXml(java.lang.String)
612      */
613     public static String escapeXml(String str) {
614         if (str == null) {
615             return null;
616         }
617         return Entities.XML.escape(str);
618     }
619 
620     //-----------------------------------------------------------------------
621     /**
622      * <p>Unescapes a string containing XML entity escapes to a string
623      * containing the actual Unicode characters corresponding to the
624      * escapes.</p>
625      *
626      * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
627      * Does not support DTDs or external entities.</p>
628      *
629      * <p>Note that numerical \\u unicode codes are unescaped to their respective 
630      *    unicode characters. This may change in future releases. </p>
631      *
632      * @param writer  the writer receiving the unescaped string, not null
633      * @param str  the <code>String</code> to unescape, may be null
634      * @throws IllegalArgumentException if the writer is null
635      * @throws IOException if there is a problem writing
636      * @see #escapeXml(String)
637      */
638     public static void unescapeXml(Writer writer, String str) throws IOException {
639         if (writer == null ) {
640             throw new IllegalArgumentException ("The Writer must not be null.");
641         }
642         if (str == null) {
643             return;
644         }
645         Entities.XML.unescape(writer, str);
646     }
647 
648     /**
649      * <p>Unescapes a string containing XML entity escapes to a string
650      * containing the actual Unicode characters corresponding to the
651      * escapes.</p>
652      *
653      * <p>Supports only the five basic XML entities (gt, lt, quot, amp, apos).
654      * Does not support DTDs or external entities.</p>
655      *
656      * <p>Note that numerical \\u unicode codes are unescaped to their respective 
657      *    unicode characters. This may change in future releases. </p>
658      *
659      * @param str  the <code>String</code> to unescape, may be null
660      * @return a new unescaped <code>String</code>, <code>null</code> if null string input
661      * @see #escapeXml(String)
662      */
663     public static String unescapeXml(String str) {
664         if (str == null) {
665             return null;
666         }
667         return Entities.XML.unescape(str);
668     }
669 
670     //-----------------------------------------------------------------------
671     /**
672      * <p>Escapes the characters in a <code>String</code> to be suitable to pass to
673      * an SQL query.</p>
674      *
675      * <p>For example,
676      * <pre>statement.executeQuery("SELECT * FROM MOVIES WHERE TITLE='" + 
677      *   StringEscapeUtils.escapeSql("McHale's Navy") + 
678      *   "'");</pre>
679      * </p>
680      *
681      * <p>At present, this method only turns single-quotes into doubled single-quotes
682      * (<code>"McHale's Navy"</code> => <code>"McHale''s Navy"</code>). It does not
683      * handle the cases of percent (%) or underscore (_) for use in LIKE clauses.</p>
684      *
685      * see http://www.jguru.com/faq/view.jsp?EID=8881
686      * @param str  the string to escape, may be null
687      * @return a new String, escaped for SQL, <code>null</code> if null string input
688      */
689     public static String escapeSql(String str) {
690         if (str == null) {
691             return null;
692         }
693         return StringUtils.replace(str, "'", "''");
694     }
695 
696     //-----------------------------------------------------------------------
697 
698     /**
699      * <p>Returns a <code>String</code> value for a CSV column enclosed in double quotes,
700      * if required.</p>
701      *
702      * <p>If the value contains a comma, newline or double quote, then the
703      *    String value is returned enclosed in double quotes.</p>
704      * </p>
705      *
706      * <p>Any double quote characters in the value are escaped with another double quote.</p>
707      *
708      * <p>If the value does not contain a comma, newline or double quote, then the
709      *    String value is returned unchanged.</p>
710      * </p>
711      *
712      * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
713      * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
714      *
715      * @param str the input CSV column String, may be null
716      * @return the input String, enclosed in double quotes if the value contains a comma,
717      * newline or double quote, <code>null</code> if null string input
718      * @since 2.4
719      */
720     public static String escapeCsv(String str) {
721         if (StringUtils.containsNone(str, CSV_SEARCH_CHARS)) {
722             return str;
723         }
724         try {
725             StringWriter writer = new StringWriter();
726             escapeCsv(writer, str);
727             return writer.toString();
728         } catch (IOException ioe) {
729             // this should never ever happen while writing to a StringWriter
730             ioe.printStackTrace();
731             return null;
732         }
733     }
734 
735     /**
736      * <p>Writes a <code>String</code> value for a CSV column enclosed in double quotes,
737      * if required.</p>
738      *
739      * <p>If the value contains a comma, newline or double quote, then the
740      *    String value is written enclosed in double quotes.</p>
741      * </p>
742      *
743      * <p>Any double quote characters in the value are escaped with another double quote.</p>
744      *
745      * <p>If the value does not contain a comma, newline or double quote, then the
746      *    String value is written unchanged (null values are ignored).</p>
747      * </p>
748      *
749      * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
750      * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
751      *
752      * @param str the input CSV column String, may be null
753      * @param out Writer to write input string to, enclosed in double quotes if it contains
754      * a comma, newline or double quote
755      * @throws IOException if error occurs on underlying Writer
756      * @since 2.4
757      */
758     public static void escapeCsv(Writer out, String str) throws IOException {
759         if (StringUtils.containsNone(str, CSV_SEARCH_CHARS)) {
760             if (str != null) {
761                 out.write(str);
762             }
763             return;
764         }
765         out.write(CSV_QUOTE);
766         for (int i = 0; i < str.length(); i++) {
767             char c = str.charAt(i);
768             if (c == CSV_QUOTE) {
769                 out.write(CSV_QUOTE); // escape double quote
770             }
771             out.write(c);
772         }
773         out.write(CSV_QUOTE);
774     }
775 
776     /**
777      * <p>Returns a <code>String</code> value for an unescaped CSV column. </p>
778      *
779      * <p>If the value is enclosed in double quotes, and contains a comma, newline 
780      *    or double quote, then quotes are removed. 
781      * </p>
782      *
783      * <p>Any double quote escaped characters (a pair of double quotes) are unescaped 
784      *    to just one double quote. </p>
785      *
786      * <p>If the value is not enclosed in double quotes, or is and does not contain a 
787      *    comma, newline or double quote, then the String value is returned unchanged.</p>
788      * </p>
789      *
790      * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
791      * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
792      *
793      * @param str the input CSV column String, may be null
794      * @return the input String, with enclosing double quotes removed and embedded double 
795      * quotes unescaped, <code>null</code> if null string input
796      * @since 2.4
797      */
798     public static String unescapeCsv(String str) {
799         if (str == null) {
800             return null;
801         }
802         try {
803             StringWriter writer = new StringWriter();
804             unescapeCsv(writer, str);
805             return writer.toString();
806         } catch (IOException ioe) {
807             // this should never ever happen while writing to a StringWriter
808             ioe.printStackTrace();
809             return null;
810         }
811     }
812 
813     /**
814      * <p>Returns a <code>String</code> value for an unescaped CSV column. </p>
815      *
816      * <p>If the value is enclosed in double quotes, and contains a comma, newline 
817      *    or double quote, then quotes are removed. 
818      * </p>
819      *
820      * <p>Any double quote escaped characters (a pair of double quotes) are unescaped 
821      *    to just one double quote. </p>
822      *
823      * <p>If the value is not enclosed in double quotes, or is and does not contain a 
824      *    comma, newline or double quote, then the String value is returned unchanged.</p>
825      * </p>
826      *
827      * see <a href="http://en.wikipedia.org/wiki/Comma-separated_values">Wikipedia</a> and
828      * <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
829      *
830      * @param str the input CSV column String, may be null
831      * @param out Writer to write the input String to, with enclosing double quotes 
832      * removed and embedded double quotes unescaped, <code>null</code> if null string input
833      * @throws IOException if error occurs on underlying Writer
834      * @since 2.4
835      */
836     public static void unescapeCsv(Writer out, String str) throws IOException {
837         if (str == null) {
838             return;
839         }
840         if (str.length() < 2) {
841             out.write(str);
842             return;
843         }
844         if ( str.charAt(0) != CSV_QUOTE || str.charAt(str.length() - 1) != CSV_QUOTE ) {
845             out.write(str);
846             return;
847         }
848 
849         // strip quotes
850         String quoteless = str.substring(1, str.length() - 1);
851 
852         if ( StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS) ) {
853             // deal with escaped quotes; ie) ""
854             str = StringUtils.replace(quoteless, CSV_QUOTE_STR + CSV_QUOTE_STR, CSV_QUOTE_STR);
855         }
856 
857         out.write(str);
858     }
859 
860 }