Skip to content
Open
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
23 changes: 21 additions & 2 deletions src/main/java/org/apache/commons/csv/CSVFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -2264,6 +2264,11 @@ private void print(final InputStream inputStream, final Appendable out, final bo
* Prints the {@code value} as the next value on the line to {@code out}. The value will be escaped or encapsulated as needed. Useful when one wants to
* avoid creating CSVPrinters. Trims the value if {@link #getTrim()} is true.
*
* <p>
* If {@code value} is a {@link Reader} and {@link #getTrim()} is true, the reader is fully read into memory so the value can be trimmed before printing;
* otherwise the value is streamed without holding it in memory. The reader is never closed by this method.
* </p>
*
* @param value value to output.
* @param out where to print the value.
* @param newRecord if this a new record.
Expand All @@ -2287,8 +2292,15 @@ public void print(final Object value, final Appendable out, final boolean newRec
} else if (value instanceof CharSequence) {
charSequence = (CharSequence) value;
} else if (value instanceof Reader) {
print((Reader) value, out, newRecord);
return;
// CSV-331: the streaming path cannot apply trim(), so when trimming is enabled the reader
// is fully read into memory first; trim() is applied below on the regular path.
// The reader is not closed here: closing it remains the caller's responsibility.
if (getTrim()) {
charSequence = IOUtils.toString((Reader) value);
} else {
print((Reader) value, out, newRecord);
return;
}
} else if (value instanceof InputStream) {
print((InputStream) value, out, newRecord);
return;
Expand Down Expand Up @@ -2348,6 +2360,13 @@ public CSVPrinter print(final Path out, final Charset charset) throws IOExceptio
return print(Files.newBufferedWriter(out, charset));
}

/**
* Prints a Reader value by streaming it without holding the whole value in memory.
* <p>
* Trimming is NOT applied here: when {@link #getTrim()} is true, {@link #print(Object, Appendable, boolean)}
* buffers the reader in memory and takes the regular CharSequence path instead (CSV-331).
* </p>
*/
private void print(final Reader reader, final Appendable out, final boolean newRecord) throws IOException {
// Reader is never null here
if (!newRecord) {
Expand Down
60 changes: 60 additions & 0 deletions src/test/java/org/apache/commons/csv/CSVFormatTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -1373,6 +1373,66 @@ void testTrim() throws IOException {
assertEquals("header", out.toString());
}

/**
* Tests <a href="https://issues.apache.org/jira/browse/CSV-331">CSV-331</a>: trim must also be applied when
* printing a Reader value.
*/
@Test
void testTrimReaderValue() throws IOException {
final CSVFormat formatWithTrim = CSVFormat.DEFAULT.withDelimiter(',').withTrim().withQuote(null).withRecordSeparator(CRLF);

Reader in = new StringReader("a,b,c");
final StringBuilder out = new StringBuilder();
formatWithTrim.print(in, out, true);
assertEquals("a,b,c", out.toString());

in = new StringReader(" x,y,z ");
out.setLength(0);
formatWithTrim.print(in, out, true);
assertEquals("x,y,z", out.toString());

in = new StringReader("");
out.setLength(0);
formatWithTrim.print(in, out, true);
assertEquals("", out.toString());

in = new StringReader("header\r\n");
out.setLength(0);
formatWithTrim.print(in, out, true);
assertEquals("header", out.toString());

// CSV-331 reproduction: ORACLE enables trim.
final StringBuilder oracleOut = new StringBuilder();
CSVFormat.ORACLE.print(new StringReader(" Alice "), oracleOut, true);
assertEquals("Alice", oracleOut.toString());
}

/**
* Tests <a href="https://issues.apache.org/jira/browse/CSV-331">CSV-331</a>: a Reader value must still be
* encapsulated when needed after trimming.
*/
@Test
void testTrimReaderValueWithQuotes() throws IOException {
final CSVFormat formatWithTrim = CSVFormat.DEFAULT.withDelimiter(',').withTrim().withRecordSeparator(CRLF);
final Reader in = new StringReader(" \"a,b\" ");
final StringBuilder out = new StringBuilder();
formatWithTrim.print(in, out, true);
assertEquals("\"\"\"a,b\"\"\"", out.toString());
}

/**
* Tests <a href="https://issues.apache.org/jira/browse/CSV-331">CSV-331</a>: without trim a Reader value must
* keep streaming as-is.
*/
@Test
void testNoTrimReaderValue() throws IOException {
final CSVFormat formatWithoutTrim = CSVFormat.DEFAULT.withDelimiter(',').withQuote(null).withRecordSeparator(CRLF);
final Reader in = new StringReader(" Alice ");
final StringBuilder out = new StringBuilder();
formatWithoutTrim.print(in, out, true);
assertEquals(" Alice ", out.toString());
}

@Test
void testWithCommentStart() {
final CSVFormat formatWithCommentStart = CSVFormat.DEFAULT.withCommentMarker('#');
Expand Down