2020import static org .apache .commons .csv .Constants .BACKSLASH ;
2121import static org .apache .commons .csv .Constants .COMMA ;
2222import static org .apache .commons .csv .Constants .COMMENT ;
23- import static org .apache .commons .csv .Constants .EMPTY ;
2423import static org .apache .commons .csv .Constants .CR ;
2524import static org .apache .commons .csv .Constants .CRLF ;
2625import static org .apache .commons .csv .Constants .DOUBLE_QUOTE_CHAR ;
26+ import static org .apache .commons .csv .Constants .EMPTY ;
2727import static org .apache .commons .csv .Constants .LF ;
2828import static org .apache .commons .csv .Constants .PIPE ;
2929import static org .apache .commons .csv .Constants .SP ;
3636import java .io .Reader ;
3737import java .io .Serializable ;
3838import java .io .StringWriter ;
39+ import java .io .Writer ;
3940import java .nio .charset .Charset ;
4041import java .nio .file .Files ;
4142import java .nio .file .Path ;
@@ -398,7 +399,7 @@ public CSVFormat getFormat() {
398399 .withQuoteMode (QuoteMode .MINIMAL )
399400 .withSkipHeaderRecord (false );
400401 // @formatter:off
401-
402+
402403 /**
403404 * Default MongoDB TSV format used by the {@code mongoexport} operation.
404405 * <p>
@@ -434,7 +435,7 @@ public CSVFormat getFormat() {
434435 .withQuoteMode (QuoteMode .MINIMAL )
435436 .withSkipHeaderRecord (false );
436437 // @formatter:off
437-
438+
438439 /**
439440 * Default MySQL format used by the {@code SELECT INTO OUTFILE} and {@code LOAD DATA INFILE} operations.
440441 *
@@ -707,6 +708,8 @@ public static CSVFormat valueOf(final String format) {
707708
708709 private final Character quoteCharacter ; // null if quoting is disabled
709710
711+ private final String quotedNullString ;
712+
710713 private final QuoteMode quoteMode ;
711714
712715 private final String recordSeparator ; // for outputs
@@ -759,11 +762,11 @@ public static CSVFormat valueOf(final String format) {
759762 * if the delimiter is a line break character
760763 */
761764 private CSVFormat (final char delimiter , final Character quoteChar , final QuoteMode quoteMode ,
762- final Character commentStart , final Character escape , final boolean ignoreSurroundingSpaces ,
763- final boolean ignoreEmptyLines , final String recordSeparator , final String nullString ,
764- final Object [] headerComments , final String [] header , final boolean skipHeaderRecord ,
765- final boolean allowMissingColumnNames , final boolean ignoreHeaderCase , final boolean trim ,
766- final boolean trailingDelimiter , final boolean autoFlush ) {
765+ final Character commentStart , final Character escape , final boolean ignoreSurroundingSpaces ,
766+ final boolean ignoreEmptyLines , final String recordSeparator , final String nullString ,
767+ final Object [] headerComments , final String [] header , final boolean skipHeaderRecord ,
768+ final boolean allowMissingColumnNames , final boolean ignoreHeaderCase , final boolean trim ,
769+ final boolean trailingDelimiter , final boolean autoFlush ) {
767770 this .delimiter = delimiter ;
768771 this .quoteCharacter = quoteChar ;
769772 this .quoteMode = quoteMode ;
@@ -781,6 +784,7 @@ private CSVFormat(final char delimiter, final Character quoteChar, final QuoteMo
781784 this .trailingDelimiter = trailingDelimiter ;
782785 this .trim = trim ;
783786 this .autoFlush = autoFlush ;
787+ this .quotedNullString = quoteCharacter + nullString + quoteCharacter ;
784788 validate ();
785789 }
786790
@@ -1172,32 +1176,41 @@ public void print(final Object value, final Appendable out, final boolean newRec
11721176 charSequence = EMPTY ;
11731177 } else {
11741178 if (QuoteMode .ALL == quoteMode ) {
1175- charSequence = quoteCharacter + nullString + quoteCharacter ;
1179+ charSequence = quotedNullString ;
11761180 } else {
11771181 charSequence = nullString ;
11781182 }
11791183 }
11801184 } else {
1181- charSequence = value instanceof CharSequence ? (CharSequence ) value : value .toString ();
1185+ if (value instanceof CharSequence ) {
1186+ charSequence = (CharSequence ) value ;
1187+ } else if (value instanceof Reader ) {
1188+ print ((Reader ) value , out , newRecord );
1189+ return ;
1190+ } else {
1191+ charSequence = value .toString ();
1192+ }
11821193 }
11831194 charSequence = getTrim () ? trim (charSequence ) : charSequence ;
1184- this . print (value , charSequence , 0 , charSequence . length () , out , newRecord );
1195+ print (value , charSequence , out , newRecord );
11851196 }
11861197
1187- private void print (final Object object , final CharSequence value , final int offset , final int len ,
1188- final Appendable out , final boolean newRecord ) throws IOException {
1198+ private void print (final Object object , final CharSequence value , final Appendable out , final boolean newRecord )
1199+ throws IOException {
1200+ final int offset = 0 ;
1201+ final int len = value .length ();
11891202 if (!newRecord ) {
11901203 out .append (getDelimiter ());
11911204 }
11921205 if (object == null ) {
11931206 out .append (value );
11941207 } else if (isQuoteCharacterSet ()) {
11951208 // the original object is needed so can check for Number
1196- printAndQuote (object , value , offset , len , out , newRecord );
1209+ printWithQuotes (object , value , out , newRecord );
11971210 } else if (isEscapeCharacterSet ()) {
1198- printAndEscape (value , offset , len , out );
1211+ printWithEscapes (value , out );
11991212 } else {
1200- out .append (value , offset , offset + len );
1213+ out .append (value , offset , len );
12011214 }
12021215 }
12031216
@@ -1221,14 +1234,90 @@ public CSVPrinter print(final Path out, final Charset charset) throws IOExceptio
12211234 return print (Files .newBufferedWriter (out , charset ));
12221235 }
12231236
1237+ private void print (final Reader reader , final Appendable out , final boolean newRecord ) throws IOException {
1238+ // Reader is never null
1239+ if (!newRecord ) {
1240+ out .append (getDelimiter ());
1241+ }
1242+ if (isQuoteCharacterSet ()) {
1243+ // the original object is needed so can check for Number
1244+ printWithQuotes (reader , out , newRecord );
1245+ } else if (isEscapeCharacterSet ()) {
1246+ printWithEscapes (reader , out );
1247+ } else if (out instanceof Writer ) {
1248+ IOUtils .copyLarge (reader , (Writer ) out );
1249+ } else {
1250+ IOUtils .copy (reader , out );
1251+ }
1252+
1253+ }
1254+
1255+ /**
1256+ * Prints to the {@link System#out}.
1257+ *
1258+ * <p>
1259+ * See also {@link CSVPrinter}.
1260+ * </p>
1261+ *
1262+ * @return a printer to {@link System#out}.
1263+ * @throws IOException
1264+ * thrown if the optional header cannot be printed.
1265+ * @since 1.5
1266+ */
1267+ public CSVPrinter printer () throws IOException {
1268+ return new CSVPrinter (System .out , this );
1269+ }
1270+
1271+ /**
1272+ * Outputs the trailing delimiter (if set) followed by the record separator (if set).
1273+ *
1274+ * @param out
1275+ * where to write
1276+ * @throws IOException
1277+ * If an I/O error occurs
1278+ * @since 1.4
1279+ */
1280+ public void println (final Appendable out ) throws IOException {
1281+ if (getTrailingDelimiter ()) {
1282+ out .append (getDelimiter ());
1283+ }
1284+ if (recordSeparator != null ) {
1285+ out .append (recordSeparator );
1286+ }
1287+ }
1288+
1289+ /**
1290+ * Prints the given {@code values} to {@code out} as a single record of delimiter separated values followed by the
1291+ * record separator.
1292+ *
1293+ * <p>
1294+ * The values will be quoted if needed. Quotes and new-line characters will be escaped. This method adds the record
1295+ * separator to the output after printing the record, so there is no need to call {@link #println(Appendable)}.
1296+ * </p>
1297+ *
1298+ * @param out
1299+ * where to write.
1300+ * @param values
1301+ * values to output.
1302+ * @throws IOException
1303+ * If an I/O error occurs.
1304+ * @since 1.4
1305+ */
1306+ public void printRecord (final Appendable out , final Object ... values ) throws IOException {
1307+ for (int i = 0 ; i < values .length ; i ++) {
1308+ print (values [i ], out , i == 0 );
1309+ }
1310+ println (out );
1311+ }
1312+
12241313 /*
12251314 * Note: must only be called if escaping is enabled, otherwise will generate NPE
12261315 */
1227- private void printAndEscape (final CharSequence value , final int offset , final int len , final Appendable out )
1228- throws IOException {
1229- int start = offset ;
1230- int pos = offset ;
1231- final int end = offset + len ;
1316+ private void printWithEscapes (final CharSequence value , final Appendable out ) throws IOException {
1317+ int start = 0 ;
1318+ int pos = 0 ;
1319+ final int len = value . length () ;
1320+ final int end = len ;
12321321
12331322 final char delim = getDelimiter ();
12341323 final char escape = getEscapeCharacter ().charValue ();
@@ -1251,7 +1340,6 @@ private void printAndEscape(final CharSequence value, final int offset, final in
12511340
12521341 start = pos + 1 ; // start on the current char after this one
12531342 }
1254-
12551343 pos ++;
12561344 }
12571345
@@ -1261,16 +1349,54 @@ private void printAndEscape(final CharSequence value, final int offset, final in
12611349 }
12621350 }
12631351
1352+ private void printWithEscapes (final Reader reader , final Appendable out ) throws IOException {
1353+ int start = 0 ;
1354+ int pos = 0 ;
1355+
1356+ final char delim = getDelimiter ();
1357+ final char escape = getEscapeCharacter ().charValue ();
1358+ final StringBuilder builder = new StringBuilder (IOUtils .DEFAULT_BUFFER_SIZE );
1359+
1360+ int c ;
1361+ while (-1 != (c = reader .read ())) {
1362+ builder .append ((char ) c );
1363+ if (c == CR || c == LF || c == delim || c == escape ) {
1364+ // write out segment up until this char
1365+ if (pos > start ) {
1366+ out .append (builder .substring (start , pos ));
1367+ builder .setLength (0 );
1368+ }
1369+ if (c == LF ) {
1370+ c = 'n' ;
1371+ } else if (c == CR ) {
1372+ c = 'r' ;
1373+ }
1374+
1375+ out .append (escape );
1376+ out .append ((char ) c );
1377+
1378+ start = pos + 1 ; // start on the current char after this one
1379+ }
1380+ pos ++;
1381+ }
1382+
1383+ // write last segment
1384+ if (pos > start ) {
1385+ out .append (builder .substring (start , pos ));
1386+ }
1387+ }
1388+
12641389 /*
12651390 * Note: must only be called if quoting is enabled, otherwise will generate NPE
12661391 */
12671392 // the original object is needed so can check for Number
1268- private void printAndQuote (final Object object , final CharSequence value , final int offset , final int len ,
1269- final Appendable out , final boolean newRecord ) throws IOException {
1393+ private void printWithQuotes (final Object object , final CharSequence value , final Appendable out ,
1394+ final boolean newRecord ) throws IOException {
12701395 boolean quote = false ;
1271- int start = offset ;
1272- int pos = offset ;
1273- final int end = offset + len ;
1396+ int start = 0 ;
1397+ int pos = 0 ;
1398+ final int len = value .length ();
1399+ final int end = len ;
12741400
12751401 final char delimChar = getDelimiter ();
12761402 final char quoteChar = getQuoteCharacter ().charValue ();
@@ -1289,7 +1415,7 @@ private void printAndQuote(final Object object, final CharSequence value, final
12891415 break ;
12901416 case NONE :
12911417 // Use the existing escaping code
1292- printAndEscape (value , offset , len , out );
1418+ printWithEscapes (value , out );
12931419 return ;
12941420 case MINIMAL :
12951421 if (len <= 0 ) {
@@ -1371,61 +1497,21 @@ private void printAndQuote(final Object object, final CharSequence value, final
13711497 }
13721498
13731499 /**
1374- * Prints to the {@link System#out}.
1375- *
1376- * <p>
1377- * See also {@link CSVPrinter}.
1378- * </p>
1500+ * Always use quotes unless QuoteMode is NONE, so we not have to look ahead.
13791501 *
1380- * @return a printer to {@link System#out}.
13811502 * @throws IOException
1382- * thrown if the optional header cannot be printed.
1383- * @since 1.5
13841503 */
1385- public CSVPrinter printer () throws IOException {
1386- return new CSVPrinter (System .out , this );
1387- }
1504+ private void printWithQuotes (final Reader reader , final Appendable out , final boolean newRecord ) throws IOException {
1505+ final char quoteChar = getQuoteCharacter ().charValue ();
13881506
1389- /**
1390- * Outputs the trailing delimiter (if set) followed by the record separator (if set).
1391- *
1392- * @param out
1393- * where to write
1394- * @throws IOException
1395- * If an I/O error occurs
1396- * @since 1.4
1397- */
1398- public void println (final Appendable out ) throws IOException {
1399- if (getTrailingDelimiter ()) {
1400- out .append (getDelimiter ());
1401- }
1402- if (recordSeparator != null ) {
1403- out .append (recordSeparator );
1507+ if (getQuoteMode () == QuoteMode .NONE ) {
1508+ printWithEscapes (reader , out );
1509+ return ;
14041510 }
1405- }
14061511
1407- /**
1408- * Prints the given {@code values} to {@code out} as a single record of delimiter separated values followed by the
1409- * record separator.
1410- *
1411- * <p>
1412- * The values will be quoted if needed. Quotes and new-line characters will be escaped. This method adds the record
1413- * separator to the output after printing the record, so there is no need to call {@link #println(Appendable)}.
1414- * </p>
1415- *
1416- * @param out
1417- * where to write.
1418- * @param values
1419- * values to output.
1420- * @throws IOException
1421- * If an I/O error occurs.
1422- * @since 1.4
1423- */
1424- public void printRecord (final Appendable out , final Object ... values ) throws IOException {
1425- for (int i = 0 ; i < values .length ; i ++) {
1426- print (values [i ], out , i == 0 );
1427- }
1428- println (out );
1512+ out .append (quoteChar );
1513+ IOUtils .copy (reader , out );
1514+ out .append (quoteChar );
14291515 }
14301516
14311517 @ Override
@@ -1589,8 +1675,8 @@ public CSVFormat withAllowMissingColumnNames(final boolean allowMissingColumnNam
15891675 */
15901676 public CSVFormat withAutoFlush (final boolean autoFlush ) {
15911677 return new CSVFormat (delimiter , quoteCharacter , quoteMode , commentMarker , escapeCharacter ,
1592- ignoreSurroundingSpaces , ignoreEmptyLines , recordSeparator , nullString , headerComments , header ,
1593- skipHeaderRecord , allowMissingColumnNames , ignoreHeaderCase , trim , trailingDelimiter , autoFlush );
1678+ ignoreSurroundingSpaces , ignoreEmptyLines , recordSeparator , nullString , headerComments , header ,
1679+ skipHeaderRecord , allowMissingColumnNames , ignoreHeaderCase , trim , trailingDelimiter , autoFlush );
15941680 }
15951681
15961682 /**
@@ -1703,6 +1789,7 @@ public CSVFormat withFirstRecordAsHeader() {
17031789 * <p>
17041790 * Example:
17051791 * </p>
1792+ *
17061793 * <pre>
17071794 * public enum Header {
17081795 * Name, Email, Phone
0 commit comments