From 38760d2171fd8eb0eb23711d6948a24dda79dae4 Mon Sep 17 00:00:00 2001 From: Niall Pemberton Date: Fri, 16 Nov 2007 04:12:44 +0000 Subject: [PATCH] LANG-374 - Add escaping for CSV columns to StringEscapeUtils git-svn-id: https://svn.apache.org/repos/asf/commons/proper/lang/trunk@595541 13f79535-47bb-0310-9956-ffa450edef68 --- .../commons/lang/StringEscapeUtils.java | 89 +++++++++++++++++++ .../commons/lang/StringEscapeUtilsTest.java | 32 +++++++ 2 files changed, 121 insertions(+) diff --git a/src/java/org/apache/commons/lang/StringEscapeUtils.java b/src/java/org/apache/commons/lang/StringEscapeUtils.java index db2d7bd47..d9ac1c994 100644 --- a/src/java/org/apache/commons/lang/StringEscapeUtils.java +++ b/src/java/org/apache/commons/lang/StringEscapeUtils.java @@ -688,4 +688,93 @@ public class StringEscapeUtils { return StringUtils.replace(str, "'", "''"); } + //----------------------------------------------------------------------- + + /** + *

Returns a String value for a CSV column escaping with double quotes, + * if required.

+ * + *

If the value contains a comma, newline or double quote, then the + * String value is returned enclosed in double quotes.

+ *

+ * + *

Any double quote characters in the value are escaped with another double quote.

+ * + * see Wikipedia and + * RFC 4180. + * + * @param str the string to escape, may be null + * @return a new String, escaped for CSV, null if null string input + * @since 2.4 + */ + public static String escapeCsv(String str) { + if (!containsCsvChars(str)) { + return str; + } + StringBuffer buffer = new StringBuffer(str.length() + 10); + buffer.append('"'); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '"') { + buffer.append('"'); // escape double quote + } + buffer.append(c); + } + buffer.append('"'); + return buffer.toString(); + } + + /** + *

Writes a String value for a CSV column escaping with double quotes, + * if required.

+ * + *

If the value contains a comma, newline or double quote, then the + * String value is written enclosed in double quotes.

+ *

+ * + *

Any double quote characters in the value are escaped with another double quote.

+ * + * see Wikipedia and + * RFC 4180. + * + * @param str the string to escape, may be null + * @param out Writer to write escaped string into + * in double quotes or only when the value contains double quotes, commas or newline + * characters. + * @throws IOException if error occurs on underlying Writer + * @since 2.4 + */ + public static void escapeCsv(Writer out, String str) throws IOException { + if (!containsCsvChars(str)) { + if (str != null) { + out.write(str); + } + return; + } + out.write('"'); + for (int i = 0; i < str.length(); i++) { + char c = str.charAt(i); + if (c == '"') { + out.write('"'); // escape double quote + } + out.write(c); + } + out.write('"'); + } + + /** + * Determine if the String contains any characters that need escaping for CSV files. + * + * @param str the string to escape, may be null + * @return true if the String contains characters that need escaping + * for CSV files, otherwise false + * @since 2.4 + */ + private static boolean containsCsvChars(String str) { + return (StringUtils.contains(str, '"') || + StringUtils.contains(str, ',') || + StringUtils.contains(str, CharUtils.CR) || + StringUtils.contains(str, CharUtils.LF)); + } + } diff --git a/src/test/org/apache/commons/lang/StringEscapeUtilsTest.java b/src/test/org/apache/commons/lang/StringEscapeUtilsTest.java index 946af2dc1..a116163d5 100644 --- a/src/test/org/apache/commons/lang/StringEscapeUtilsTest.java +++ b/src/test/org/apache/commons/lang/StringEscapeUtilsTest.java @@ -332,4 +332,36 @@ public class StringEscapeUtilsTest extends TestCase { assertEquals("& &", StringEscapeUtils.unescapeHtml("& &")); } + + public void testEscapeCsvString() throws Exception + { + assertEquals("foo.bar", StringEscapeUtils.escapeCsv("foo.bar")); + assertEquals("\"foo,bar\"", StringEscapeUtils.escapeCsv("foo,bar")); + assertEquals("\"foo\nbar\"", StringEscapeUtils.escapeCsv("foo\nbar")); + assertEquals("\"foo\rbar\"", StringEscapeUtils.escapeCsv("foo\rbar")); + assertEquals("\"foo\"\"bar\"", StringEscapeUtils.escapeCsv("foo\"bar")); + assertEquals("", StringEscapeUtils.escapeCsv("")); + assertEquals(null, StringEscapeUtils.escapeCsv(null)); + } + + public void testEscapeCsvWriter() throws Exception + { + checkCsvEscapeWriter("foo.bar", "foo.bar"); + checkCsvEscapeWriter("\"foo,bar\"", "foo,bar"); + checkCsvEscapeWriter("\"foo\nbar\"", "foo\nbar"); + checkCsvEscapeWriter("\"foo\rbar\"", "foo\rbar"); + checkCsvEscapeWriter("\"foo\"\"bar\"", "foo\"bar"); + checkCsvEscapeWriter("", null); + checkCsvEscapeWriter("", ""); + } + + private void checkCsvEscapeWriter(String expected, String value) { + try { + StringWriter writer = new StringWriter(); + StringEscapeUtils.escapeCsv(writer, value); + assertEquals(expected, writer.toString()); + } catch (IOException e) { + fail("Threw: " + e); + } + } }