[CSV-130] CSVFormat#withHeader doesn't work well with #printComment, add withHeaderComments(String...)

git-svn-id: https://svn.apache.org/repos/asf/commons/proper/csv/trunk@1623984 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gary D. Gregory 2014-09-10 12:48:02 +00:00
parent 807ddd164b
commit a4f737108b
4 changed files with 151 additions and 47 deletions

View File

@ -39,6 +39,7 @@
</properties> </properties>
<body> <body>
<release version="1.1" date="2014-mm-dd" description="Feature and bug fix release"> <release version="1.1" date="2014-mm-dd" description="Feature and bug fix release">
<action issue="CSV-130" type="fix" dev="ggregory" due-to="Sergei Lebedev">CSVFormat#withHeader doesn't work well with #printComment, add withHeaderComments(String...)</action>
<action issue="CSV-128" type="fix" dev="ggregory">CSVFormat.EXCEL should ignore empty header names</action> <action issue="CSV-128" type="fix" dev="ggregory">CSVFormat.EXCEL should ignore empty header names</action>
<action issue="CSV-129" type="add" dev="ggregory">Add CSVFormat#with 0-arg methods matching boolean arg methods</action> <action issue="CSV-129" type="add" dev="ggregory">Add CSVFormat#with 0-arg methods matching boolean arg methods</action>
<action issue="CSV-132" type="fix" dev="ggregory" due-to="Sascha Szott">Incorrect Javadoc referencing org.apache.commons.csv.CSVFormat withQuote()</action> <action issue="CSV-132" type="fix" dev="ggregory" due-to="Sascha Szott">Incorrect Javadoc referencing org.apache.commons.csv.CSVFormat withQuote()</action>

View File

