package com.talend.csv; /** * Copyright 2005 Bytecode Pty Ltd. * * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with * the License. You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the * specific language governing permissions and limitations under the License. */ import java.io.Closeable; import java.io.IOException; import java.io.PrintWriter; import java.io.Writer; import java.util.List; /** * A very simple CSV writer released under a commercial-friendly license. * * @author Glen Smith * */ public class CSVWriter implements Closeable { public static final int INITIAL_STRING_SIZE = 128; private Writer rawWriter; private PrintWriter pw; private char separator = ','; private char quotechar = '"'; private char escapechar = '"'; private String lineEnd; public enum QuoteStatus { FORCE, AUTO, NO } private QuoteStatus quotestatus = QuoteStatus.AUTO; /** * Constructs CSVWriter using a comma for the separator. * * @param writer the writer to an underlying CSV source. */ public CSVWriter(Writer writer) { this.rawWriter = writer; this.pw = new PrintWriter(writer); } /** * Writes the entire list to a CSV file. The list is assumed to be a String[] * * @param allLines a List of String[], with each String[] representing a line of the file. */ public void writeAll(List allLines) { for (String[] line : allLines) { writeNext(line); } } public CSVWriter setLineEnd(String lineEnd) { this.lineEnd = lineEnd; return this; } public CSVWriter setSeparator(char separator) { this.separator = separator; return this; } public CSVWriter setEscapeChar(char escapechar) { this.escapechar = escapechar; if(this.escapechar == '\0') { throw new RuntimeException("unvalid escape char"); } return this; } public CSVWriter setQuoteChar(char quotechar) { this.quotechar = quotechar; if(this.quotechar == '\0') { throw new RuntimeException("unvalid quote char"); } return this; } public CSVWriter setQuoteStatus(QuoteStatus quotestatus) { this.quotestatus = quotestatus; return this; } /** * Writes the next line to the file. * * @param nextLine a string array with each comma-separated element as a separate entry. */ public void writeNext(String[] nextLine) { if (nextLine == null) { return; } StringBuilder sb = new StringBuilder(INITIAL_STRING_SIZE); for (int i = 0; i < nextLine.length; i++) { if (i != 0) { sb.append(separator); } String nextElement = nextLine[i]; if (nextElement == null) { nextElement = ""; } boolean quote = false; if(this.quotestatus == QuoteStatus.AUTO) { quote = needQuote(nextElement,i); } else if(this.quotestatus == QuoteStatus.FORCE) { quote = true; } if(quote) { sb.append(quotechar); } StringBuilder escapeResult = escape(nextElement,quote); if(escapeResult!=null) { sb.append(escapeResult); } else { sb.append(nextElement); } if(quote) { sb.append(quotechar); } } if(lineEnd!=null) { sb.append(lineEnd); pw.write(sb.toString()); } else { pw.println(sb.toString()); } } private boolean needQuote(String field, int fieldIndex) { boolean need = field.indexOf(quotechar) > -1 || field.indexOf(separator) > -1 || (lineEnd == null && (field.indexOf('\n') > -1 || field.indexOf('\r') > -1)) || (lineEnd != null && field.indexOf(lineEnd) > -1) || (fieldIndex == 0 && field.length() == 0); if(!need && field.length() > 0) { char first = field.charAt(0); if (first == ' ' || first == '\t') { need = true; } if (!need && field.length() > 1) { char last = field.charAt(field.length() - 1); if (last == ' ' || last == '\t') { need = true; } } } return need; } private StringBuilder escape(String field, boolean quote) { if (quote) { return processLine(field); } else if (escapechar!=quotechar) { return processLine2(field); } return null; } /** * escape when text quote * @param nextElement * @return */ protected StringBuilder processLine(String nextElement) { StringBuilder sb = new StringBuilder(INITIAL_STRING_SIZE); for (int j = 0; j < nextElement.length(); j++) { char nextChar = nextElement.charAt(j); if (nextChar == quotechar) { sb.append(escapechar).append(nextChar); } else if (nextChar == escapechar) { sb.append(escapechar).append(nextChar); } else { sb.append(nextChar); } } return sb; } /** * escape when no text quote * @param nextElement * @return */ protected StringBuilder processLine2(String nextElement) { StringBuilder sb = new StringBuilder(INITIAL_STRING_SIZE); for (int j = 0; j < nextElement.length(); j++) { char nextChar = nextElement.charAt(j); if (nextChar == escapechar) { sb.append(escapechar).append(nextChar); } else if (nextChar == separator) { sb.append(escapechar).append(nextChar); } else if(lineEnd==null && (nextChar=='\r' || nextChar=='\n')){ sb.append(escapechar).append(nextChar); } else if(lineEnd!=null && lineEnd.indexOf(nextChar) > -1) { sb.append(escapechar).append(nextChar); //TODO how to escape char sequence that contain more than one char without text quote? } else { sb.append(nextChar); } } return sb; } /** * Flush underlying stream to writer. * * @throws IOException if bad things happen */ public void flush() throws IOException { pw.flush(); } /** * Close the underlying stream writer flushing any buffered content. * * @throws IOException if bad things happen * */ @Override public void close() throws IOException { flush(); pw.close(); rawWriter.close(); } /** * Checks to see if the there has been an error in the printstream. */ public boolean checkError() { return pw.checkError(); } }