[CSV-175] Support for ignoring trailing delimiter.

[CSV-177] Support trimming leading and trailing blanks.
[CSV-178] Create default formats for Informix UNLOAD and UNLOAD CSV.


git-svn-id: https://svn.apache.org/repos/asf/commons/proper/csv/trunk@1739694 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Gary D. Gregory 2016-04-18 06:16:51 +00:00
parent 103995083c
commit 16b9e2c791
6 changed files with 615 additions and 403 deletions

View File

@ -43,9 +43,12 @@
<action issue="CSV-153" type="update" dev="britter" due-to="Wren">CSVPrinter doesn't skip creation of header record if skipHeaderRecord is set to true</action> <action issue="CSV-153" type="update" dev="britter" due-to="Wren">CSVPrinter doesn't skip creation of header record if skipHeaderRecord is set to true</action>
<action issue="CSV-159" type="add" dev="ggregory" due-to="Yamil Medina">Add IgnoreCase option for accessing header names</action> <action issue="CSV-159" type="add" dev="ggregory" due-to="Yamil Medina">Add IgnoreCase option for accessing header names</action>
<action issue="CSV-169" type="add" dev="ggregory" due-to="Gary Gregory">The null string should be case-sensitive when reading records</action> <action issue="CSV-169" type="add" dev="ggregory" due-to="Gary Gregory">The null string should be case-sensitive when reading records</action>
<action issue="CSV-168" type="fix" dev="ggregory" due-to="Gary Gregory, cornel creanga">CsvFormat.nullString should not be escaped</action> <action issue="CSV-168" type="fix" dev="ggregory" due-to="Gary Gregory, cornel creanga">CSVFormat.nullString should not be escaped</action>
<action issue="CSV-170" type="fix" dev="ggregory" due-to="Gary Gregory, cornel creanga">CSVFormat.MYSQL nullString should be "\N"</action> <action issue="CSV-170" type="fix" dev="ggregory" due-to="Gary Gregory, cornel creanga">CSVFormat.MYSQL nullString should be "\N"</action>
<action issue="CSV-161" type="fix" dev="ggregory" due-to="Gary Gregory, Kristof Meixner, Emmanuel Bourg">Fix Javadoc to say CSVFormat with() methods return a new CSVFormat</action> <action issue="CSV-161" type="fix" dev="ggregory" due-to="Gary Gregory, Kristof Meixner, Emmanuel Bourg">Fix Javadoc to say CSVFormat with() methods return a new CSVFormat</action>
<action issue="CSV-175" type="add" dev="ggregory" due-to="Gary Gregory, Chris Jones">Support for ignoring trailing delimiter.</action>
<action issue="CSV-177" type="add" dev="ggregory" due-to="Gary Gregory">Support trimming leading and trailing blanks.</action>
<action issue="CSV-178" type="add" dev="ggregory" due-to="Gary Gregory">Create default formats for Informix UNLOAD and UNLOAD CSV.</action>
</release> </release>
<release version="1.2" date="2015-08-24" description="Feature and bug fix release"> <release version="1.2" date="2015-08-24" description="Feature and bug fix release">
<action issue="CSV-145" type="fix" dev="ggregory" due-to="Frank Ulbricht">CSVFormat.with* methods clear the header comments</action> <action issue="CSV-145" type="fix" dev="ggregory" due-to="Frank Ulbricht">CSVFormat.with* methods clear the header comments</action>

View File

