package org.supercsv.io; import java.io.BufferedWriter; import java.io.IOException; import java.io.Writer; import java.util.List; import org.supercsv.exception.NullInputException; import org.supercsv.exception.SuperCSVException; import org.supercsv.prefs.CsvPreference; import org.supercsv.util.CSVContext; /** * The writer class capable of writing arrays, maps,... to a CSV file. Notice that the cell processors can also be * utilized when writing. E.g. they can help ensure that only numbers are written in numeric columns, that numbers are * unique or the output does not contain certain characters or exceed specified string lengths. * * @author Kasper B. Graversen */ public abstract class AbstractCsvWriter implements ICsvWriter { final StringBuilder sb = new StringBuilder(); BufferedWriter outStream; int lineNo; CsvPreference preference; protected AbstractCsvWriter(final Writer stream, final CsvPreference preference) { setPreferences(preference); outStream = new BufferedWriter(stream); lineNo = 1; } /** * {@inheritDoc} */ public void close() throws IOException { outStream.flush(); outStream.close(); } /** * Make a string ready for writing by escaping various characters as specified by the CSV format * * @param csvElem * an elem of a csv file * @return an escaped version of the csv elem ready for persisting */ protected String escapeString(final String csvElem) { if( csvElem.length() == 0 ) { return ""; } sb.delete(0, sb.length()); // reusing builder object final int delimiter = preference.getDelimiterChar(); final char quote = (char) preference.getQuoteChar(); final char whiteSpace = ' '; final String EOLSymbols = preference.getEndOfLineSymbols(); boolean needForEscape = false; // if newline or start with space if( csvElem.charAt(0) == whiteSpace ) { needForEscape = true; } char c; final int lastPos = csvElem.length() - 1; for( int i = 0; i <= lastPos; i++ ) { c = csvElem.charAt(i); if( c == delimiter ) { needForEscape = true; sb.append(c); } else if( c == quote ) { // if its the first character, escape it and set need for space if( i == 0 ) { sb.append(quote); sb.append(quote); needForEscape = true; } else { sb.append(quote); sb.append(quote); needForEscape = true; // TODO review comments above } } else if( c == '\n' ) { needForEscape = true; sb.append(EOLSymbols); } else { sb.append(c); } } // if element contains a newline (mac,windows or linux), escape the // whole with a surrounding quotes if( needForEscape ) { return quote + sb.toString() + quote; } return sb.toString(); } /** * {@inheritDoc} */ public int getLineNumber() { return lineNo; } /** * {@inheritDoc} */ public ICsvWriter setPreferences(final CsvPreference preference) { this.preference = preference; return this; } /** * The actual write to stream */ protected void write(final List<? extends Object> content) throws IOException { write(content.toArray()); } protected void write(final Object... content) throws IOException { // convert object array to strings and write them final String[] strarr = new String[content.length]; int i = 0; for( final Object o : content ) { if( o == null ) { throw new NullInputException("Object at position " + i + " is null", new CSVContext(getLineNumber(), i), (Throwable) null); } strarr[i++] = o.toString(); } write(strarr); } protected void write(final String... content) throws IOException { lineNo++; final int delimiter = preference.getDelimiterChar(); int i = 0; switch( content.length ) { case 0: throw new SuperCSVException("There is no content to write for line " + getLineNumber(), new CSVContext( getLineNumber(), 0)); case 1: // just write last element after switch break; default: // write first 0..N-1 elems for( ; i < content.length - 1; i++ ) { outStream.write(escapeString(content[i])); outStream.write(delimiter); } break; } // write last elem (without delimiter) and the EOL outStream.write(escapeString(content[i])); outStream.write(preference.getEndOfLineSymbols()); return; } /** * {@inheritDoc} */ public void writeHeader(final String... header) throws IOException, SuperCSVException { this.write(header); } }