diff --git a/src/main/java/org/apache/commons/csv/CSVPrinter.java b/src/main/java/org/apache/commons/csv/CSVPrinter.java index c45bd3ba1..42c01bcc0 100644 --- a/src/main/java/org/apache/commons/csv/CSVPrinter.java +++ b/src/main/java/org/apache/commons/csv/CSVPrinter.java @@ -246,7 +246,7 @@ public void printComment(final String comment) throws IOException { } // falls-through: break intentionally excluded. case LF: - println(); + printCommentSeparator(); appendable.append(format.getCommentMarker().charValue()); // Explicit unboxing is intentional appendable.append(SP); break; @@ -255,12 +255,26 @@ public void printComment(final String comment) throws IOException { break; } } - println(); + printCommentSeparator(); } finally { lock.unlock(); } } + /** + * Ends a comment line with the record separator only. A comment is not a record, so the trailing delimiter that + * {@link #println()} writes must not follow it. + * + * @throws IOException If an I/O error occurs. + */ + private void printCommentSeparator() throws IOException { + final String recordSeparator = format.getRecordSeparator(); + if (recordSeparator != null) { + appendable.append(recordSeparator); + } + newRecord = true; + } + /** * Prints headers for a result set based on its metadata. * diff --git a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java index a34212924..486980068 100644 --- a/src/test/java/org/apache/commons/csv/CSVPrinterTest.java +++ b/src/test/java/org/apache/commons/csv/CSVPrinterTest.java @@ -1107,6 +1107,38 @@ void testMultiLineComment() throws IOException { } } + @Test + void testMultiLineCommentWithTrailingDelimiter() throws IOException { + // A comment is not a record, so the trailing delimiter must not follow comment lines, or it becomes + // part of the comment text on read back. The following data record still gets its trailing delimiter. + final CSVFormat format = CSVFormat.DEFAULT.builder().setCommentMarker('#').setTrailingDelimiter(true).get(); + final StringWriter sw = new StringWriter(); + try (CSVPrinter printer = new CSVPrinter(sw, format)) { + printer.printComment("This is a comment\non multiple lines"); + printer.printRecord("A", "B"); + } + final String string = sw.toString(); + assertEquals("# This is a comment" + RECORD_SEPARATOR + "# on multiple lines" + RECORD_SEPARATOR + "A,B," + RECORD_SEPARATOR, string); + try (CSVParser parser = CSVParser.parse(string, format)) { + final List records = parser.getRecords(); + assertEquals(1, records.size()); + assertEquals("This is a comment\non multiple lines", records.get(0).getComment()); + assertEquals("A", records.get(0).get(0)); + assertEquals("B", records.get(0).get(1)); + } + } + + @Test + void testMultiLineCommentWithoutRecordSeparator() throws IOException { + // A null record separator writes nothing between comment lines, matching the record output path. + final CSVFormat format = CSVFormat.DEFAULT.builder().setCommentMarker('#').setRecordSeparator(null).get(); + final StringWriter sw = new StringWriter(); + try (CSVPrinter printer = new CSVPrinter(sw, format)) { + printer.printComment("a\nb"); + } + assertEquals("# a# b", sw.toString()); + } + @Test void testMySqlNullOutput() throws IOException { Object[] s = new String[] { "NULL", null };