diff --git a/src/changes/changes.xml b/src/changes/changes.xml index 72ac11f5..3db75c49 100644 --- a/src/changes/changes.xml +++ b/src/changes/changes.xml @@ -59,6 +59,7 @@ Update CSVParser.parse(File, Charset, CSVFormat) from IO to NIO. Missing separator with print(object) followed by printRecord(Object[]) #157. Fix EOL checking for read array in ExtendedBufferedReader #5. + Print from Reader with embedded quotes generates incorrect output #78. Make CSVRecord#toList() public. Add CSVRecord#stream(). diff --git a/src/main/java/org/apache/commons/csv/CSVFormat.java b/src/main/java/org/apache/commons/csv/CSVFormat.java index 5281a340..550f5165 100644 --- a/src/main/java/org/apache/commons/csv/CSVFormat.java +++ b/src/main/java/org/apache/commons/csv/CSVFormat.java @@ -1747,7 +1747,7 @@ public final class CSVFormat implements Serializable { /** * 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 + * avoid creating CSVPrinters. Trims the value if {@link #getTrim()} is true. * * @param value value to output. * @param out where to print the value. @@ -2120,11 +2120,11 @@ public final class CSVFormat implements Serializable { // write out segment up until this char if (pos > 0) { append(builder.substring(0, pos), appendable); + append(quote, appendable); builder.setLength(0); pos = -1; } - append(quote, appendable); append((char) c, appendable); } pos++; diff --git a/src/test/java/org/apache/commons/csv/CSVFormatTest.java b/src/test/java/org/apache/commons/csv/CSVFormatTest.java index 400cc072..ecb02a09 100644 --- a/src/test/java/org/apache/commons/csv/CSVFormatTest.java +++ b/src/test/java/org/apache/commons/csv/CSVFormatTest.java @@ -971,7 +971,7 @@ public class CSVFormatTest { final Appendable out = new StringBuilder(); final CSVFormat format = CSVFormat.RFC4180.withDelimiter(',').withQuote('"').withEscape('?').withQuoteMode(QuoteMode.NON_NUMERIC); format.print(in, out, true); - assertEquals("\"\"\"\"a,b,c\r\nx,y,z\"", out.toString()); + assertEquals("\"\"\"a,b,c\r\nx,y,z\"", out.toString()); } @Test diff --git a/src/test/java/org/apache/commons/csv/issues/JiraCsv263Test.java b/src/test/java/org/apache/commons/csv/issues/JiraCsv263Test.java new file mode 100644 index 00000000..08436b74 --- /dev/null +++ b/src/test/java/org/apache/commons/csv/issues/JiraCsv263Test.java @@ -0,0 +1,77 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one or more + * contributor license agreements. See the NOTICE file distributed with + * this work for additional information regarding copyright ownership. + * The ASF licenses this file to You under the Apache License, Version 2.0 + * (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.apache.commons.csv.issues; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.io.IOException; +import java.io.Reader; +import java.io.StringReader; + +import org.apache.commons.csv.CSVFormat; +import org.apache.commons.csv.QuoteMode; +import org.junit.jupiter.api.Test; + +/** + * Tests [CSV-263] Print from Reader with embedded quotes generates incorrect output. + */ +public class JiraCsv263Test { + + @Test + public void testPrintFromReaderWithQuotes() throws IOException { + // @formatter:off + final CSVFormat format = CSVFormat.RFC4180.builder() + .setDelimiter(',') + .setQuote('"') + .setEscape('?') + .setQuoteMode(QuoteMode.NON_NUMERIC) + .build(); + // @formatter:on + final StringBuilder out = new StringBuilder(); + + final Reader atStartOnly = new StringReader("\"a,b,c\r\nx,y,z"); + format.print(atStartOnly, out, true); + assertEquals("\"\"\"a,b,c\r\nx,y,z\"", out.toString()); + + final Reader atEndOnly = new StringReader("a,b,c\r\nx,y,z\""); + out.setLength(0); + format.print(atEndOnly, out, true); + assertEquals("\"a,b,c\r\nx,y,z\"\"\"", out.toString()); + + final Reader atBeginEnd = new StringReader("\"a,b,c\r\nx,y,z\""); + out.setLength(0); + format.print(atBeginEnd, out, true); + assertEquals("\"\"\"a,b,c\r\nx,y,z\"\"\"", out.toString()); + + final Reader embeddedBeginMiddle = new StringReader("\"a\",b,c\r\nx,\"y\",z"); + out.setLength(0); + format.print(embeddedBeginMiddle, out, true); + assertEquals("\"\"\"a\"\",b,c\r\nx,\"\"y\"\",z\"", out.toString()); + + final Reader embeddedMiddleEnd = new StringReader("a,\"b\",c\r\nx,y,\"z\""); + out.setLength(0); + format.print(embeddedMiddleEnd, out, true); + assertEquals("\"a,\"\"b\"\",c\r\nx,y,\"\"z\"\"\"", out.toString()); + + final Reader nested = new StringReader("a,\"b \"and\" c\",d"); + out.setLength(0); + format.print(nested, out, true); + assertEquals("\"a,\"\"b \"\"and\"\" c\"\",d\"", out.toString()); + } + +} \ No newline at end of file