Skip to content

Commit

Permalink
[CSV-63] CSVPrinter always quotes empty string if it is the first on a
Browse files Browse the repository at this point in the history
line
  • Loading branch information
tha2015 committed Dec 10, 2016
1 parent 9afec3e commit e5f85a0
Show file tree
Hide file tree
Showing 3 changed files with 153 additions and 20 deletions.
44 changes: 37 additions & 7 deletions src/main/java/org/apache/commons/csv/CSVFormat.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@
import java.sql.SQLException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;

/**
Expand Down Expand Up @@ -900,7 +901,7 @@ public CSVPrinter print(final File out, Charset charset) throws IOException {
*
* @param out
* the output.
* @param charset
* @param charset
* A charset.
* @return a printer to an output.
* @throws IOException
Expand All @@ -926,6 +927,10 @@ public CSVPrinter print(final Path out, Charset charset) throws IOException {
* @since 1.4
*/
public void print(final Object value, final Appendable out, final boolean newRecord) throws IOException {
print(value, out, newRecord, false);
}

private void print(final Object value, final Appendable out, final boolean newRecord, final boolean hasMoreValues) throws IOException {
// null values are considered empty
// Only call CharSequence.toString() if you have to, helps GC-free use cases.
CharSequence charSequence;
Expand All @@ -935,19 +940,19 @@ public void print(final Object value, final Appendable out, final boolean newRec
charSequence = value instanceof CharSequence ? (CharSequence) value : value.toString();
}
charSequence = getTrim() ? trim(charSequence) : charSequence;
this.print(value, charSequence, 0, charSequence.length(), out, newRecord);
this.print(value, charSequence, 0, charSequence.length(), out, newRecord, hasMoreValues);
}

