Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 20 additions & 0 deletions src/main/java/org/apache/commons/csv/CSVFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -883,6 +883,16 @@ public Builder setTrailingData(final boolean trailingData) {
/**
* Sets whether to add a trailing delimiter.
*
* <p>
* When writing, a delimiter is appended after the last value of each record. When reading, the empty field
* that such a trailing delimiter produces is dropped so the output round-trips back to the original record;
* a quoted empty trailing field ({@code ""}) is a real value rather than a trailing delimiter and is kept.
* </p>
* <p>
* This is unrelated to {@link #setTrailingData(boolean) trailing data}, which controls whether characters
* after the closing quote of an encapsulated value are tolerated when reading.
* </p>
*
* @param trailingDelimiter whether to add a trailing delimiter.
* @return This instance.
*/
Expand Down Expand Up @@ -2012,6 +2022,16 @@ public boolean getTrailingData() {
/**
* Gets whether to add a trailing delimiter.
*
* <p>
* When writing, a delimiter is appended after the last value of each record. When reading, the empty field
* that such a trailing delimiter produces is dropped so the output round-trips back to the original record;
* a quoted empty trailing field ({@code ""}) is a real value rather than a trailing delimiter and is kept.
* </p>
* <p>
* This is unrelated to {@link #getTrailingData() trailing data}, which controls whether characters after the
* closing quote of an encapsulated value are tolerated when reading.
* </p>
*
* @return whether to add a trailing delimiter.
* @since 1.3
*/
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/org/apache/commons/csv/CSVParser.java
Original file line number Diff line number Diff line change
Expand Up @@ -580,7 +580,9 @@ public CSVParser(final Reader reader, final CSVFormat format, final long charact

private void addRecordValue(final boolean lastRecord) {
final String input = format.trim(reusableToken.content.toString());
if (lastRecord && input.isEmpty() && format.getTrailingDelimiter()) {
// Only drop the empty field produced by an actual trailing delimiter. A quoted empty
// field ("") is a real value, not a trailing delimiter, so it must be kept.
if (lastRecord && input.isEmpty() && format.getTrailingDelimiter() && !reusableToken.isQuoted) {
return;
}
recordList.add(handleNull(input));
Expand Down
17 changes: 17 additions & 0 deletions src/test/java/org/apache/commons/csv/CSVParserTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1949,6 +1949,23 @@ void testTrailingDelimiter() throws Exception {
}
}

@Test
void testTrailingDelimiterKeepsQuotedEmptyLastField() throws Exception {
final CSVFormat format = CSVFormat.DEFAULT.builder().setTrailingDelimiter(true).get();
try (CSVParser parser = CSVParser.parse("a,b,\"\"", format)) {
final CSVRecord record = parser.iterator().next();
assertEquals(3, record.size());
assertEquals("a", record.get(0));
assertEquals("b", record.get(1));
assertEquals("", record.get(2));
}
// An unquoted trailing delimiter still drops the empty field.
try (CSVParser parser = CSVParser.parse("a,b,", format)) {
final CSVRecord record = parser.iterator().next();
assertEquals(2, record.size());
}
}

@Test
void testTrim() throws Exception {
final Reader in = new StringReader("a,a,a\n\" 1 \",\" 2 \",\" 3 \"\nx,y,z");
Expand Down
Loading