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