4040import java .util .Objects ;
4141import java .util .Set ;
4242
43+ import org .apache .commons .codec .binary .Base64OutputStream ;
44+ import org .apache .commons .io .IOUtils ;
45+ import org .apache .commons .io .function .Uncheck ;
46+ import org .apache .commons .io .output .AppendableOutputStream ;
4347
4448/**
4549 * Specifies the format of a CSV file for parsing and writing.
@@ -1638,7 +1642,19 @@ private void escape(final char c, final Appendable appendable) throws IOExceptio
16381642 * @param values the values to format
16391643 * @return the formatted values
16401644 */
1645+ public String format (final Object ... values ) {
1646+ return Uncheck .get (() -> format_ (values ));
1647+ }
16411648
1649+ private String format_ (final Object ... values ) throws IOException {
1650+ final StringWriter out = new StringWriter ();
1651+ try (CSVPrinter csvPrinter = new CSVPrinter (out , this )) {
1652+ csvPrinter .printRecord (values );
1653+ final String res = out .toString ();
1654+ final int len = recordSeparator != null ? res .length () - recordSeparator .length () : res .length ();
1655+ return res .substring (0 , len );
1656+ }
1657+ }
16421658
16431659 /**
16441660 * Gets whether duplicate names are allowed in the headers.
@@ -2034,6 +2050,9 @@ public CSVParser parse(final Reader reader) throws IOException {
20342050 * @return a printer to an output.
20352051 * @throws IOException thrown if the optional header cannot be printed.
20362052 */
2053+ public CSVPrinter print (final Appendable out ) throws IOException {
2054+ return new CSVPrinter (out , this );
2055+ }
20372056
20382057 /**
20392058 * Prints to the specified {@code File} with given {@code Charset}.
@@ -2048,7 +2067,31 @@ public CSVParser parse(final Reader reader) throws IOException {
20482067 * @throws IOException thrown if the optional header cannot be printed.
20492068 * @since 1.5
20502069 */
2070+ @ SuppressWarnings ("resource" )
2071+ public CSVPrinter print (final File out , final Charset charset ) throws IOException {
2072+ // The writer will be closed when close() is called.
2073+ return new CSVPrinter (new OutputStreamWriter (new FileOutputStream (out ), charset ), this );
2074+ }
20512075
2076+ private void print (final InputStream inputStream , final Appendable out , final boolean newRecord ) throws IOException {
2077+ // InputStream is never null here
2078+ // There is nothing to escape when quoting is used which is the default.
2079+ if (!newRecord ) {
2080+ append (getDelimiterString (), out );
2081+ }
2082+ final boolean quoteCharacterSet = isQuoteCharacterSet ();
2083+ if (quoteCharacterSet ) {
2084+ append (getQuoteCharacter ().charValue (), out );
2085+ }
2086+ // Stream the input to the output without reading or holding the whole value in memory.
2087+ // AppendableOutputStream cannot "close" an Appendable.
2088+ try (OutputStream outputStream = new Base64OutputStream (new AppendableOutputStream <>(out ))) {
2089+ IOUtils .copy (inputStream , outputStream );
2090+ }
2091+ if (quoteCharacterSet ) {
2092+ append (getQuoteCharacter ().charValue (), out );
2093+ }
2094+ }
20522095
20532096 /**
20542097 * Prints the {@code value} as the next value on the line to {@code out}. The value will be escaped or encapsulated as needed. Useful when one wants to
@@ -2060,7 +2103,51 @@ public CSVParser parse(final Reader reader) throws IOException {
20602103 * @throws IOException If an I/O error occurs.
20612104 * @since 1.4
20622105 */
2106+ public synchronized void print (final Object value , final Appendable out , final boolean newRecord ) throws IOException {
2107+ // null values are considered empty
2108+ // Only call CharSequence.toString() if you have to, helps GC-free use cases.
2109+ CharSequence charSequence ;
2110+ if (value == null ) {
2111+ // https://issues.apache.org/jira/browse/CSV-203
2112+ if (null == nullString ) {
2113+ charSequence = Constants .EMPTY ;
2114+ } else if (QuoteMode .ALL == quoteMode ) {
2115+ charSequence = quotedNullString ;
2116+ } else {
2117+ charSequence = nullString ;
2118+ }
2119+ } else if (value instanceof CharSequence ) {
2120+ charSequence = (CharSequence ) value ;
2121+ } else if (value instanceof Reader ) {
2122+ print ((Reader ) value , out , newRecord );
2123+ return ;
2124+ } else if (value instanceof InputStream ) {
2125+ print ((InputStream ) value , out , newRecord );
2126+ return ;
2127+ } else {
2128+ charSequence = value .toString ();
2129+ }
2130+ charSequence = getTrim () ? trim (charSequence ) : charSequence ;
2131+ print (value , charSequence , out , newRecord );
2132+ }
20632133
2134+ private synchronized void print (final Object object , final CharSequence value , final Appendable out , final boolean newRecord ) throws IOException {
2135+ final int offset = 0 ;
2136+ final int len = value .length ();
2137+ if (!newRecord ) {
2138+ out .append (getDelimiterString ());
2139+ }
2140+ if (object == null ) {
2141+ out .append (value );
2142+ } else if (isQuoteCharacterSet ()) {
2143+ // The original object is needed so can check for Number
2144+ printWithQuotes (object , value , out , newRecord );
2145+ } else if (isEscapeCharacterSet ()) {
2146+ printWithEscapes (value , out );
2147+ } else {
2148+ out .append (value , offset , len );
2149+ }
2150+ }
20642151
20652152 /**
20662153 * Prints to the specified {@code Path} with given {@code Charset},
@@ -2076,7 +2163,26 @@ public CSVParser parse(final Reader reader) throws IOException {
20762163 * @throws IOException thrown if the optional header cannot be printed.
20772164 * @since 1.5
20782165 */
2166+ @ SuppressWarnings ("resource" )
2167+ public CSVPrinter print (final Path out , final Charset charset ) throws IOException {
2168+ return print (Files .newBufferedWriter (out , charset ));
2169+ }
20792170
2171+ private void print (final Reader reader , final Appendable out , final boolean newRecord ) throws IOException {
2172+ // Reader is never null here
2173+ if (!newRecord ) {
2174+ append (getDelimiterString (), out );
2175+ }
2176+ if (isQuoteCharacterSet ()) {
2177+ printWithQuotes (reader , out );
2178+ } else if (isEscapeCharacterSet ()) {
2179+ printWithEscapes (reader , out );
2180+ } else if (out instanceof Writer ) {
2181+ IOUtils .copyLarge (reader , (Writer ) out );
2182+ } else {
2183+ IOUtils .copy (reader , out );
2184+ }
2185+ }
20802186
20812187 /**
20822188 * Prints to the {@link System#out}.
@@ -2089,6 +2195,9 @@ public CSVParser parse(final Reader reader) throws IOException {
20892195 * @throws IOException thrown if the optional header cannot be printed.
20902196 * @since 1.5
20912197 */
2198+ public CSVPrinter printer () throws IOException {
2199+ return new CSVPrinter (System .out , this );
2200+ }
20922201
20932202 /**
20942203 * Outputs the trailing delimiter (if set) followed by the record separator (if set).
@@ -2119,6 +2228,12 @@ public synchronized void println(final Appendable appendable) throws IOException
21192228 * @throws IOException If an I/O error occurs.
21202229 * @since 1.4
21212230 */
2231+ public synchronized void printRecord (final Appendable appendable , final Object ... values ) throws IOException {
2232+ for (int i = 0 ; i < values .length ; i ++) {
2233+ print (values [i ], appendable , i == 0 );
2234+ }
2235+ println (appendable );
2236+ }
21222237
21232238 /*
21242239 * Note: Must only be called if escaping is enabled, otherwise can throw exceptions.
0 commit comments