Skip to main content
added 57 characters in body
Source Link
scala> import test.CSV
import test.CSV

scala> CSV.split("N/A,N\\A,ThirdField")
res0: Array[String] = Array(N/A, N\A, ThirdField)

scala> CSV.split(""""A quoted value","One with ""embedded"" quotes",One without,"",a,""")
res1: Array[String] = Array(A quoted value, One with "embedded" quotes, One without, "", a)

scala> res1(3)
res2: String = ""

scala> res2.length
res3: Int = 0

scala> CSV.split("""First,second,"third, with a comma",fourth has a \nnewline,"fifth:\tbeing tricky, has ""some real"" testing craziness!"
     | """
     | )
res4: Array[String] =
  Array(First, second, third, with a comma, fourth has a
newline, "fifth:  being tricky, has "some real" testing craziness!
")

scala> res4(2)
res5: String = third, with a comma

scala> CSV.split("""One,second has \"escaped\" quotes,third""")
res5res6: Array[String] = Array(One, second has "escaped" quotes, third)
scala> import test.CSV
import test.CSV

scala> CSV.split("N/A,N\\A,ThirdField")
res0: Array[String] = Array(N/A, N\A, ThirdField)

scala> CSV.split(""""A quoted value","One with ""embedded"" quotes",One without,"",a,""")
res1: Array[String] = Array(A quoted value, One with "embedded" quotes, One without, "", a)

scala> res1(3)
res2: String = ""

scala> res2.length
res3: Int = 0

scala> CSV.split("""First,second,"third, with a comma",fourth has a \nnewline,"fifth:\tbeing tricky, has ""some real"" testing craziness!"
     | """
     | )
res4: Array[String] =
 Array(First, second, third, with a comma, fourth has a
newline, "fifth:  being tricky, has "some real" testing craziness!
")

scala> CSV.split("""One,second has \"escaped\" quotes,third""")
res5: Array[String] = Array(One, second has "escaped" quotes, third)
scala> import test.CSV
import test.CSV

scala> CSV.split("N/A,N\\A,ThirdField")
res0: Array[String] = Array(N/A, N\A, ThirdField)

scala> CSV.split(""""A quoted value","One with ""embedded"" quotes",One without,"",a,""")
res1: Array[String] = Array(A quoted value, One with "embedded" quotes, One without, "", a)

scala> res1(3)
res2: String = ""

scala> res2.length
res3: Int = 0

scala> CSV.split("""First,second,"third, with a comma",fourth has a \nnewline,"fifth:\tbeing tricky, has ""some real"" testing craziness!"
     | """
     | )
res4: Array[String] = Array(First, second, third, with a comma, fourth has a
newline, "fifth:  being tricky, has "some real" testing craziness!
")

scala> res4(2)
res5: String = third, with a comma

scala> CSV.split("""One,second has \"escaped\" quotes,third""")
res6: Array[String] = Array(One, second has "escaped" quotes, third)
Source Link

It may be simpler to code in terms of single fields, instead of lines or "records" of CSV fields. Code sample below uses Java8 streams to build up a result; however, it's trivial to convert it to Java7 using an ArrayList<String> to build up the results.

package test;
import java.util.stream.Stream;

public class CSV {
  public static String[] split(final String text) {
    return (text.indexOf('"') < 0)? text.split(",")
                                  : splitCSV(Stream.<String>builder(), text, 0).build().toArray(String[]::new);
  }


  private static Stream.Builder<String> splitCSV(final Stream.Builder<String> accum, final String text, int start) {
    final int length = text.length();
    if (start >= length) return accum;

    final StringBuilder buf = new StringBuilder(length);
    boolean inquote = false;

    if (text.charAt(start) == '"') {
      inquote = true;
      start++;
    }

    int i = start;

  loop:
    for (; i < length; i++) {
      final char c = text.charAt(i);
      switch (c) {
        case '"':
          if (inquote) {
            if (i + 1 < length && text.charAt(i + 1) == '"') {
              i++;
              break;
            }
            else {
              inquote = false;
              continue loop;
            }
          }
          break;

        case '\\':
          if (i + 1 < length) {
            final char next = text.charAt(i + 1);
            switch (next) {
              case 'b': buf.append('\b'); i++; continue loop;
              case 'f': buf.append('\f'); i++; continue loop;
              case 'r': buf.append('\r'); i++; continue loop;
              case 'n': buf.append('\n'); i++; continue loop;
              case 't': buf.append('\t'); i++; continue loop;
              case '"': buf.append('"');  i++; continue loop;
            }
          }
          break;

        case ',':
          if (!inquote) {
              // value termination
            i++;
            break loop;
          }
          break;
      }

      buf.append(c);
    }

    return splitCSV(accum.add(buf.toString()), text, i);
  }
}

I tested it briefly using the Scala interpreter:

scala> import test.CSV
import test.CSV

scala> CSV.split("N/A,N\\A,ThirdField")
res0: Array[String] = Array(N/A, N\A, ThirdField)

scala> CSV.split(""""A quoted value","One with ""embedded"" quotes",One without,"",a,""")
res1: Array[String] = Array(A quoted value, One with "embedded" quotes, One without, "", a)

scala> res1(3)
res2: String = ""

scala> res2.length
res3: Int = 0

scala> CSV.split("""First,second,"third, with a comma",fourth has a \nnewline,"fifth:\tbeing tricky, has ""some real"" testing craziness!"
     | """
     | )
res4: Array[String] =
Array(First, second, third, with a comma, fourth has a
newline, "fifth:  being tricky, has "some real" testing craziness!
")

scala> CSV.split("""One,second has \"escaped\" quotes,third""")
res5: Array[String] = Array(One, second has "escaped" quotes, third)

Test Notes:

1. Scala has triple-quoted strings that allow embedded single quotes, newlines, and backslashes, etc., without escaping.

2. The Scala interpreter prints empty strings as a set of empty quotes (""), but non-empty strings without surrounding quotes.