@ -157,6 +157,7 @@ public final class CSVFormat implements Serializable {
private final String recordSeparator; // for outputs private final String recordSeparator; // for outputs
private final String nullString; // the string to be used for null values private final String nullString; // the string to be used for null values
private final String[] header; // array of header column names private final String[] header; // array of header column names
private final String[] headerComments; // array of header comment lines
private final boolean skipHeaderRecord; private final boolean skipHeaderRecord;
/** /**
@ -173,7 +174,7 @@ public final class CSVFormat implements Serializable {
* </ul> * </ul>
*/ */
public static final CSVFormat DEFAULT = new CSVFormat(COMMA, DOUBLE_QUOTE_CHAR, null, null, null, public static final CSVFormat DEFAULT = new CSVFormat(COMMA, DOUBLE_QUOTE_CHAR, null, null, null,
false, true, CRLF, null, null, false, false); false, true, CRLF, null, null, null, false, false);
/** /**
* Comma separated format as defined by <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>. * Comma separated format as defined by <a href="http://tools.ietf.org/html/rfc4180">RFC 4180</a>.
@ -307,7 +308,7 @@ public final class CSVFormat implements Serializable {
* @see #TDF * @see #TDF
*/ */
public static CSVFormat newFormat(final char delimiter) { public static CSVFormat newFormat(final char delimiter) {
return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, false, false); return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, null, false, false);
} }
/** /**
@ -331,6 +332,7 @@ public final class CSVFormat implements Serializable {
* the line separator to use for output * the line separator to use for output
* @param nullString * @param nullString
* the line separator to use for output * the line separator to use for output
* @param toHeaderComments TODO
* @param header * @param header
* the header * the header
* @param skipHeaderRecord TODO * @param skipHeaderRecord TODO
@ -341,8 +343,8 @@ public final class CSVFormat implements Serializable {
final QuoteMode quoteMode, final Character commentStart, final QuoteMode quoteMode, final Character commentStart,
final Character escape, final boolean ignoreSurroundingSpaces, final Character escape, final boolean ignoreSurroundingSpaces,
final boolean ignoreEmptyLines, final String recordSeparator, final boolean ignoreEmptyLines, final String recordSeparator,
final String nullString, final String[] header, final boolean skipHeaderRecord, final String nullString, final Object[] headerComments, final String[] header,
final boolean allowMissingColumnNames) { final boolean skipHeaderRecord, final boolean allowMissingColumnNames) {
if (isLineBreak(delimiter)) { if (isLineBreak(delimiter)) {
throw new IllegalArgumentException("The delimiter cannot be a line break"); throw new IllegalArgumentException("The delimiter cannot be a line break");
} }
@ -356,6 +358,7 @@ public final class CSVFormat implements Serializable {
this.ignoreEmptyLines = ignoreEmptyLines; this.ignoreEmptyLines = ignoreEmptyLines;
this.recordSeparator = recordSeparator; this.recordSeparator = recordSeparator;
this.nullString = nullString; this.nullString = nullString;
this.headerComments = toStringArray(headerComments);
if (header == null) { if (header == null) {
this.header = null; this.header = null;
} else { } else {
@ -372,6 +375,18 @@ public final class CSVFormat implements Serializable {
validate(); validate();
} }
private String[] toStringArray(Object[] values) {
if (values == null) {
return null;
}
String[] strings = new String[values.length];
for (int i = 0; i < values.length; i++) {
Object value = values[i];
strings[i] = value == null ? null : value.toString();
}
return strings;
}
@Override @Override
public boolean equals(final Object obj) { public boolean equals(final Object obj) {
if (this == obj) { if (this == obj) {
@ -495,6 +510,15 @@ public final class CSVFormat implements Serializable {
return header != null ? header.clone() : null; return header != null ? header.clone() : null;
} }
/**
* Returns a copy of the header comment array.
*
* @return a copy of the header comment array; {@code null} if disabled.
*/
public String[] getHeaderComments() {
return headerComments != null ? headerComments.clone() : null;
}
/** /**
* Specifies whether missing column names are allowed when parsing the header line. * Specifies whether missing column names are allowed when parsing the header line.
* *
@ -701,6 +725,10 @@ public final class CSVFormat implements Serializable {
sb.append(" SurroundingSpaces:ignored"); sb.append(" SurroundingSpaces:ignored");
} }
sb.append(" SkipHeaderRecord:").append(skipHeaderRecord); sb.append(" SkipHeaderRecord:").append(skipHeaderRecord);
if (headerComments != null) {
sb.append(' ');
sb.append("HeaderComments:").append(Arrays.toString(headerComments));
}
if (header != null) { if (header != null) {
sb.append(' '); sb.append(' ');
sb.append("Header:").append(Arrays.toString(header)); sb.append("Header:").append(Arrays.toString(header));
@ -775,8 +803,8 @@ public final class CSVFormat implements Serializable {
throw new IllegalArgumentException("The comment start marker character cannot be a line break"); throw new IllegalArgumentException("The comment start marker character cannot be a line break");
} }
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -793,8 +821,8 @@ public final class CSVFormat implements Serializable {
throw new IllegalArgumentException("The delimiter cannot be a line break"); throw new IllegalArgumentException("The delimiter cannot be a line break");
} }
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -824,8 +852,8 @@ public final class CSVFormat implements Serializable {
throw new IllegalArgumentException("The escape character cannot be a line break"); throw new IllegalArgumentException("The escape character cannot be a line break");
} }
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escape, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escape,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -847,8 +875,27 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withHeader(final String... header) { public CSVFormat withHeader(final String... header) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
}
/**
* Sets the header comments of the format. The comments will be printed first, before the headers.
*
* <pre>
* CSVFormat format = aformat.withHeaderComments("Generated by Apache Commons CSV 1.1.", new Date());</pre>
*
* @param header
* the header, {@code null} if disabled, empty if parsed automatically, user specified otherwise.
*
* @return A new CSVFormat that is equal to this but with the specified header
* @see #withSkipHeaderRecord(boolean)
* @since 1.1
*/
public CSVFormat withHeaderComments(final Object... headerComments) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -872,8 +919,8 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withAllowMissingColumnNames(final boolean allowMissingColumnNames) { public CSVFormat withAllowMissingColumnNames(final boolean allowMissingColumnNames) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -897,8 +944,8 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withIgnoreEmptyLines(final boolean ignoreEmptyLines) { public CSVFormat withIgnoreEmptyLines(final boolean ignoreEmptyLines) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -922,8 +969,8 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpaces) { public CSVFormat withIgnoreSurroundingSpaces(final boolean ignoreSurroundingSpaces) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -943,8 +990,8 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withNullString(final String nullString) { public CSVFormat withNullString(final String nullString) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -974,8 +1021,8 @@ public final class CSVFormat implements Serializable {
throw new IllegalArgumentException("The quoteChar cannot be a line break"); throw new IllegalArgumentException("The quoteChar cannot be a line break");
} }
return new CSVFormat(delimiter, quoteChar, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteChar, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -988,8 +1035,8 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withQuoteMode(final QuoteMode quoteModePolicy) { public CSVFormat withQuoteMode(final QuoteMode quoteModePolicy) {
return new CSVFormat(delimiter, quoteCharacter, quoteModePolicy, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteModePolicy, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -1022,8 +1069,8 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withRecordSeparator(final String recordSeparator) { public CSVFormat withRecordSeparator(final String recordSeparator) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
/** /**
@ -1052,7 +1099,7 @@ public final class CSVFormat implements Serializable {
*/ */
public CSVFormat withSkipHeaderRecord(final boolean skipHeaderRecord) { public CSVFormat withSkipHeaderRecord(final boolean skipHeaderRecord) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, header, skipHeaderRecord, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, null, header,
allowMissingColumnNames); skipHeaderRecord, allowMissingColumnNames);
} }
} }

View File

@ -50,13 +50,13 @@ public final class CSVPrinter implements Flushable, Closeable {
* </p> * </p>
* *
* @param out * @param out
* stream to which to print. Must not be null. * stream to which to print. Must not be null.
* @param format * @param format
* the CSV format. Must not be null. * the CSV format. Must not be null.
* @throws IOException * @throws IOException
* thrown if the optional header cannot be printed. * thrown if the optional header cannot be printed.
* @throws IllegalArgumentException * @throws IllegalArgumentException
* thrown if the parameters of the format are inconsistent or if either out or format are null. * thrown if the parameters of the format are inconsistent or if either out or format are null.
*/ */
public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException { public CSVPrinter(final Appendable out, final CSVFormat format) throws IOException {
Assertions.notNull(out, "out"); Assertions.notNull(out, "out");
@ -66,6 +66,13 @@ public final class CSVPrinter implements Flushable, Closeable {
this.format = format; this.format = format;
// TODO: Is it a good idea to do this here instead of on the first call to a print method? // TODO: Is it a good idea to do this here instead of on the first call to a print method?
// It seems a pain to have to track whether the header has already been printed or not. // It seems a pain to have to track whether the header has already been printed or not.
if (format.getHeaderComments() != null) {
for (String line : format.getHeaderComments()) {
if (line != null) {
this.printComment(line);
}
}
}
if (format.getHeader() != null) { if (format.getHeader() != null) {
this.printRecord((Object[]) format.getHeader()); this.printRecord((Object[]) format.getHeader());
} }
@ -113,8 +120,8 @@ public final class CSVPrinter implements Flushable, Closeable {
this.print(value, strValue, 0, strValue.length()); this.print(value, strValue, 0, strValue.length());
} }
private void print(final Object object, final CharSequence value, private void print(final Object object, final CharSequence value, final int offset, final int len)
final int offset, final int len) throws IOException { throws IOException {
if (!newRecord) { if (!newRecord) {
out.append(format.getDelimiter()); out.append(format.getDelimiter());
} }
@ -172,8 +179,8 @@ public final class CSVPrinter implements Flushable, Closeable {
* Note: must only be called if quoting is enabled, otherwise will generate NPE * Note: must only be called if quoting is enabled, otherwise will generate NPE
*/ */
// the original object is needed so can check for Number // the original object is needed so can check for Number
private void printAndQuote(final Object object, final CharSequence value, private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len)
final int offset, final int len) throws IOException { throws IOException {
boolean quote = false; boolean quote = false;
int start = offset; int start = offset;
int pos = offset; int pos = offset;
@ -381,11 +388,16 @@ public final class CSVPrinter implements Flushable, Closeable {
/** /**
* Prints all the objects in the given collection handling nested collections/arrays as records. * Prints all the objects in the given collection handling nested collections/arrays as records.
* *
* <p>If the given collection only contains simple objects, this method will print a single record like * <p>
* If the given collection only contains simple objects, this method will print a single record like
* {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements * {@link #printRecord(Iterable)}. If the given collections contains nested collections/arrays those nested elements
* will each be printed as records using {@link #printRecord(Object...)}.</p> * will each be printed as records using {@link #printRecord(Object...)}.
* </p>
*
* <p>
* Given the following data structure:
* </p>
* *
* <p>Given the following data structure:</p>
* <pre> * <pre>
* <code> * <code>
* List&lt;String[]&gt; data = ... * List&lt;String[]&gt; data = ...
@ -395,7 +407,10 @@ public final class CSVPrinter implements Flushable, Closeable {
* </code> * </code>
* </pre> * </pre>
* *
* <p>Calling this method will print:</p> * <p>
* Calling this method will print:
* </p>
*
* <pre> * <pre>
* <code> * <code>
* A, B, C * A, B, C
@ -424,11 +439,16 @@ public final class CSVPrinter implements Flushable, Closeable {
/** /**
* Prints all the objects in the given array handling nested collections/arrays as records. * Prints all the objects in the given array handling nested collections/arrays as records.
* *
* <p>If the given array only contains simple objects, this method will print a single record like * <p>
* If the given array only contains simple objects, this method will print a single record like
* {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested * {@link #printRecord(Object...)}. If the given collections contains nested collections/arrays those nested
* elements will each be printed as records using {@link #printRecord(Object...)}.</p> * elements will each be printed as records using {@link #printRecord(Object...)}.
* </p>
*
* <p>
* Given the following data structure:
* </p>
* *
* <p>Given the following data structure:</p>
* <pre> * <pre>
* <code> * <code>
* String[][] data = new String[3][] * String[][] data = new String[3][]
@ -438,7 +458,10 @@ public final class CSVPrinter implements Flushable, Closeable {
* </code> * </code>
* </pre> * </pre>
* *
* <p>Calling this method will print:</p> * <p>
* Calling this method will print:
* </p>
*
* <pre> * <pre>
* <code> * <code>
* A, B, C * A, B, C
@ -467,11 +490,12 @@ public final class CSVPrinter implements Flushable, Closeable {
/** /**
* Prints all the objects in the given JDBC result set. * Prints all the objects in the given JDBC result set.
* *
* @param resultSet result set * @param resultSet
* the values to print. * result set the values to print.
* @throws IOException * @throws IOException
* If an I/O error occurs * If an I/O error occurs
* @throws SQLException if a database access error occurs * @throws SQLException
* if a database access error occurs
*/ */
public void printRecords(final ResultSet resultSet) throws SQLException, IOException { public void printRecords(final ResultSet resultSet) throws SQLException, IOException {
final int columnCount = resultSet.getMetaData().getColumnCount(); final int columnCount = resultSet.getMetaData().getColumnCount();

View File

@ -29,6 +29,7 @@ import java.sql.DriverManager;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.Arrays; import java.util.Arrays;
import java.util.Date;
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Random; import java.util.Random;
@ -496,6 +497,37 @@ public class CSVPrinterTest {
printer.close(); printer.close();
} }
@Test
public void testHeaderCommentExcel() throws IOException {
final StringWriter sw = new StringWriter();
Date now = new Date();
CSVFormat format = CSVFormat.EXCEL;
final CSVPrinter csvPrinter = printWithHeaderComments(sw, now, format);
assertEquals("# Generated by Apache Commons CSV 1.1\r\n# " + now + "\r\nCol1,Col2\r\nA,B\r\nC,D\r\n", sw.toString());
csvPrinter.close();
}
@Test
public void testHeaderCommentTdf() throws IOException {
final StringWriter sw = new StringWriter();
Date now = new Date();
CSVFormat format = CSVFormat.TDF;
final CSVPrinter csvPrinter = printWithHeaderComments(sw, now, format);
assertEquals("# Generated by Apache Commons CSV 1.1\r\n# " + now + "\r\nCol1\tCol2\r\nA\tB\r\nC\tD\r\n", sw.toString());
csvPrinter.close();
}
private CSVPrinter printWithHeaderComments(final StringWriter sw, Date now, CSVFormat format)
throws IOException {
format = format.withCommentMarker('#').withHeader("Col1", "Col2");
format = format.withHeaderComments("Generated by Apache Commons CSV 1.1", now);
final CSVPrinter csvPrinter = format.print(sw);
csvPrinter.printRecord("A", "B");
csvPrinter.printRecord("C", "D");
csvPrinter.close();
return csvPrinter;
}
@Test @Test
public void testEOLPlain() throws IOException { public void testEOLPlain() throws IOException {
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();