Skip to content

Commit 1846538

Browse files
committed
[CSV-63] CSVPrinter always quotes empty string if it is the first on a line
1 parent be917c6 commit 1846538

2 files changed

Lines changed: 40 additions & 13 deletions

File tree

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

Lines changed: 22 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -39,8 +39,10 @@ public final class CSVPrinter implements Flushable, Closeable {
3939
private final Appendable out;
4040
private final CSVFormat format;
4141

42-
/** True if we just began a new record. */
43-
private boolean newRecord = true;
42+
/** 0 if we just began a new record. */
43+
private int newRecord = 0;
44+
/** true if first value is empty */
45+
private boolean firstEmpty;
4446

4547
/**
4648
* Creates a printer that will print values to the given stream following the CSVFormat.
@@ -124,7 +126,7 @@ public void print(final Object value) throws IOException {
124126

125127
private void print(final Object object, final CharSequence value, final int offset, final int len)
126128
throws IOException {
127-
if (!newRecord) {
129+
if (newRecord > 0) {
128130
out.append(format.getDelimiter());
129131
}
130132
if (format.isQuoteCharacterSet()) {
@@ -135,7 +137,7 @@ private void print(final Object object, final CharSequence value, final int offs
135137
} else {
136138
out.append(value, offset, offset + len);
137139
}
138-
newRecord = false;
140+
newRecord++;
139141
}
140142

141143
/*
@@ -208,18 +210,16 @@ private void printAndQuote(final Object object, final CharSequence value, final
208210
return;
209211
case MINIMAL:
210212
if (len <= 0) {
211-
// always quote an empty token that is the first
212-
// on the line, as it may be the only thing on the
213-
// line. If it were not quoted in that case,
214-
// an empty line has no tokens.
215-
if (newRecord) {
216-
quote = true;
213+
// mark quotes may be required for first empty value
214+
if (newRecord == 0) {
215+
firstEmpty = true;
216+
return;
217217
}
218218
} else {
219219
char c = value.charAt(pos);
220220

221221
// TODO where did this rule come from?
222-
if (newRecord && (c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z'))) {
222+
if (newRecord == 0 && (c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z'))) {
223223
quote = true;
224224
} else if (c <= COMMENT) {
225225
// Some other chars at the start of a value caused the parser to fail, so for now
@@ -307,7 +307,7 @@ public void printComment(final String comment) throws IOException {
307307
if (!format.isCommentMarkerSet()) {
308308
return;
309309
}
310-
if (!newRecord) {
310+
if (newRecord > 0) {
311311
println();
312312
}
313313
out.append(format.getCommentMarker().charValue());
@@ -340,11 +340,20 @@ public void printComment(final String comment) throws IOException {
340340
* If an I/O error occurs
341341
*/
342342
public void println() throws IOException {
343+
// if the line only contains an empty value
344+
if (newRecord == 1 && firstEmpty
345+
&& (format.getQuoteMode() == null || format.getQuoteMode() == QuoteMode.MINIMAL)) {
346+
final char quoteChar = format.getQuoteCharacter().charValue();
347+
out.append(quoteChar);
348+
out.append(quoteChar);
349+
}
350+
343351
final String recordSeparator = format.getRecordSeparator();
344352
if (recordSeparator != null) {
345353
out.append(recordSeparator);
346354
}
347-
newRecord = true;
355+
newRecord = 0;
356+
firstEmpty = false;
348357
}
349358

350359
/**

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

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -391,6 +391,24 @@ public void testPrintCustomNullValues() throws IOException {
391391
printer.close();
392392
}
393393

394+
@Test
395+
public void testPrintFirstEmptyValue() throws IOException {
396+
final StringWriter sw = new StringWriter();
397+
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT);
398+
printer.printRecord("", "b", "c");
399+
assertEquals(",b,c" + recordSeparator, sw.toString());
400+
printer.close();
401+
}
402+
403+
@Test
404+
public void testPrintOnlyEmptyValue() throws IOException {
405+
final StringWriter sw = new StringWriter();
406+
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT);
407+
printer.printRecord("");
408+
assertEquals("\"\"" + recordSeparator, sw.toString());
409+
printer.close();
410+
}
411+
394412
@Test
395413
public void testParseCustomNullValues() throws IOException {
396414
final StringWriter sw = new StringWriter();

0 commit comments

Comments
 (0)