Skip to content

Commit 6595acc

Browse files
committed
quote value with leading or trailing unicode whitespace
1 parent cbc710c commit 6595acc

2 files changed

Lines changed: 30 additions & 4 deletions

File tree

src/main/java/org/apache/commons/csv/CSVFormat.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2514,11 +2514,12 @@ private void printWithQuotes(final Object object, final CharSequence charSeq, fi
25142514
}
25152515
} else {
25162516
char c = charSeq.charAt(pos);
2517-
if (c <= Constants.COMMENT || isCommentMarkerSet() && c == commentMarker.charValue()) {
2517+
if (c <= Constants.COMMENT || Character.isWhitespace(c) || isCommentMarkerSet() && c == commentMarker.charValue()) {
25182518
// Some other chars at the start of a value caused the parser to fail, so for now
25192519
// encapsulate if we start in anything less than '#'. We are being conservative
25202520
// by including the default comment char and any configured comment marker too,
2521-
// which the parser would otherwise read back as a comment line.
2521+
// which the parser would otherwise read back as a comment line. A leading Unicode
2522+
// whitespace above ' ' is stripped by ignoreSurroundingSpaces on read, so quote it too.
25222523
quote = true;
25232524
} else {
25242525
while (pos < len) {
@@ -2534,8 +2535,9 @@ private void printWithQuotes(final Object object, final CharSequence charSeq, fi
25342535
pos = len - 1;
25352536
c = charSeq.charAt(pos);
25362537
// Some other chars at the end caused the parser to fail, so for now
2537-
// encapsulate if we end in anything less than ' '
2538-
if (isTrimChar(c)) {
2538+
// encapsulate if we end in anything less than ' '. A trailing Unicode whitespace
2539+
// above ' ' is stripped by ignoreSurroundingSpaces on read, so quote it too.
2540+
if (isTrimChar(c) || Character.isWhitespace(c)) {
25392541
quote = true;
25402542
} else if (endsWithDelimiterPrefix(charSeq, delim, delimLength)) {
25412543
// A trailing partial multi-character delimiter would merge with the following delimiter on read.

src/test/java/org/apache/commons/csv/CSVPrinterTest.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1963,6 +1963,30 @@ void testQuoteValueEndingWithSupplementaryDelimiterPrefix() throws IOException {
19631963
}
19641964
}
19651965

1966+
@Test
1967+
void testQuoteValueWithLeadingOrTrailingUnicodeWhitespace() throws IOException {
1968+
// U+3000 IDEOGRAPHIC SPACE is Character.isWhitespace but greater than ' ', so ignoreSurroundingSpaces
1969+
// strips it on read. It must be quoted or the surrounding whitespace is lost on a round trip.
1970+
final String idSpace = " ";
1971+
final CSVFormat format = CSVFormat.DEFAULT.builder().setIgnoreSurroundingSpaces(true).get();
1972+
final StringWriter sw = new StringWriter();
1973+
try (CSVPrinter printer = new CSVPrinter(sw, format)) {
1974+
printer.printRecord(idSpace + "a" + idSpace, "b");
1975+
// A value without surrounding whitespace stays unquoted.
1976+
printer.printRecord("ab", "c");
1977+
}
1978+
final String string = sw.toString();
1979+
assertEquals("\"" + idSpace + "a" + idSpace + "\",b" + RECORD_SEPARATOR + "ab,c" + RECORD_SEPARATOR, string);
1980+
try (CSVParser parser = CSVParser.parse(string, format)) {
1981+
final List<CSVRecord> records = parser.getRecords();
1982+
assertEquals(2, records.size());
1983+
assertEquals(idSpace + "a" + idSpace, records.get(0).get(0));
1984+
assertEquals("b", records.get(0).get(1));
1985+
assertEquals("ab", records.get(1).get(0));
1986+
assertEquals("c", records.get(1).get(1));
1987+
}
1988+
}
1989+
19661990
@Test
19671991
void testRandomDefault() throws Exception {
19681992
doRandom(CSVFormat.DEFAULT, ITERATIONS_FOR_RANDOM_TEST);

0 commit comments

Comments
 (0)