private void print(final Object object, final CharSequence value, final int offset, final int len,
final Appendable out, final boolean newRecord) throws IOException {
final Appendable out, final boolean newRecord, final boolean hasMoreValues) throws IOException {
if (!newRecord) {
out.append(getDelimiter());
}
if (object == null) {
out.append(value);
} else if (isQuoteCharacterSet()) {
// the original object is needed so can check for Number
printAndQuote(object, value, offset, len, out, newRecord);
printAndQuote(object, value, offset, len, out, newRecord, hasMoreValues);
} else if (isEscapeCharacterSet()) {
printAndEscape(value, offset, len, out);
} else {
Expand Down Expand Up @@ -1000,7 +1005,7 @@ private void printAndEscape(final CharSequence value, final int offset, final in
*/
// the original object is needed so can check for Number
private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len,
final Appendable out, final boolean newRecord) throws IOException {
final Appendable out, final boolean newRecord, final boolean hasMoreValues) throws IOException {
boolean quote = false;
int start = offset;
int pos = offset;
Expand Down Expand Up @@ -1030,7 +1035,7 @@ private void printAndQuote(final Object object, final CharSequence value, final
// on the line, as it may be the only thing on the
// line. If it were not quoted in that case,
// an empty line has no tokens.
if (newRecord) {
if (newRecord && !hasMoreValues) {
quote = true;
}
} else {
Expand Down Expand Up @@ -1143,7 +1148,32 @@ public void println(final Appendable out) throws IOException {
*/
public void printRecord(final Appendable out, final Object... values) throws IOException {
for (int i = 0; i < values.length; i++) {
print(values[i], out, i == 0);
print(values[i], out, i == 0, i < values.length - 1);
}
println(out);
}

/**
* Prints the given {@code values} to {@code out} as a single record of delimiter separated values followed by the
* record separator.
*
* <p>
* The values will be quoted if needed. Quotes and new-line characters will be escaped. This method adds the record
* separator to the output after printing the record, so there is no need to call {@link #println(Appendable)}.
* </p>
*
* @param out
* where to write.
* @param values
* values to output.
* @throws IOException
* If an I/O error occurs.
* @since 1.5
*/
public void printRecord(final Appendable out, final Iterable<?> values) throws IOException {
boolean firstValue = true;
for (final Iterator<?> it = values.iterator(); it.hasNext(); firstValue = false) {
print(it.next(), out, firstValue, it.hasNext());
}
println(out);
}
Expand Down
44 changes: 31 additions & 13 deletions src/main/java/org/apache/commons/csv/CSVPrinter.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Iterator;

/**
* Prints values in a CSV format.
Expand Down Expand Up @@ -193,10 +194,8 @@ public void println() throws IOException {
* If an I/O error occurs
*/
public void printRecord(final Iterable<?> values) throws IOException {
for (final Object value : values) {
print(value);
}
println();
format.printRecord(out, values);
newRecord = true;
}

/**
Expand Down Expand Up @@ -257,13 +256,24 @@ public void printRecord(final Object... values) throws IOException {
* If an I/O error occurs
*/
public void printRecords(final Iterable<?> values) throws IOException {
for (final Object value : values) {
if (value instanceof Object[]) {
this.printRecord((Object[]) value);
} else if (value instanceof Iterable) {
this.printRecord((Iterable<?>) value);
} else {
this.printRecord(value);
final Iterator<?> it = values.iterator();
if (it.hasNext()) {
final Object firstValue = it.next();

// When printing a single record, use printRecord(Iterable) to handle first value correctly
if (!(firstValue instanceof Object[]) && !(firstValue instanceof Iterable)) {
this.printRecord(values);
return;
}

for (final Object value : values) {
if (value instanceof Object[]) {
this.printRecord((Object[]) value);
} else if (value instanceof Iterable) {
this.printRecord((Iterable<?>) value);
} else {
this.printRecord(value);
}
}
}
}
Expand Down Expand Up @@ -308,6 +318,12 @@ public void printRecords(final Iterable<?> values) throws IOException {
* If an I/O error occurs
*/
public void printRecords(final Object... values) throws IOException {
// When printing a single record, use printRecord(Object...) to handle first value correctly
if (values.length > 0 && !(values[0] instanceof Object[]) && !(values[0] instanceof Iterable)) {
this.printRecord(values);
return;
}

for (final Object value : values) {
if (value instanceof Object[]) {
this.printRecord((Object[]) value);
Expand All @@ -331,11 +347,13 @@ public void printRecords(final Object... values) throws IOException {
*/
public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
final int columnCount = resultSet.getMetaData().getColumnCount();
final Object[] values = new Object[columnCount];
while (resultSet.next()) {
for (int i = 1; i <= columnCount; i++) {
print(resultSet.getObject(i));
values[i - 1] = resultSet.getObject(i);
}
println();
// use printRecord(Object...) to handle first value correctly
this.printRecord(values);
}
}
}
85 changes: 85 additions & 0 deletions src/test/java/org/apache/commons/csv/CSVPrinterTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -518,6 +518,91 @@ public void testJdbcPrinterWithResultSet() throws IOException, ClassNotFoundExce
assertEquals("ID,NAME" + recordSeparator + "1,r1" + recordSeparator + "2,r2" + recordSeparator, sw.toString());
}

@Test
public void testExcelPrintAllArrayOfArraysWithFirstEmptyValue1() throws IOException {
final StringWriter sw = new StringWriter();
try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
printer.printRecords((Object[]) new String[][] { { "", "r1c2" } });
assertEquals(",r1c2" + recordSeparator, sw.toString());
}
}
@Test
public void testExcelPrintAllArrayOfArraysWithFirstEmptyValue2() throws IOException {
final StringWriter sw = new StringWriter();
try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
printer.printRecords((Object[]) new String[][] { { "" } });
assertEquals("\"\"" + recordSeparator, sw.toString());
}
}

@Test
public void testExcelPrintAllArrayOfListsWithFirstEmptyValue1() throws IOException {
final StringWriter sw = new StringWriter();
try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
printer.printRecords(
(Object[]) new List[] { Arrays.asList("", "r1c2") });
assertEquals(",r1c2" + recordSeparator, sw.toString());
}
}

@Test
public void testExcelPrintAllArrayOfListsWithFirstEmptyValue2() throws IOException {
final StringWriter sw = new StringWriter();
try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
printer.printRecords(
(Object[]) new List[] { Arrays.asList("") });
assertEquals("\"\"" + recordSeparator, sw.toString());
}
}

@Test
public void testExcelPrintAllIterableOfListsWithFirstEmptyValue1() throws IOException {
final StringWriter sw = new StringWriter();
try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
printer.printRecords(
Arrays.asList(new List[] { Arrays.asList("", "r1c2") }));
assertEquals(",r1c2" + recordSeparator, sw.toString());
}
}

@Test
public void testExcelPrintAllIterableOfArraysWithFirstEmptyValue2() throws IOException {
final StringWriter sw = new StringWriter();
try (final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.EXCEL)) {
printer.printRecords(Arrays.asList(new String[][] { { "" } }));
assertEquals("\"\"" + recordSeparator, sw.toString());
}
}


@Test
public void testJdbcPrinterWithFirstEmptyValue1() throws IOException, ClassNotFoundException, SQLException {
final StringWriter sw = new StringWriter();
Class.forName("org.h2.Driver");
try (final Connection connection = geH2Connection();) {
try (final Statement stmt = connection.createStatement();
final ResultSet resultSet = stmt.executeQuery("select '' AS EMPTYVALUE, 1 AS ID from DUAL");
final CSVPrinter printer = CSVFormat.DEFAULT.withHeader(resultSet).print(sw)) {
printer.printRecords(resultSet);
}
}
assertEquals("EMPTYVALUE,ID" + recordSeparator + ",1" + recordSeparator, sw.toString());
}

@Test
public void testJdbcPrinterWithFirstEmptyValue2() throws IOException, ClassNotFoundException, SQLException {
final StringWriter sw = new StringWriter();
Class.forName("org.h2.Driver");
try (final Connection connection = geH2Connection();) {
try (final Statement stmt = connection.createStatement();
final ResultSet resultSet = stmt.executeQuery("select '' AS EMPTYVALUE from DUAL");
final CSVPrinter printer = CSVFormat.DEFAULT.withHeader(resultSet).print(sw)) {
printer.printRecords(resultSet);
}
}
assertEquals("EMPTYVALUE" + recordSeparator + "\"\"" + recordSeparator, sw.toString());
}

@Test
public void testJdbcPrinterWithResultSetMetaData() throws IOException, ClassNotFoundException, SQLException {
final StringWriter sw = new StringWriter();
Expand Down

0 comments on commit e5f85a0

Please sign in to comment.