@ -23,6 +23,7 @@ import static org.apache.commons.csv.Constants.CR;
import static org.apache.commons.csv.Constants.CRLF; import static org.apache.commons.csv.Constants.CRLF;
import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR; import static org.apache.commons.csv.Constants.DOUBLE_QUOTE_CHAR;
import static org.apache.commons.csv.Constants.LF; import static org.apache.commons.csv.Constants.LF;
import static org.apache.commons.csv.Constants.PIPE;
import static org.apache.commons.csv.Constants.TAB; import static org.apache.commons.csv.Constants.TAB;
import java.io.IOException; import java.io.IOException;
@ -164,6 +165,18 @@ public final class CSVFormat implements Serializable {
*/ */
Excel(CSVFormat.EXCEL), Excel(CSVFormat.EXCEL),
/**
* @see CSVFormat#INFORMIX_UNLOAD
* @since 1.3
*/
InformixUnload(CSVFormat.INFORMIX_UNLOAD),
/**
* @see CSVFormat#INFORMIX_UNLOAD_CSV
* @since 1.3
*/
InformixUnloadCsv(CSVFormat.INFORMIX_UNLOAD_CSV),
/** /**
* @see CSVFormat#MYSQL * @see CSVFormat#MYSQL
*/ */
@ -207,10 +220,11 @@ public final class CSVFormat implements Serializable {
* <li>withRecordSeparator("\r\n")</li> * <li>withRecordSeparator("\r\n")</li>
* <li>withIgnoreEmptyLines(true)</li> * <li>withIgnoreEmptyLines(true)</li>
* </ul> * </ul>
*
* @see Predefined#Default * @see Predefined#Default
*/ */
public static final CSVFormat DEFAULT = new CSVFormat(COMMA, DOUBLE_QUOTE_CHAR, null, null, null, false, true, public static final CSVFormat DEFAULT = new CSVFormat(COMMA, DOUBLE_QUOTE_CHAR, null, null, null, false, true, CRLF,
CRLF, null, null, null, false, false, false); null, null, null, false, false, false, false, false);
/** /**
* Excel file format (using a comma as the value delimiter). Note that the actual value delimiter used by Excel is * Excel file format (using a comma as the value delimiter). Note that the actual value delimiter used by Excel is
@ -238,10 +252,64 @@ public final class CSVFormat implements Serializable {
* Note: this is currently like {@link #RFC4180} plus {@link #withAllowMissingColumnNames(boolean) * Note: this is currently like {@link #RFC4180} plus {@link #withAllowMissingColumnNames(boolean)
* withAllowMissingColumnNames(true)}. * withAllowMissingColumnNames(true)}.
* </p> * </p>
*
* @see Predefined#Excel * @see Predefined#Excel
*/ */
public static final CSVFormat EXCEL = DEFAULT.withIgnoreEmptyLines(false).withAllowMissingColumnNames(); public static final CSVFormat EXCEL = DEFAULT.withIgnoreEmptyLines(false).withAllowMissingColumnNames();
/**
* Default Informix CSV UNLOAD format used by the {@code UNLOAD TO file_name} operation.
*
* <p>
* This is a comma-delimited format with a LF character as the line separator. Values are not quoted and special
* characters are escaped with {@code '\'}. The default NULL string is {@code "\\N"}.
* </p>
*
* <p>
* Settings are:
* </p>
* <ul>
* <li>withDelimiter(',')</li>
* <li>withQuote("\"")</li>
* <li>withRecordSeparator('\n')</li>
* <li>withEscape('\\')</li>
* </ul>
*
* @see Predefined#MySQL
* @see <a href=
* "http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm">
* http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm</a>
* @since 1.3
*/
public static final CSVFormat INFORMIX_UNLOAD = DEFAULT.withDelimiter(PIPE).withEscape(BACKSLASH)
.withQuote(DOUBLE_QUOTE_CHAR).withRecordSeparator(LF);
/**
* Default Informix CSV UNLOAD format used by the {@code UNLOAD TO file_name} operation.
*
* <p>
* This is a comma-delimited format with a LF character as the line separator. Values are not quoted and special
* characters are escaped with {@code '\'}. The default NULL string is {@code "\\N"}.
* </p>
*
* <p>
* Settings are:
* </p>
* <ul>
* <li>withDelimiter(',')</li>
* <li>withQuote("\"")</li>
* <li>withRecordSeparator('\n')</li>
* </ul>
*
* @see Predefined#MySQL
* @see <a href=
* "http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm">
* http://www.ibm.com/support/knowledgecenter/SSBJG3_2.5.0/com.ibm.gen_busug.doc/c_fgl_InOutSql_UNLOAD.htm</a>
* @since 1.3
*/
public static final CSVFormat INFORMIX_UNLOAD_CSV = DEFAULT.withDelimiter(COMMA).withQuote(DOUBLE_QUOTE_CHAR)
.withRecordSeparator(LF);
/** /**
* Default MySQL format used by the {@code SELECT INTO OUTFILE} and {@code LOAD DATA INFILE} operations. * Default MySQL format used by the {@code SELECT INTO OUTFILE} and {@code LOAD DATA INFILE} operations.
* *
@ -263,8 +331,8 @@ public final class CSVFormat implements Serializable {
* </ul> * </ul>
* *
* @see Predefined#MySQL * @see Predefined#MySQL
* @see <a href="http://dev.mysql.com/doc/refman/5.1/en/load-data.html"> * @see <a href="http://dev.mysql.com/doc/refman/5.1/en/load-data.html"> http://dev.mysql.com/doc/refman/5.1/en/load
* http://dev.mysql.com/doc/refman/5.1/en/load-data.html</a> * -data.html</a>
*/ */
public static final CSVFormat MYSQL = DEFAULT.withDelimiter(TAB).withEscape(BACKSLASH).withIgnoreEmptyLines(false) public static final CSVFormat MYSQL = DEFAULT.withDelimiter(TAB).withEscape(BACKSLASH).withIgnoreEmptyLines(false)
.withQuote(null).withRecordSeparator(LF).withNullString("\\N"); .withQuote(null).withRecordSeparator(LF).withNullString("\\N");
@ -281,6 +349,7 @@ public final class CSVFormat implements Serializable {
* <li>withRecordSeparator("\r\n")</li> * <li>withRecordSeparator("\r\n")</li>
* <li>withIgnoreEmptyLines(false)</li> * <li>withIgnoreEmptyLines(false)</li>
* </ul> * </ul>
*
* @see Predefined#RFC4180 * @see Predefined#RFC4180
*/ */
public static final CSVFormat RFC4180 = DEFAULT.withIgnoreEmptyLines(false); public static final CSVFormat RFC4180 = DEFAULT.withIgnoreEmptyLines(false);
@ -299,6 +368,7 @@ public final class CSVFormat implements Serializable {
* <li>withRecordSeparator("\r\n")</li> * <li>withRecordSeparator("\r\n")</li>
* <li>withIgnoreSurroundingSpaces(true)</li> * <li>withIgnoreSurroundingSpaces(true)</li>
* </ul> * </ul>
*
* @see Predefined#TDF * @see Predefined#TDF
*/ */
public static final CSVFormat TDF = DEFAULT.withDelimiter(TAB).withIgnoreSurroundingSpaces(); public static final CSVFormat TDF = DEFAULT.withDelimiter(TAB).withIgnoreSurroundingSpaces();
@ -348,7 +418,8 @@ 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, null, false, false, false); return new CSVFormat(delimiter, null, null, null, null, false, false, null, null, null, null, false, false,
false, false, false);
} }
/** /**
@ -391,6 +462,10 @@ public final class CSVFormat implements Serializable {
private final boolean skipHeaderRecord; private final boolean skipHeaderRecord;
private final boolean trailingDelimiter;
private final boolean trim;
/** /**
* Creates a customized CSV format. * Creates a customized CSV format.
* *
@ -422,6 +497,9 @@ public final class CSVFormat implements Serializable {
* TODO * TODO
* @param ignoreHeaderCase * @param ignoreHeaderCase
* TODO * TODO
* @param trim
* TODO
* @param trailingDelimiter TODO
* @throws IllegalArgumentException * @throws IllegalArgumentException
* if the delimiter is a line break character * if the delimiter is a line break character
*/ */
@ -429,7 +507,7 @@ public final class CSVFormat implements Serializable {
final Character commentStart, final Character escape, final boolean ignoreSurroundingSpaces, final Character commentStart, final Character escape, final boolean ignoreSurroundingSpaces,
final boolean ignoreEmptyLines, final String recordSeparator, final String nullString, final boolean ignoreEmptyLines, final String recordSeparator, final String nullString,
final Object[] headerComments, final String[] header, final boolean skipHeaderRecord, final Object[] headerComments, final String[] header, final boolean skipHeaderRecord,
final boolean allowMissingColumnNames, final boolean ignoreHeaderCase) { final boolean allowMissingColumnNames, final boolean ignoreHeaderCase, boolean trim, boolean trailingDelimiter) {
this.delimiter = delimiter; this.delimiter = delimiter;
this.quoteCharacter = quoteChar; this.quoteCharacter = quoteChar;
this.quoteMode = quoteMode; this.quoteMode = quoteMode;
@ -444,6 +522,8 @@ public final class CSVFormat implements Serializable {
this.header = header == null ? null : header.clone(); this.header = header == null ? null : header.clone();
this.skipHeaderRecord = skipHeaderRecord; this.skipHeaderRecord = skipHeaderRecord;
this.ignoreHeaderCase = ignoreHeaderCase; this.ignoreHeaderCase = ignoreHeaderCase;
this.trailingDelimiter = trailingDelimiter;
this.trim = trim;
validate(); validate();
} }
@ -620,11 +700,9 @@ public final class CSVFormat implements Serializable {
/** /**
* Gets the String to convert to and from {@code null}. * Gets the String to convert to and from {@code null}.
* <ul> * <ul>
* <li> * <li><strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
* <strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
* records.</li> * records.</li>
* <li> * <li><strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
* <strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
* </ul> * </ul>
* *
* @return the String to convert to and from {@code null}. No substitution occurs if {@code null} * @return the String to convert to and from {@code null}. No substitution occurs if {@code null}
@ -669,6 +747,24 @@ public final class CSVFormat implements Serializable {
return skipHeaderRecord; return skipHeaderRecord;
} }
/**
* Returns whether to add a trailing delimiter.
*
* @return whether to add a trailing delimiter.
*/
public boolean getTrailingDelimiter() {
return trailingDelimiter;
}
/**
* Returns whether to trim leading and trailing blanks.
*
* @return whether to trim leading and trailing blanks.
*/
public boolean getTrim() {
return trim;
}
@Override @Override
public int hashCode() { public int hashCode() {
final int prime = 31; final int prime = 31;
@ -829,28 +925,28 @@ public final class CSVFormat implements Serializable {
} }
if (quoteCharacter != null && delimiter == quoteCharacter.charValue()) { if (quoteCharacter != null && delimiter == quoteCharacter.charValue()) {
throw new IllegalArgumentException("The quoteChar character and the delimiter cannot be the same ('" + throw new IllegalArgumentException(
quoteCharacter + "')"); "The quoteChar character and the delimiter cannot be the same ('" + quoteCharacter + "')");
} }
if (escapeCharacter != null && delimiter == escapeCharacter.charValue()) { if (escapeCharacter != null && delimiter == escapeCharacter.charValue()) {
throw new IllegalArgumentException("The escape character and the delimiter cannot be the same ('" + throw new IllegalArgumentException(
escapeCharacter + "')"); "The escape character and the delimiter cannot be the same ('" + escapeCharacter + "')");
} }
if (commentMarker != null && delimiter == commentMarker.charValue()) { if (commentMarker != null && delimiter == commentMarker.charValue()) {
throw new IllegalArgumentException("The comment start character and the delimiter cannot be the same ('" + throw new IllegalArgumentException(
commentMarker + "')"); "The comment start character and the delimiter cannot be the same ('" + commentMarker + "')");
} }
if (quoteCharacter != null && quoteCharacter.equals(commentMarker)) { if (quoteCharacter != null && quoteCharacter.equals(commentMarker)) {
throw new IllegalArgumentException("The comment start character and the quoteChar cannot be the same ('" + throw new IllegalArgumentException(
commentMarker + "')"); "The comment start character and the quoteChar cannot be the same ('" + commentMarker + "')");
} }
if (escapeCharacter != null && escapeCharacter.equals(commentMarker)) { if (escapeCharacter != null && escapeCharacter.equals(commentMarker)) {
throw new IllegalArgumentException("The comment start and the escape character cannot be the same ('" + throw new IllegalArgumentException(
commentMarker + "')"); "The comment start and the escape character cannot be the same ('" + commentMarker + "')");
} }
if (escapeCharacter == null && quoteMode == QuoteMode.NONE) { if (escapeCharacter == null && quoteMode == QuoteMode.NONE) {
@ -862,8 +958,8 @@ public final class CSVFormat implements Serializable {
final Set<String> dupCheck = new HashSet<String>(); final Set<String> dupCheck = new HashSet<String>();
for (final String hdr : header) { for (final String hdr : header) {
if (!dupCheck.add(hdr)) { if (!dupCheck.add(hdr)) {
throw new IllegalArgumentException("The header contains a duplicate entry: '" + hdr + "' in " + throw new IllegalArgumentException(
Arrays.toString(header)); "The header contains a duplicate entry: '" + hdr + "' in " + Arrays.toString(header));
} }
} }
} }
@ -891,7 +987,7 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -926,7 +1022,7 @@ public final class CSVFormat implements Serializable {
} }
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -944,7 +1040,7 @@ public final class CSVFormat implements Serializable {
} }
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -975,12 +1071,12 @@ public final class CSVFormat implements Serializable {
} }
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escape, ignoreSurroundingSpaces, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escape, ignoreSurroundingSpaces,
ignoreEmptyLines, recordSeparator, nullString, headerComments, header, skipHeaderRecord, ignoreEmptyLines, recordSeparator, nullString, headerComments, header, skipHeaderRecord,
allowMissingColumnNames, ignoreHeaderCase); allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
* Returns a new {@code CSVFormat} with the header of the format set from the result set metadata. * Returns a new {@code CSVFormat} with the header of the format set from the result set metadata. The header can
* The header can either be parsed automatically from the input file with: * either be parsed automatically from the input file with:
* *
* <pre> * <pre>
* CSVFormat format = aformat.withHeader(); * CSVFormat format = aformat.withHeader();
@ -1009,8 +1105,8 @@ public final class CSVFormat implements Serializable {
} }
/** /**
* Returns a new {@code CSVFormat} with the header of the format set from the result set metadata. * Returns a new {@code CSVFormat} with the header of the format set from the result set metadata. The header can
* The header can either be parsed automatically from the input file with: * either be parsed automatically from the input file with:
* *
* <pre> * <pre>
* CSVFormat format = aformat.withHeader(); * CSVFormat format = aformat.withHeader();
@ -1045,12 +1141,12 @@ public final class CSVFormat implements Serializable {
} }
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, labels, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, labels,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
* Returns a new {@code CSVFormat} with the header of the format set to the given values. * Returns a new {@code CSVFormat} with the header of the format set to the given values. The header can either be
* The header can either be parsed automatically from the input file with: * parsed automatically from the input file with:
* *
* <pre> * <pre>
* CSVFormat format = aformat.withHeader(); * CSVFormat format = aformat.withHeader();
@ -1074,12 +1170,12 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
* Returns a new {@code CSVFormat} with the header comments of the format set to the given values. * Returns a new {@code CSVFormat} with the header comments of the format set to the given values. The comments will
* The comments will be printed first, before the headers. This setting is ignored by the parser. * be printed first, before the headers. This setting is ignored by the parser.
* *
* <pre> * <pre>
* CSVFormat format = aformat.withHeaderComments(&quot;Generated by Apache Commons CSV 1.1.&quot;, new Date()); * CSVFormat format = aformat.withHeaderComments(&quot;Generated by Apache Commons CSV 1.1.&quot;, new Date());
@ -1095,7 +1191,7 @@ public final class CSVFormat implements Serializable {
public CSVFormat withHeaderComments(final Object... headerComments) { public CSVFormat withHeaderComments(final Object... headerComments) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -1120,7 +1216,7 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -1138,14 +1234,14 @@ public final class CSVFormat implements Serializable {
* Returns a new {@code CSVFormat} with whether header names should be accessed ignoring case. * Returns a new {@code CSVFormat} with whether header names should be accessed ignoring case.
* *
* @param ignoreHeaderCase * @param ignoreHeaderCase
* the case mapping behavior, {@code true} to access name/values, {@code false} to leave the * the case mapping behavior, {@code true} to access name/values, {@code false} to leave the mapping as
* mapping as is. * is.
* @return A new CSVFormat that will ignore case header name if specified as {@code true} * @return A new CSVFormat that will ignore case header name if specified as {@code true}
*/ */
public CSVFormat withIgnoreHeaderCase(final boolean ignoreHeaderCase) { public CSVFormat withIgnoreHeaderCase(final boolean ignoreHeaderCase) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter, return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -1170,17 +1266,15 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
* Returns a new {@code CSVFormat} with conversions to and from null for strings on input and output. * Returns a new {@code CSVFormat} with conversions to and from null for strings on input and output.
* <ul> * <ul>
* <li> * <li><strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
* <strong>Reading:</strong> Converts strings equal to the given {@code nullString} to {@code null} when reading
* records.</li> * records.</li>
* <li> * <li><strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
* <strong>Writing:</strong> Writes {@code null} as the given {@code nullString} when writing records.</li>
* </ul> * </ul>
* *
* @param nullString * @param nullString
@ -1191,7 +1285,7 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -1222,7 +1316,7 @@ public final class CSVFormat implements Serializable {
} }
return new CSVFormat(delimiter, quoteChar, quoteMode, commentMarker, escapeCharacter, ignoreSurroundingSpaces, return new CSVFormat(delimiter, quoteChar, quoteMode, commentMarker, escapeCharacter, ignoreSurroundingSpaces,
ignoreEmptyLines, recordSeparator, nullString, headerComments, header, skipHeaderRecord, ignoreEmptyLines, recordSeparator, nullString, headerComments, header, skipHeaderRecord,
allowMissingColumnNames, ignoreHeaderCase); allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -1236,7 +1330,7 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -1274,7 +1368,7 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
} }
/** /**
@ -1301,6 +1395,52 @@ 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, headerComments, header, ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase); skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
}
/**
* Returns a new {@code CSVFormat} with whether to trim leading and trailing blanks.
*
* @param trim
* whether to trim leading and trailing blanks.
*
* @return A new CSVFormat that is equal to this but with the specified trim setting.
*/
public CSVFormat withTrim(final boolean trim) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
}
/**
* Returns a new {@code CSVFormat} to add a trailing delimiter.
* *
* @return A new CSVFormat that is equal to this but with the trailing delimiter setting.
*/
public CSVFormat withTrailingDelimiter() {
return withTrailingDelimiter(true);
}
/**
* Returns a new {@code CSVFormat} with whether to add a trailing delimiter.
*
* @param trim
* whether to add a trailing delimiter.
*
* @return A new CSVFormat that is equal to this but with the specified trailing delimiter setting.
*/
public CSVFormat withTrailingDelimiter(final boolean trailingDelimiter) {
return new CSVFormat(delimiter, quoteCharacter, quoteMode, commentMarker, escapeCharacter,
ignoreSurroundingSpaces, ignoreEmptyLines, recordSeparator, nullString, headerComments, header,
skipHeaderRecord, allowMissingColumnNames, ignoreHeaderCase, trim, trailingDelimiter);
}
/**
* Returns a new {@code CSVFormat} to trim leading and trailing blanks.
*
* @return A new CSVFormat that is equal to this but with the trim setting on.
*/
public CSVFormat withTrim() {
return withTrim(true);
} }
} }

