Skip to content

Commit 1dd04be

Browse files
authored
Merge pull request #616 from rootvector2/trailing-delimiter-quoted-empty
Keep quoted empty trailing field with trailingDelimiter
2 parents 53360c4 + fc4c0e3 commit 1dd04be

3 files changed

Lines changed: 40 additions & 1 deletion

File tree

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -883,6 +883,16 @@ public Builder setTrailingData(final boolean trailingData) {
883883
/**
884884
* Sets whether to add a trailing delimiter.
885885
*
886+
* <p>
887+
* When writing, a delimiter is appended after the last value of each record. When reading, the empty field
888+
* that such a trailing delimiter produces is dropped so the output round-trips back to the original record;
889+
* a quoted empty trailing field ({@code ""}) is a real value rather than a trailing delimiter and is kept.
890+
* </p>
891+
* <p>
892+
* This is unrelated to {@link #setTrailingData(boolean) trailing data}, which controls whether characters
893+
* after the closing quote of an encapsulated value are tolerated when reading.
894+
* </p>
895+
*
886896
* @param trailingDelimiter whether to add a trailing delimiter.
887897
* @return This instance.
888898
*/
@@ -2012,6 +2022,16 @@ public boolean getTrailingData() {
20122022
/**
20132023
* Gets whether to add a trailing delimiter.
20142024
*
2025+
* <p>
2026+
* When writing, a delimiter is appended after the last value of each record. When reading, the empty field
2027+
* that such a trailing delimiter produces is dropped so the output round-trips back to the original record;
2028+
* a quoted empty trailing field ({@code ""}) is a real value rather than a trailing delimiter and is kept.
2029+
* </p>
2030+
* <p>
2031+
* This is unrelated to {@link #getTrailingData() trailing data}, which controls whether characters after the
2032+
* closing quote of an encapsulated value are tolerated when reading.
2033+
* </p>
2034+
*
20152035
* @return whether to add a trailing delimiter.
20162036
* @since 1.3
20172037
*/

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

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -580,7 +580,9 @@ public CSVParser(final Reader reader, final CSVFormat format, final long charact
580580

581581
private void addRecordValue(final boolean lastRecord) {
582582
final String input = format.trim(reusableToken.content.toString());
583-
if (lastRecord && input.isEmpty() && format.getTrailingDelimiter()) {
583+
// Only drop the empty field produced by an actual trailing delimiter. A quoted empty
584+
// field ("") is a real value, not a trailing delimiter, so it must be kept.
585+
if (lastRecord && input.isEmpty() && format.getTrailingDelimiter() && !reusableToken.isQuoted) {
584586
return;
585587
}
586588
recordList.add(handleNull(input));

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1949,6 +1949,23 @@ void testTrailingDelimiter() throws Exception {
19491949
}
19501950
}
19511951

1952+
@Test
1953+
void testTrailingDelimiterKeepsQuotedEmptyLastField() throws Exception {
1954+
final CSVFormat format = CSVFormat.DEFAULT.builder().setTrailingDelimiter(true).get();
1955+
try (CSVParser parser = CSVParser.parse("a,b,\"\"", format)) {
1956+
final CSVRecord record = parser.iterator().next();
1957+
assertEquals(3, record.size());
1958+
assertEquals("a", record.get(0));
1959+
assertEquals("b", record.get(1));
1960+
assertEquals("", record.get(2));
1961+
}
1962+
// An unquoted trailing delimiter still drops the empty field.
1963+
try (CSVParser parser = CSVParser.parse("a,b,", format)) {
1964+
final CSVRecord record = parser.iterator().next();
1965+
assertEquals(2, record.size());
1966+
}
1967+
}
1968+
19521969
@Test
19531970
void testTrim() throws Exception {
19541971
final Reader in = new StringReader("a,a,a\n\" 1 \",\" 2 \",\" 3 \"\nx,y,z");

0 commit comments

Comments
 (0)