View File

@ -286,10 +286,14 @@ public final class CSVParser implements Iterable<CSVRecord>, Closeable {
this.recordNumber = recordNumber - 1; this.recordNumber = recordNumber - 1;
} }
private void addRecordValue() { private void addRecordValue(boolean lastRecord) {
final String input = this.reusableToken.content.toString(); final String input = this.reusableToken.content.toString();
final String inputClean = this.format.getTrim() ? input.trim() : input;
if (lastRecord && inputClean.isEmpty() && this.format.getTrailingDelimiter()) {
return;
}
final String nullString = this.format.getNullString(); final String nullString = this.format.getNullString();
this.record.add(input.equals(nullString) ? null : input); this.record.add(inputClean.equals(nullString) ? null : inputClean);
} }
/** /**
@ -497,14 +501,14 @@ public final class CSVParser implements Iterable<CSVRecord>, Closeable {
this.lexer.nextToken(this.reusableToken); this.lexer.nextToken(this.reusableToken);
switch (this.reusableToken.type) { switch (this.reusableToken.type) {
case TOKEN: case TOKEN:
this.addRecordValue(); this.addRecordValue(false);
break; break;
case EORECORD: case EORECORD:
this.addRecordValue(); this.addRecordValue(true);
break; break;
case EOF: case EOF:
if (this.reusableToken.isReady) { if (this.reusableToken.isReady) {
this.addRecordValue(); this.addRecordValue(true);
} }
break; break;
case INVALID: case INVALID:

View File

@ -128,6 +128,7 @@ public final class CSVPrinter implements Flushable, Closeable {
} else { } else {
strValue = value.toString(); strValue = value.toString();
} }
strValue = format.getTrim() ? strValue.trim() : strValue;
this.print(value, strValue, 0, strValue.length()); this.print(value, strValue, 0, strValue.length());
} }
@ -351,6 +352,9 @@ public final class CSVPrinter implements Flushable, Closeable {
* If an I/O error occurs * If an I/O error occurs
*/ */
public void println() throws IOException { public void println() throws IOException {
if (format.getTrailingDelimiter()) {
out.append(format.getDelimiter());
}
final String recordSeparator = format.getRecordSeparator(); final String recordSeparator = format.getRecordSeparator();
if (recordSeparator != null) { if (recordSeparator != null) {
out.append(recordSeparator); out.append(recordSeparator);

View File

@ -911,6 +911,30 @@ public class CSVParserTest {
} }
} }
@Test
public void testTrailingDelimiter() throws Exception {
final Reader in = new StringReader("a,a,a,\n\"1\",\"2\",\"3\",\nx,y,z,");
final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().withTrailingDelimiter()
.parse(in).iterator();
final CSVRecord record = records.next();
assertEquals("1", record.get("X"));
assertEquals("2", record.get("Y"));
assertEquals("3", record.get("Z"));
Assert.assertEquals(3, record.size());
}
@Test
public void testTrim() throws Exception {
final Reader in = new StringReader("a,a,a\n\" 1 \",\" 2 \",\" 3 \"\nx,y,z");
final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().withTrim()
.parse(in).iterator();
final CSVRecord record = records.next();
assertEquals("1", record.get("X"));
assertEquals("2", record.get("Y"));
assertEquals("3", record.get("Z"));
Assert.assertEquals(3, record.size());
}
private void validateLineNumbers(final String lineSeparator) throws IOException { private void validateLineNumbers(final String lineSeparator) throws IOException {
final CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c", final CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c",
CSVFormat.DEFAULT.withRecordSeparator(lineSeparator)); CSVFormat.DEFAULT.withRecordSeparator(lineSeparator));

View File

@ -50,8 +50,6 @@ public class CSVPrinterTest {
private static final int ITERATIONS_FOR_RANDOM_TEST = 50000; private static final int ITERATIONS_FOR_RANDOM_TEST = 50000;
private final String recordSeparator = CSVFormat.DEFAULT.getRecordSeparator();
private static String printable(final String s) { private static String printable(final String s) {
final StringBuilder sb = new StringBuilder(); final StringBuilder sb = new StringBuilder();
for (int i = 0; i < s.length(); i++) { for (int i = 0; i < s.length(); i++) {
@ -65,6 +63,8 @@ public class CSVPrinterTest {
return sb.toString(); return sb.toString();
} }
private final String recordSeparator = CSVFormat.DEFAULT.getRecordSeparator();
private void doOneRandom(final CSVFormat format) throws Exception { private void doOneRandom(final CSVFormat format) throws Exception {
final Random r = new Random(); final Random r = new Random();
@ -97,6 +97,31 @@ public class CSVPrinterTest {
parser.close(); parser.close();
} }
private void doRandom(final CSVFormat format, final int iter) throws Exception {
for (int i = 0; i < iter; i++) {
doOneRandom(format);
}
}
/**
* Converts an input CSV array into expected output values WRT NULLs. NULL strings are converted to null values
* because the parser will convert these strings to null.
*/
private <T> T[] expectNulls(final T[] original, final CSVFormat csvFormat) {
final T[] fixed = original.clone();
for (int i = 0; i < fixed.length; i++) {
if (ObjectUtils.equals(csvFormat.getNullString(), fixed[i])) {
fixed[i] = null;
}
}
return fixed;
}
private Connection geH2Connection() throws SQLException, ClassNotFoundException {
Class.forName("org.h2.Driver");
return DriverManager.getConnection("jdbc:h2:mem:my_test;", "sa", "");
}
private String[][] generateLines(final int nLines, final int nCol) { private String[][] generateLines(final int nLines, final int nCol) {
final String[][] lines = new String[nLines][]; final String[][] lines = new String[nLines][];
for (int i = 0; i < nLines; i++) { for (int i = 0; i < nLines; i++) {
@ -109,10 +134,18 @@ public class CSVPrinterTest {
return lines; return lines;
} }
private void doRandom(final CSVFormat format, final int iter) throws Exception { private CSVPrinter printWithHeaderComments(final StringWriter sw, final Date now, final CSVFormat baseFormat)
for (int i = 0; i < iter; i++) { throws IOException {
doOneRandom(format); CSVFormat format = baseFormat;
} // Use withHeaderComments first to test CSV-145
format = format.withHeaderComments("Generated by Apache Commons CSV 1.1", now);
format = format.withCommentMarker('#');
format = format.withHeader("Col1", "Col2");
final CSVPrinter csvPrinter = format.print(sw);
csvPrinter.printRecord("A", "B");
csvPrinter.printRecord("C", "D");
csvPrinter.close();
return csvPrinter;
} }
private String randStr() { private String randStr() {
@ -163,6 +196,58 @@ public class CSVPrinterTest {
return new String(buf); return new String(buf);
} }
private void setUpTable(final Connection connection) throws SQLException {
final Statement statement = connection.createStatement();
try {
statement.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))");
statement.execute("insert into TEST values(1, 'r1')");
statement.execute("insert into TEST values(2, 'r2')");
} finally {
statement.close();
}
}
@Test
public void testDelimeterQuoted() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
printer.print("a,b,c");
printer.print("xyz");
assertEquals("'a,b,c',xyz", sw.toString());
printer.close();
}
@Test
public void testDelimeterQuoteNONE() throws IOException {
final StringWriter sw = new StringWriter();
final CSVFormat format = CSVFormat.DEFAULT.withEscape('!').withQuoteMode(QuoteMode.NONE);
final CSVPrinter printer = new CSVPrinter(sw, format);
printer.print("a,b,c");
printer.print("xyz");
assertEquals("a!,b!,c,xyz", sw.toString());
printer.close();
}
@Test
public void testDelimiterEscaped() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape('!').withQuote(null));
printer.print("a,b,c");
printer.print("xyz");
assertEquals("a!,b!,c,xyz", sw.toString());
printer.close();
}
@Test
public void testDelimiterPlain() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
printer.print("a,b,c");
printer.print("xyz");
assertEquals("a,b,c,xyz", sw.toString());
printer.close();
}
@Test @Test
public void testDisabledComment() throws IOException { public void testDisabledComment() throws IOException {
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
@ -173,6 +258,72 @@ public class CSVPrinterTest {
printer.close(); printer.close();
} }
@Test
public void testEOLEscaped() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
printer.print("a\rb\nc");
printer.print("x\fy\bz");
assertEquals("a!rb!nc,x\fy\bz", sw.toString());
printer.close();
}
@Test
public void testEOLPlain() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
printer.print("a\rb\nc");
printer.print("x\fy\bz");
assertEquals("a\rb\nc,x\fy\bz", sw.toString());
printer.close();
}
@Test
public void testEOLQuoted() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
printer.print("a\rb\nc");
printer.print("x\by\fz");
assertEquals("'a\rb\nc',x\by\fz", sw.toString());
printer.close();
}
@Test
public void testEscapeBackslash() throws IOException {
StringWriter sw = new StringWriter();
final char quoteChar = '\'';
final String eol = "\r\n";
CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("\\");
printer.close();
assertEquals("'\\'", sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("\\\r");
printer.close();
assertEquals("'\\\r'", sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("X\\\r");
printer.close();
assertEquals("'X\\\r'", sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.printRecord(new Object[] { "\\\r" });
printer.close();
assertEquals("'\\\r'" + eol, sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("\\\\");
printer.close();
assertEquals("'\\\\'", sw.toString());
}
@Test @Test
public void testExcelPrintAllArrayOfArrays() throws IOException { public void testExcelPrintAllArrayOfArrays() throws IOException {
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
@ -228,75 +379,52 @@ public class CSVPrinterTest {
printer.close(); printer.close();
} }
private Connection geH2Connection() throws SQLException, ClassNotFoundException { @Test
Class.forName("org.h2.Driver"); public void testHeader() throws IOException {
return DriverManager.getConnection("jdbc:h2:mem:my_test;", "sa", ""); final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3"));
printer.printRecord("a", "b", "c");
printer.printRecord("x", "y", "z");
assertEquals("C1,C2,C3\r\na,b,c\r\nx,y,z\r\n", sw.toString());
printer.close();
} }
@Test @Test
@Ignore public void testHeaderCommentExcel() throws IOException {
public void testJira135All() throws IOException {
final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, format); final Date now = new Date();
final List<String> list = new LinkedList<String>(); final CSVFormat format = CSVFormat.EXCEL;
list.add("\""); final CSVPrinter csvPrinter = printWithHeaderComments(sw, now, format);
list.add("\n"); assertEquals("# Generated by Apache Commons CSV 1.1\r\n# " + now + "\r\nCol1,Col2\r\nA,B\r\nC,D\r\n",
list.add("\\"); sw.toString());
printer.printRecord(list); csvPrinter.close();
printer.close();
final String expected = "\"\\\"\",\"\\n\",\"\\\"" + format.getRecordSeparator();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
} }
@Test @Test
@Ignore public void testHeaderCommentTdf() throws IOException {
public void testJira135_part3() throws IOException {
final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, format); final Date now = new Date();
final List<String> list = new LinkedList<String>(); final CSVFormat format = CSVFormat.TDF;
list.add("\\"); final CSVPrinter csvPrinter = printWithHeaderComments(sw, now, format);
printer.printRecord(list); assertEquals("# Generated by Apache Commons CSV 1.1\r\n# " + now + "\r\nCol1\tCol2\r\nA\tB\r\nC\tD\r\n",
printer.close(); sw.toString());
final String expected = "\"\\\\\"" + format.getRecordSeparator(); csvPrinter.close();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
} }
@Test @Test
@Ignore public void testHeaderNotSet() throws IOException {
public void testJira135_part2() throws IOException {
final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, format); final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
final List<String> list = new LinkedList<String>(); printer.printRecord("a", "b", "c");
list.add("\n"); printer.printRecord("x", "y", "z");
printer.printRecord(list); assertEquals("a,b,c\r\nx,y,z\r\n", sw.toString());
printer.close(); printer.close();
final String expected = "\"\\n\"" + format.getRecordSeparator();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
} }
@Test @Test(expected = IllegalArgumentException.class)
@Ignore public void testInvalidFormat() throws Exception {
public void testJira135_part1() throws IOException { final CSVFormat invalidFormat = CSVFormat.DEFAULT.withDelimiter(CR);
final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\'); new CSVPrinter(new StringWriter(), invalidFormat).close();
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, format);
final List<String> list = new LinkedList<String>();
list.add("\"");
printer.printRecord(list);
printer.close();
final String expected = "\"\\\"\"" + format.getRecordSeparator();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
} }
@Test @Test
@ -361,15 +489,70 @@ public class CSVPrinterTest {
} }
} }
private void setUpTable(final Connection connection) throws SQLException { @Test
final Statement statement = connection.createStatement(); @Ignore
try { public void testJira135_part1() throws IOException {
statement.execute("CREATE TABLE TEST(ID INT PRIMARY KEY, NAME VARCHAR(255))"); final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
statement.execute("insert into TEST values(1, 'r1')"); final StringWriter sw = new StringWriter();
statement.execute("insert into TEST values(2, 'r2')"); final CSVPrinter printer = new CSVPrinter(sw, format);
} finally { final List<String> list = new LinkedList<String>();
statement.close(); list.add("\"");
printer.printRecord(list);
printer.close();
final String expected = "\"\\\"\"" + format.getRecordSeparator();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
} }
@Test
@Ignore
public void testJira135_part2() throws IOException {
final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, format);
final List<String> list = new LinkedList<String>();
list.add("\n");
printer.printRecord(list);
printer.close();
final String expected = "\"\\n\"" + format.getRecordSeparator();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
}
@Test
@Ignore
public void testJira135_part3() throws IOException {
final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, format);
final List<String> list = new LinkedList<String>();
list.add("\\");
printer.printRecord(list);
printer.close();
final String expected = "\"\\\\\"" + format.getRecordSeparator();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
}
@Test
@Ignore
public void testJira135All() throws IOException {
final CSVFormat format = CSVFormat.DEFAULT.withRecordSeparator('\n').withQuote('"').withEscape('\\');
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, format);
final List<String> list = new LinkedList<String>();
list.add("\"");
list.add("\n");
list.add("\\");
printer.printRecord(list);
printer.close();
final String expected = "\"\\\"\",\"\\n\",\"\\\"" + format.getRecordSeparator();
assertEquals(expected, sw.toString());
final String[] record0 = toFirstRecordValues(expected, format);
assertArrayEquals(expectNulls(list.toArray(), format), record0);
} }
@Test @Test
@ -382,11 +565,6 @@ public class CSVPrinterTest {
printer.close(); printer.close();
} }
@Test
public void testMySqlNullStringDefault() throws IOException {
assertEquals("\\N", CSVFormat.MYSQL.getNullString());
}
@Test @Test
public void testMySqlNullOutput() throws IOException { public void testMySqlNullOutput() throws IOException {
Object[] s = new String[] { "NULL", null }; Object[] s = new String[] { "NULL", null };
@ -489,22 +667,85 @@ public class CSVPrinterTest {
assertArrayEquals(expectNulls(s, format), record0); assertArrayEquals(expectNulls(s, format), record0);
} }
/** @Test
* Converts an input CSV array into expected output values WRT NULLs. NULL strings are converted to null values public void testMySqlNullStringDefault() throws IOException {
* because the parser will convert these strings to null. assertEquals("\\N", CSVFormat.MYSQL.getNullString());
*/
private <T> T[] expectNulls(final T[] original, final CSVFormat csvFormat) {
final T[] fixed = original.clone();
for (int i = 0; i < fixed.length; i++) {
if (ObjectUtils.equals(csvFormat.getNullString(), fixed[i])) {
fixed[i] = null;
}
}
return fixed;
} }
private String[] toFirstRecordValues(final String expected, final CSVFormat format) throws IOException { @Test(expected = IllegalArgumentException.class)
return CSVParser.parse(expected, format).getRecords().get(0).values(); public void testNewCsvPrinterAppendableNullFormat() throws Exception {
new CSVPrinter(new StringWriter(), null).close();
}
@Test(expected = IllegalArgumentException.class)
public void testNewCSVPrinterNullAppendableFormat() throws Exception {
new CSVPrinter(null, CSVFormat.DEFAULT).close();
}
@Test
public void testParseCustomNullValues() throws IOException {
final StringWriter sw = new StringWriter();
final CSVFormat format = CSVFormat.DEFAULT.withNullString("NULL");
final CSVPrinter printer = new CSVPrinter(sw, format);
printer.printRecord("a", null, "b");
printer.close();
final String csvString = sw.toString();
assertEquals("a,NULL,b" + recordSeparator, csvString);
final Iterable<CSVRecord> iterable = format.parse(new StringReader(csvString));
final Iterator<CSVRecord> iterator = iterable.iterator();
final CSVRecord record = iterator.next();
assertEquals("a", record.get(0));
assertEquals(null, record.get(1));
assertEquals("b", record.get(2));
assertFalse(iterator.hasNext());
((CSVParser) iterable).close();
}
@Test
public void testPlainEscaped() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
printer.print("abc");
printer.print("xyz");
assertEquals("abc,xyz", sw.toString());
printer.close();
}
@Test
public void testPlainPlain() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
printer.print("abc");
printer.print("xyz");
assertEquals("abc,xyz", sw.toString());
printer.close();
}
@Test
public void testPlainQuoted() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
printer.print("abc");
assertEquals("abc", sw.toString());
printer.close();
}
@Test
public void testPrint() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = CSVFormat.DEFAULT.print(sw);
printer.printRecord("a", "b\\c");
assertEquals("a,b\\c" + recordSeparator, sw.toString());
printer.close();
}
@Test
public void testPrintCustomNullValues() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withNullString("NULL"));
printer.printRecord("a", null, "b");
assertEquals("a,NULL,b" + recordSeparator, sw.toString());
printer.close();
} }
@Test @Test
@ -570,15 +811,6 @@ public class CSVPrinterTest {
printer.close(); printer.close();
} }
@Test
public void testPrint() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = CSVFormat.DEFAULT.print(sw);
printer.printRecord("a", "b\\c");
assertEquals("a,b\\c" + recordSeparator, sw.toString());
printer.close();
}
@Test @Test
public void testPrintNullValues() throws IOException { public void testPrintNullValues() throws IOException {
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
@ -588,34 +820,6 @@ public class CSVPrinterTest {
printer.close(); printer.close();
} }
@Test
public void testPrintCustomNullValues() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withNullString("NULL"));
printer.printRecord("a", null, "b");
assertEquals("a,NULL,b" + recordSeparator, sw.toString());
printer.close();
}
@Test
public void testParseCustomNullValues() throws IOException {
final StringWriter sw = new StringWriter();
final CSVFormat format = CSVFormat.DEFAULT.withNullString("NULL");
final CSVPrinter printer = new CSVPrinter(sw, format);
printer.printRecord("a", null, "b");
printer.close();
final String csvString = sw.toString();
assertEquals("a,NULL,b" + recordSeparator, csvString);
final Iterable<CSVRecord> iterable = format.parse(new StringReader(csvString));
final Iterator<CSVRecord> iterator = iterable.iterator();
final CSVRecord record = iterator.next();
assertEquals("a", record.get(0));
assertEquals(null, record.get(1));
assertEquals("b", record.get(2));
assertFalse(iterator.hasNext());
((CSVParser) iterable).close();
}
@Test @Test
public void testQuoteAll() throws IOException { public void testQuoteAll() throws IOException {
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
@ -649,23 +853,14 @@ public class CSVPrinterTest {
doRandom(CSVFormat.MYSQL, ITERATIONS_FOR_RANDOM_TEST); doRandom(CSVFormat.MYSQL, ITERATIONS_FOR_RANDOM_TEST);
} }
@Test
public void testRandomTdf() throws Exception {
doRandom(CSVFormat.TDF, ITERATIONS_FOR_RANDOM_TEST);
}
@Test @Test
public void testRandomRfc4180() throws Exception { public void testRandomRfc4180() throws Exception {
doRandom(CSVFormat.RFC4180, ITERATIONS_FOR_RANDOM_TEST); doRandom(CSVFormat.RFC4180, ITERATIONS_FOR_RANDOM_TEST);
} }
@Test @Test
public void testPlainQuoted() throws IOException { public void testRandomTdf() throws Exception {
final StringWriter sw = new StringWriter(); doRandom(CSVFormat.TDF, ITERATIONS_FOR_RANDOM_TEST);
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
printer.print("abc");
assertEquals("abc", sw.toString());
printer.close();
} }
@Test @Test
@ -689,142 +884,17 @@ public class CSVPrinterTest {
} }
@Test @Test
public void testDelimeterQuoted() throws IOException { public void testSkipHeaderRecordFalse() throws IOException {
// functionally identical to testHeader, used to test CSV-153
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\'')); final CSVPrinter printer = new CSVPrinter(sw,
printer.print("a,b,c"); CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3").withSkipHeaderRecord(false));
printer.print("xyz");
assertEquals("'a,b,c',xyz", sw.toString());
printer.close();
}
@Test
public void testDelimeterQuoteNONE() throws IOException {
final StringWriter sw = new StringWriter();
final CSVFormat format = CSVFormat.DEFAULT.withEscape('!').withQuoteMode(QuoteMode.NONE);
final CSVPrinter printer = new CSVPrinter(sw, format);
printer.print("a,b,c");
printer.print("xyz");
assertEquals("a!,b!,c,xyz", sw.toString());
printer.close();
}
@Test
public void testEOLQuoted() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote('\''));
printer.print("a\rb\nc");
printer.print("x\by\fz");
assertEquals("'a\rb\nc',x\by\fz", sw.toString());
printer.close();
}
@Test
public void testEscapeBackslash() throws IOException {
StringWriter sw = new StringWriter();
final char quoteChar = '\'';
final String eol = "\r\n";
CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("\\");
printer.close();
assertEquals("'\\'", sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("\\\r");
printer.close();
assertEquals("'\\\r'", sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("X\\\r");
printer.close();
assertEquals("'X\\\r'", sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.printRecord(new Object[] { "\\\r" });
printer.close();
assertEquals("'\\\r'" + eol, sw.toString());
sw = new StringWriter();
printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(quoteChar));
printer.print("\\\\");
printer.close();
assertEquals("'\\\\'", sw.toString());
}
@Test
public void testPlainEscaped() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
printer.print("abc");
printer.print("xyz");
assertEquals("abc,xyz", sw.toString());
printer.close();
}
@Test
public void testDelimiterEscaped() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withEscape('!').withQuote(null));
printer.print("a,b,c");
printer.print("xyz");
assertEquals("a!,b!,c,xyz", sw.toString());
printer.close();
}
@Test
public void testEOLEscaped() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withEscape('!'));
printer.print("a\rb\nc");
printer.print("x\fy\bz");
assertEquals("a!rb!nc,x\fy\bz", sw.toString());
printer.close();
}
@Test
public void testPlainPlain() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
printer.print("abc");
printer.print("xyz");
assertEquals("abc,xyz", sw.toString());
printer.close();
}
@Test
public void testDelimiterPlain() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
printer.print("a,b,c");
printer.print("xyz");
assertEquals("a,b,c,xyz", sw.toString());
printer.close();
}
@Test
public void testHeader() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3"));
printer.printRecord("a", "b", "c"); printer.printRecord("a", "b", "c");
printer.printRecord("x", "y", "z"); printer.printRecord("x", "y", "z");
assertEquals("C1,C2,C3\r\na,b,c\r\nx,y,z\r\n", sw.toString()); assertEquals("C1,C2,C3\r\na,b,c\r\nx,y,z\r\n", sw.toString());
printer.close(); printer.close();
} }
@Test
public void testHeaderNotSet() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
printer.printRecord("a", "b", "c");
printer.printRecord("x", "y", "z");
assertEquals("a,b,c\r\nx,y,z\r\n", sw.toString());
printer.close();
}
@Test @Test
public void testSkipHeaderRecordTrue() throws IOException { public void testSkipHeaderRecordTrue() throws IOException {
// functionally identical to testHeaderNotSet, used to test CSV-153 // functionally identical to testHeaderNotSet, used to test CSV-153
@ -838,76 +908,43 @@ public class CSVPrinterTest {
} }
@Test @Test
public void testSkipHeaderRecordFalse() throws IOException { public void testTrimOnOneColumn() throws IOException {
// functionally identical to testHeader, used to test CSV-153
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim());
CSVFormat.DEFAULT.withQuote(null).withHeader("C1", "C2", "C3").withSkipHeaderRecord(false)); printer.print(" A ");
printer.printRecord("a", "b", "c"); assertEquals("A", sw.toString());
printer.printRecord("x", "y", "z");
assertEquals("C1,C2,C3\r\na,b,c\r\nx,y,z\r\n", sw.toString());
printer.close(); printer.close();
} }
@Test @Test
public void testHeaderCommentExcel() throws IOException { public void testTrimOnTwoColumns() throws IOException {
final StringWriter sw = new StringWriter(); final StringWriter sw = new StringWriter();
final Date now = new Date(); final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim());
final CSVFormat format = CSVFormat.EXCEL; printer.print(" A ");
final CSVPrinter csvPrinter = printWithHeaderComments(sw, now, format); printer.print(" B ");
assertEquals("# Generated by Apache Commons CSV 1.1\r\n# " + now + "\r\nCol1,Col2\r\nA,B\r\nC,D\r\n", assertEquals("A,B", sw.toString());
sw.toString());
csvPrinter.close();
}
@Test
public void testHeaderCommentTdf() throws IOException {
final StringWriter sw = new StringWriter();
final Date now = new Date();
final 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, final Date now, final CSVFormat baseFormat)
throws IOException {
CSVFormat format = baseFormat;
// Use withHeaderComments first to test CSV-145
format = format.withHeaderComments("Generated by Apache Commons CSV 1.1", now);
format = format.withCommentMarker('#');
format = format.withHeader("Col1", "Col2");
final CSVPrinter csvPrinter = format.print(sw);
csvPrinter.printRecord("A", "B");
csvPrinter.printRecord("C", "D");
csvPrinter.close();
return csvPrinter;
}
@Test
public void testEOLPlain() throws IOException {
final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withQuote(null));
printer.print("a\rb\nc");
printer.print("x\fy\bz");
assertEquals("a\rb\nc,x\fy\bz", sw.toString());
printer.close(); printer.close();
} }
@Test(expected = IllegalArgumentException.class) @Test
public void testInvalidFormat() throws Exception { public void testTrailingDelimiterOnTwoColumns() throws IOException {
final CSVFormat invalidFormat = CSVFormat.DEFAULT.withDelimiter(CR); final StringWriter sw = new StringWriter();
new CSVPrinter(new StringWriter(), invalidFormat).close(); final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrailingDelimiter());
printer.printRecord("A", "B");
assertEquals("A,B,\r\n", sw.toString());
printer.close();
} }
@Test(expected = IllegalArgumentException.class) @Test
public void testNewCSVPrinterNullAppendableFormat() throws Exception { public void testTrimOffOneColumn() throws IOException {
new CSVPrinter(null, CSVFormat.DEFAULT).close(); final StringWriter sw = new StringWriter();
final CSVPrinter printer = new CSVPrinter(sw, CSVFormat.DEFAULT.withTrim(false));
printer.print(" A ");
assertEquals("\" A \"", sw.toString());
printer.close();
} }
@Test(expected = IllegalArgumentException.class) private String[] toFirstRecordValues(final String expected, final CSVFormat format) throws IOException {
public void testNewCsvPrinterAppendableNullFormat() throws Exception { return CSVParser.parse(expected, format).getRecords().get(0).values();
new CSVPrinter(new StringWriter(), null).close();
} }
} }