Java/Development Class/CSV File

Материал из Java эксперт
Версия от 18:01, 31 мая 2010; (обсуждение)
(разн.) ← Предыдущая | Текущая версия (разн.) | Следующая → (разн.)
Перейти к: навигация, поиск

A stream based parser for parsing delimited text data from a file or a stream

  
/*
 * Java CSV is a stream based library for reading and writing
 * CSV and other delimited data.
 *   
 * Copyright (C) Bruce Dunwiddie bruce@csvreader.ru
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.HashMap;
/**
 * A stream based parser for parsing delimited text data from a file or a
 * stream.
 */
public class CsvReader {
  private Reader inputStream = null;
  private String fileName = null;
  // this holds all the values for switches that the user is allowed to set
  private UserSettings userSettings = new UserSettings();
  private Charset charset = null;
  private boolean useCustomRecordDelimiter = false;
  // this will be our working buffer to hold data chunks
  // read in from the data file
  private DataBuffer dataBuffer = new DataBuffer();
  private ColumnBuffer columnBuffer = new ColumnBuffer();
  private RawRecordBuffer rawBuffer = new RawRecordBuffer();
  private boolean[] isQualified = null;
  private String rawRecord = "";
  private HeadersHolder headersHolder = new HeadersHolder();
  // these are all more or less global loop variables
  // to keep from needing to pass them all into various
  // methods during parsing
  private boolean startedColumn = false;
  private boolean startedWithQualifier = false;
  private boolean hasMoreData = true;
  private char lastLetter = "\0";
  private boolean hasReadNextLine = false;
  private int columnsCount = 0;
  private long currentRecord = 0;
  private String[] values = new String[StaticSettings.INITIAL_COLUMN_COUNT];
  private boolean initialized = false;
  private boolean closed = false;
  /**
   * Double up the text qualifier to represent an occurance of the text
   * qualifier.
   */
  public static final int ESCAPE_MODE_DOUBLED = 1;
  /**
   * Use a backslash character before the text qualifier to represent an
   * occurance of the text qualifier.
   */
  public static final int ESCAPE_MODE_BACKSLASH = 2;
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file
   * as the data source.
   * 
   * @param fileName
   *            The path to the file to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            parsing the data.
   */
  public CsvReader(String fileName, char delimiter, Charset charset)
      throws FileNotFoundException {
    if (fileName == null) {
      throw new IllegalArgumentException(
          "Parameter fileName can not be null.");
    }
    if (charset == null) {
      throw new IllegalArgumentException(
          "Parameter charset can not be null.");
    }
    if (!new File(fileName).exists()) {
      throw new FileNotFoundException("File " + fileName
          + " does not exist.");
    }
    this.fileName = fileName;
    this.userSettings.Delimiter = delimiter;
    this.charset = charset;
    isQualified = new boolean[values.length];
  }
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file
   * as the data source. Uses ISO-8859-1 as the
   * {@link java.nio.charset.Charset Charset}.
   * 
   * @param fileName
   *            The path to the file to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   */
  public CsvReader(String fileName, char delimiter)
      throws FileNotFoundException {
    this(fileName, delimiter, Charset.forName("ISO-8859-1"));
  }
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file
   * as the data source. Uses a comma as the column delimiter and
   * ISO-8859-1 as the {@link java.nio.charset.Charset Charset}.
   * 
   * @param fileName
   *            The path to the file to use as the data source.
   */
  public CsvReader(String fileName) throws FileNotFoundException {
    this(fileName, Letters.ruMA);
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a
   * {@link java.io.Reader Reader} object as the data source.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   */
  public CsvReader(Reader inputStream, char delimiter) {
    if (inputStream == null) {
      throw new IllegalArgumentException(
          "Parameter inputStream can not be null.");
    }
    this.inputStream = inputStream;
    this.userSettings.Delimiter = delimiter;
    initialized = true;
    isQualified = new boolean[values.length];
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a
   * {@link java.io.Reader Reader} object as the data source. Uses a
   * comma as the column delimiter.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   */
  public CsvReader(Reader inputStream) {
    this(inputStream, Letters.ruMA);
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an
   * {@link java.io.InputStream InputStream} object as the data source.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            parsing the data.
   */
  public CsvReader(InputStream inputStream, char delimiter, Charset charset) {
    this(new InputStreamReader(inputStream, charset), delimiter);
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an
   * {@link java.io.InputStream InputStream} object as the data
   * source. Uses a comma as the column delimiter.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            parsing the data.
   */
  public CsvReader(InputStream inputStream, Charset charset) {
    this(new InputStreamReader(inputStream, charset));
  }
  public boolean getCaptureRawRecord() {
    return userSettings.CaptureRawRecord;
  }
  public void setCaptureRawRecord(boolean captureRawRecord) {
    userSettings.CaptureRawRecord = captureRawRecord;
  }
  public String getRawRecord() {
    return rawRecord;
  }
  /**
   * Gets whether leading and trailing whitespace characters are being trimmed
   * from non-textqualified column data. Default is true.
   * 
   * @return Whether leading and trailing whitespace characters are being
   *         trimmed from non-textqualified column data.
   */
  public boolean getTrimWhitespace() {
    return userSettings.TrimWhitespace;
  }
  /**
   * Sets whether leading and trailing whitespace characters should be trimmed
   * from non-textqualified column data or not. Default is true.
   * 
   * @param trimWhitespace
   *            Whether leading and trailing whitespace characters should be
   *            trimmed from non-textqualified column data or not.
   */
  public void setTrimWhitespace(boolean trimWhitespace) {
    userSettings.TrimWhitespace = trimWhitespace;
  }
  /**
   * Gets the character being used as the column delimiter. Default is comma,
   * ",".
   * 
   * @return The character being used as the column delimiter.
   */
  public char getDelimiter() {
    return userSettings.Delimiter;
  }
  /**
   * Sets the character to use as the column delimiter. Default is comma, ",".
   * 
   * @param delimiter
   *            The character to use as the column delimiter.
   */
  public void setDelimiter(char delimiter) {
    userSettings.Delimiter = delimiter;
  }
  public char getRecordDelimiter() {
    return userSettings.RecordDelimiter;
  }
  /**
   * Sets the character to use as the record delimiter.
   * 
   * @param recordDelimiter
   *            The character to use as the record delimiter. Default is
   *            combination of standard end of line characters for Windows,
   *            Unix, or Mac.
   */
  public void setRecordDelimiter(char recordDelimiter) {
    useCustomRecordDelimiter = true;
    userSettings.RecordDelimiter = recordDelimiter;
  }
  /**
   * Gets the character to use as a text qualifier in the data.
   * 
   * @return The character to use as a text qualifier in the data.
   */
  public char getTextQualifier() {
    return userSettings.TextQualifier;
  }
  /**
   * Sets the character to use as a text qualifier in the data.
   * 
   * @param textQualifier
   *            The character to use as a text qualifier in the data.
   */
  public void setTextQualifier(char textQualifier) {
    userSettings.TextQualifier = textQualifier;
  }
  /**
   * Whether text qualifiers will be used while parsing or not.
   * 
   * @return Whether text qualifiers will be used while parsing or not.
   */
  public boolean getUseTextQualifier() {
    return userSettings.UseTextQualifier;
  }
  /**
   * Sets whether text qualifiers will be used while parsing or not.
   * 
   * @param useTextQualifier
   *            Whether to use a text qualifier while parsing or not.
   */
  public void setUseTextQualifier(boolean useTextQualifier) {
    userSettings.UseTextQualifier = useTextQualifier;
  }
  /**
   * Gets the character being used as a comment signal.
   * 
   * @return The character being used as a comment signal.
   */
  public char getComment() {
    return userSettings.rument;
  }
  /**
   * Sets the character to use as a comment signal.
   * 
   * @param comment
   *            The character to use as a comment signal.
   */
  public void setComment(char comment) {
    userSettings.rument = comment;
  }
  /**
   * Gets whether comments are being looked for while parsing or not.
   * 
   * @return Whether comments are being looked for while parsing or not.
   */
  public boolean getUseComments() {
    return userSettings.UseComments;
  }
  /**
   * Sets whether comments are being looked for while parsing or not.
   * 
   * @param useComments
   *            Whether comments are being looked for while parsing or not.
   */
  public void setUseComments(boolean useComments) {
    userSettings.UseComments = useComments;
  }
  /**
   * Gets the current way to escape an occurance of the text qualifier inside
   * qualified data.
   * 
   * @return The current way to escape an occurance of the text qualifier
   *         inside qualified data.
   */
  public int getEscapeMode() {
    return userSettings.EscapeMode;
  }
  /**
   * Sets the current way to escape an occurance of the text qualifier inside
   * qualified data.
   * 
   * @param escapeMode
   *            The way to escape an occurance of the text qualifier inside
   *            qualified data.
   * @exception IllegalArgumentException
   *                When an illegal value is specified for escapeMode.
   */
  public void setEscapeMode(int escapeMode) throws IllegalArgumentException {
    if (escapeMode != ESCAPE_MODE_DOUBLED
        && escapeMode != ESCAPE_MODE_BACKSLASH) {
      throw new IllegalArgumentException(
          "Parameter escapeMode must be a valid value.");
    }
    userSettings.EscapeMode = escapeMode;
  }
  public boolean getSkipEmptyRecords() {
    return userSettings.SkipEmptyRecords;
  }
  public void setSkipEmptyRecords(boolean skipEmptyRecords) {
    userSettings.SkipEmptyRecords = skipEmptyRecords;
  }
  /**
   * Safety caution to prevent the parser from using large amounts of memory
   * in the case where parsing settings like file encodings don"t end up
   * matching the actual format of a file. This switch can be turned off if
   * the file format is known and tested. With the switch off, the max column
   * lengths and max column count per record supported by the parser will
   * greatly increase. Default is true.
   * 
   * @return The current setting of the safety switch.
   */
  public boolean getSafetySwitch() {
    return userSettings.SafetySwitch;
  }
  /**
   * Safety caution to prevent the parser from using large amounts of memory
   * in the case where parsing settings like file encodings don"t end up
   * matching the actual format of a file. This switch can be turned off if
   * the file format is known and tested. With the switch off, the max column
   * lengths and max column count per record supported by the parser will
   * greatly increase. Default is true.
   * 
   * @param safetySwitch
   */
  public void setSafetySwitch(boolean safetySwitch) {
    userSettings.SafetySwitch = safetySwitch;
  }
  /**
   * Gets the count of columns found in this record.
   * 
   * @return The count of columns found in this record.
   */
  public int getColumnCount() {
    return columnsCount;
  }
  /**
   * Gets the index of the current record.
   * 
   * @return The index of the current record.
   */
  public long getCurrentRecord() {
    return currentRecord - 1;
  }
  /**
   * Gets the count of headers read in by a previous call to
   * {@link com.csvreader.CsvReader#readHeaders readHeaders()}.
   * 
   * @return The count of headers read in by a previous call to
   *         {@link com.csvreader.CsvReader#readHeaders readHeaders()}.
   */
  public int getHeaderCount() {
    return headersHolder.Length;
  }
  /**
   * Returns the header values as a string array.
   * 
   * @return The header values as a String array.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String[] getHeaders() throws IOException {
    checkClosed();
    if (headersHolder.Headers == null) {
      return null;
    } else {
      // use clone here to prevent the outside code from
      // setting values on the array directly, which would
      // throw off the index lookup based on header name
      String[] clone = new String[headersHolder.Length];
      System.arraycopy(headersHolder.Headers, 0, clone, 0,
          headersHolder.Length);
      return clone;
    }
  }
  public void setHeaders(String[] headers) {
    headersHolder.Headers = headers;
    headersHolder.IndexByName.clear();
    if (headers != null) {
      headersHolder.Length = headers.length;
    } else {
      headersHolder.Length = 0;
    }
    // use headersHolder.Length here in case headers is null
    for (int i = 0; i < headersHolder.Length; i++) {
      headersHolder.IndexByName.put(headers[i], new Integer(i));
    }
  }
  public String[] getValues() throws IOException {
    checkClosed();
    // need to return a clone, and can"t use clone because values.Length
    // might be greater than columnsCount
    String[] clone = new String[columnsCount];
    System.arraycopy(values, 0, clone, 0, columnsCount);
    return clone;
  }
  /**
   * Returns the current column value for a given column index.
   * 
   * @param columnIndex
   *            The index of the column.
   * @return The current column value.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String get(int columnIndex) throws IOException {
    checkClosed();
    if (columnIndex > -1 && columnIndex < columnsCount) {
      return values[columnIndex];
    } else {
      return "";
    }
  }
  /**
   * Returns the current column value for a given column header name.
   * 
   * @param headerName
   *            The header name of the column.
   * @return The current column value.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String get(String headerName) throws IOException {
    checkClosed();
    return get(getIndex(headerName));
  }
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a string
   * of data as the source.&nbsp;Uses ISO-8859-1 as the
   * {@link java.nio.charset.Charset Charset}.
   * 
   * @param data
   *            The String of data to use as the source.
   * @return A {@link com.csvreader.CsvReader CsvReader} object using the
   *         String of data as the source.
   */
  public static CsvReader parse(String data) {
    if (data == null) {
      throw new IllegalArgumentException(
          "Parameter data can not be null.");
    }
    return new CsvReader(new StringReader(data));
  }
  /**
   * Reads another record.
   * 
   * @return Whether another record was successfully read or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean readRecord() throws IOException {
    checkClosed();
    columnsCount = 0;
    rawBuffer.Position = 0;
    dataBuffer.LineStart = dataBuffer.Position;
    hasReadNextLine = false;
    // check to see if we"ve already found the end of data
    if (hasMoreData) {
      // loop over the data stream until the end of data is found
      // or the end of the record is found
      do {
        if (dataBuffer.Position == dataBuffer.Count) {
          checkDataLength();
        } else {
          startedWithQualifier = false;
          // grab the current letter as a char
          char currentLetter = dataBuffer.Buffer[dataBuffer.Position];
          if (userSettings.UseTextQualifier
              && currentLetter == userSettings.TextQualifier) {
            // this will be a text qualified column, so
            // we need to set startedWithQualifier to make it
            // enter the seperate branch to handle text
            // qualified columns
            lastLetter = currentLetter;
            // read qualified
            startedColumn = true;
            dataBuffer.ColumnStart = dataBuffer.Position + 1;
            startedWithQualifier = true;
            boolean lastLetterWasQualifier = false;
            char escapeChar = userSettings.TextQualifier;
            if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
              escapeChar = Letters.BACKSLASH;
            }
            boolean eatingTrailingJunk = false;
            boolean lastLetterWasEscape = false;
            boolean readingComplexEscape = false;
            int escape = ComplexEscape.UNICODE;
            int escapeLength = 0;
            char escapeValue = (char) 0;
            dataBuffer.Position++;
            do {
              if (dataBuffer.Position == dataBuffer.Count) {
                checkDataLength();
              } else {
                // grab the current letter as a char
                currentLetter = dataBuffer.Buffer[dataBuffer.Position];
                if (eatingTrailingJunk) {
                  dataBuffer.ColumnStart = dataBuffer.Position + 1;
                  if (currentLetter == userSettings.Delimiter) {
                    endColumn();
                  } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))
                      || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {
                    endColumn();
                    endRecord();
                  }
                } else if (readingComplexEscape) {
                  escapeLength++;
                  switch (escape) {
                  case ComplexEscape.UNICODE:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 4) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.OCTAL:
                    escapeValue *= (char) 8;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.DECIMAL:
                    escapeValue *= (char) 10;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.HEX:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 2) {
                      readingComplexEscape = false;
                    }
                    break;
                  }
                  if (!readingComplexEscape) {
                    appendLetter(escapeValue);
                  } else {
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                  }
                } else if (currentLetter == userSettings.TextQualifier) {
                  if (lastLetterWasEscape) {
                    lastLetterWasEscape = false;
                    lastLetterWasQualifier = false;
                  } else {
                    updateCurrentValue();
                    if (userSettings.EscapeMode == ESCAPE_MODE_DOUBLED) {
                      lastLetterWasEscape = true;
                    }
                    lastLetterWasQualifier = true;
                  }
                } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH
                    && lastLetterWasEscape) {
                  switch (currentLetter) {
                  case "n":
                    appendLetter(Letters.LF);
                    break;
                  case "r":
                    appendLetter(Letters.CR);
                    break;
                  case "t":
                    appendLetter(Letters.TAB);
                    break;
                  case "b":
                    appendLetter(Letters.BACKSPACE);
                    break;
                  case "f":
                    appendLetter(Letters.FORM_FEED);
                    break;
                  case "e":
                    appendLetter(Letters.ESCAPE);
                    break;
                  case "v":
                    appendLetter(Letters.VERTICAL_TAB);
                    break;
                  case "a":
                    appendLetter(Letters.ALERT);
                    break;
                  case "0":
                  case "1":
                  case "2":
                  case "3":
                  case "4":
                  case "5":
                  case "6":
                  case "7":
                    escape = ComplexEscape.OCTAL;
                    readingComplexEscape = true;
                    escapeLength = 1;
                    escapeValue = (char) (currentLetter - "0");
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  case "u":
                  case "x":
                  case "o":
                  case "d":
                  case "U":
                  case "X":
                  case "O":
                  case "D":
                    switch (currentLetter) {
                    case "u":
                    case "U":
                      escape = ComplexEscape.UNICODE;
                      break;
                    case "x":
                    case "X":
                      escape = ComplexEscape.HEX;
                      break;
                    case "o":
                    case "O":
                      escape = ComplexEscape.OCTAL;
                      break;
                    case "d":
                    case "D":
                      escape = ComplexEscape.DECIMAL;
                      break;
                    }
                    readingComplexEscape = true;
                    escapeLength = 0;
                    escapeValue = (char) 0;
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  default:
                    break;
                  }
                  lastLetterWasEscape = false;
                  // can only happen for ESCAPE_MODE_BACKSLASH
                } else if (currentLetter == escapeChar) {
                  updateCurrentValue();
                  lastLetterWasEscape = true;
                } else {
                  if (lastLetterWasQualifier) {
                    if (currentLetter == userSettings.Delimiter) {
                      endColumn();
                    } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))
                        || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {
                      endColumn();
                      endRecord();
                    } else {
                      dataBuffer.ColumnStart = dataBuffer.Position + 1;
                      eatingTrailingJunk = true;
                    }
                    // make sure to clear the flag for next
                    // run of the loop
                    lastLetterWasQualifier = false;
                  }
                }
                // keep track of the last letter because we need
                // it for several key decisions
                lastLetter = currentLetter;
                if (startedColumn) {
                  dataBuffer.Position++;
                  if (userSettings.SafetySwitch
                      && dataBuffer.Position
                          - dataBuffer.ColumnStart
                          + columnBuffer.Position > 100000) {
                    close();
                    throw new IOException(
                        "Maximum column length of 100,000 exceeded in column "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    columnsCount)
                            + " in record "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    currentRecord)
                            + ". Set the SafetySwitch property to false"
                            + " if you"re expecting column lengths greater than 100,000 characters to"
                            + " avoid this error.");
                  }
                }
              } // end else
            } while (hasMoreData && startedColumn);
          } else if (currentLetter == userSettings.Delimiter) {
            // we encountered a column with no data, so
            // just send the end column
            lastLetter = currentLetter;
            endColumn();
          } else if (useCustomRecordDelimiter
              && currentLetter == userSettings.RecordDelimiter) {
            // this will skip blank lines
            if (startedColumn || columnsCount > 0
                || !userSettings.SkipEmptyRecords) {
              endColumn();
              endRecord();
            } else {
              dataBuffer.LineStart = dataBuffer.Position + 1;
            }
            lastLetter = currentLetter;
          } else if (!useCustomRecordDelimiter
              && (currentLetter == Letters.CR || currentLetter == Letters.LF)) {
            // this will skip blank lines
            if (startedColumn
                || columnsCount > 0
                || (!userSettings.SkipEmptyRecords && (currentLetter == Letters.CR || lastLetter != Letters.CR))) {
              endColumn();
              endRecord();
            } else {
              dataBuffer.LineStart = dataBuffer.Position + 1;
            }
            lastLetter = currentLetter;
          } else if (userSettings.UseComments && columnsCount == 0
              && currentLetter == userSettings.rument) {
            // encountered a comment character at the beginning of
            // the line so just ignore the rest of the line
            lastLetter = currentLetter;
            skipLine();
          } else if (userSettings.TrimWhitespace
              && (currentLetter == Letters.SPACE || currentLetter == Letters.TAB)) {
            // do nothing, this will trim leading whitespace
            // for both text qualified columns and non
            startedColumn = true;
            dataBuffer.ColumnStart = dataBuffer.Position + 1;
          } else {
            // since the letter wasn"t a special letter, this
            // will be the first letter of our current column
            startedColumn = true;
            dataBuffer.ColumnStart = dataBuffer.Position;
            boolean lastLetterWasBackslash = false;
            boolean readingComplexEscape = false;
            int escape = ComplexEscape.UNICODE;
            int escapeLength = 0;
            char escapeValue = (char) 0;
            boolean firstLoop = true;
            do {
              if (!firstLoop
                  && dataBuffer.Position == dataBuffer.Count) {
                checkDataLength();
              } else {
                if (!firstLoop) {
                  // grab the current letter as a char
                  currentLetter = dataBuffer.Buffer[dataBuffer.Position];
                }
                if (!userSettings.UseTextQualifier
                    && userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH
                    && currentLetter == Letters.BACKSLASH) {
                  if (lastLetterWasBackslash) {
                    lastLetterWasBackslash = false;
                  } else {
                    updateCurrentValue();
                    lastLetterWasBackslash = true;
                  }
                } else if (readingComplexEscape) {
                  escapeLength++;
                  switch (escape) {
                  case ComplexEscape.UNICODE:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 4) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.OCTAL:
                    escapeValue *= (char) 8;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.DECIMAL:
                    escapeValue *= (char) 10;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.HEX:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 2) {
                      readingComplexEscape = false;
                    }
                    break;
                  }
                  if (!readingComplexEscape) {
                    appendLetter(escapeValue);
                  } else {
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                  }
                } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH
                    && lastLetterWasBackslash) {
                  switch (currentLetter) {
                  case "n":
                    appendLetter(Letters.LF);
                    break;
                  case "r":
                    appendLetter(Letters.CR);
                    break;
                  case "t":
                    appendLetter(Letters.TAB);
                    break;
                  case "b":
                    appendLetter(Letters.BACKSPACE);
                    break;
                  case "f":
                    appendLetter(Letters.FORM_FEED);
                    break;
                  case "e":
                    appendLetter(Letters.ESCAPE);
                    break;
                  case "v":
                    appendLetter(Letters.VERTICAL_TAB);
                    break;
                  case "a":
                    appendLetter(Letters.ALERT);
                    break;
                  case "0":
                  case "1":
                  case "2":
                  case "3":
                  case "4":
                  case "5":
                  case "6":
                  case "7":
                    escape = ComplexEscape.OCTAL;
                    readingComplexEscape = true;
                    escapeLength = 1;
                    escapeValue = (char) (currentLetter - "0");
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  case "u":
                  case "x":
                  case "o":
                  case "d":
                  case "U":
                  case "X":
                  case "O":
                  case "D":
                    switch (currentLetter) {
                    case "u":
                    case "U":
                      escape = ComplexEscape.UNICODE;
                      break;
                    case "x":
                    case "X":
                      escape = ComplexEscape.HEX;
                      break;
                    case "o":
                    case "O":
                      escape = ComplexEscape.OCTAL;
                      break;
                    case "d":
                    case "D":
                      escape = ComplexEscape.DECIMAL;
                      break;
                    }
                    readingComplexEscape = true;
                    escapeLength = 0;
                    escapeValue = (char) 0;
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  default:
                    break;
                  }
                  lastLetterWasBackslash = false;
                } else {
                  if (currentLetter == userSettings.Delimiter) {
                    endColumn();
                  } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))
                      || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {
                    endColumn();
                    endRecord();
                  }
                }
                // keep track of the last letter because we need
                // it for several key decisions
                lastLetter = currentLetter;
                firstLoop = false;
                if (startedColumn) {
                  dataBuffer.Position++;
                  if (userSettings.SafetySwitch
                      && dataBuffer.Position
                          - dataBuffer.ColumnStart
                          + columnBuffer.Position > 100000) {
                    close();
                    throw new IOException(
                        "Maximum column length of 100,000 exceeded in column "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    columnsCount)
                            + " in record "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    currentRecord)
                            + ". Set the SafetySwitch property to false"
                            + " if you"re expecting column lengths greater than 100,000 characters to"
                            + " avoid this error.");
                  }
                }
              } // end else
            } while (hasMoreData && startedColumn);
          }
          if (hasMoreData) {
            dataBuffer.Position++;
          }
        } // end else
      } while (hasMoreData && !hasReadNextLine);
      // check to see if we hit the end of the file
      // without processing the current record
      if (startedColumn || lastLetter == userSettings.Delimiter) {
        endColumn();
        endRecord();
      }
    }
    if (userSettings.CaptureRawRecord) {
      if (hasMoreData) {
        if (rawBuffer.Position == 0) {
          rawRecord = new String(dataBuffer.Buffer,
              dataBuffer.LineStart, dataBuffer.Position
                  - dataBuffer.LineStart - 1);
        } else {
          rawRecord = new String(rawBuffer.Buffer, 0,
              rawBuffer.Position)
              + new String(dataBuffer.Buffer,
                  dataBuffer.LineStart, dataBuffer.Position
                      - dataBuffer.LineStart - 1);
        }
      } else {
        // for hasMoreData to ever be false, all data would have had to
        // have been
        // copied to the raw buffer
        rawRecord = new String(rawBuffer.Buffer, 0, rawBuffer.Position);
      }
    } else {
      rawRecord = "";
    }
    return hasReadNextLine;
  }
  /**
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  private void checkDataLength() throws IOException {
    if (!initialized) {
      if (fileName != null) {
        inputStream = new BufferedReader(new InputStreamReader(
            new FileInputStream(fileName), charset),
            StaticSettings.MAX_FILE_BUFFER_SIZE);
      }
      charset = null;
      initialized = true;
    }
    updateCurrentValue();
    if (userSettings.CaptureRawRecord && dataBuffer.Count > 0) {
      if (rawBuffer.Buffer.length - rawBuffer.Position < dataBuffer.Count
          - dataBuffer.LineStart) {
        int newLength = rawBuffer.Buffer.length
            + Math.max(dataBuffer.Count - dataBuffer.LineStart,
                rawBuffer.Buffer.length);
        char[] holder = new char[newLength];
        System.arraycopy(rawBuffer.Buffer, 0, holder, 0,
            rawBuffer.Position);
        rawBuffer.Buffer = holder;
      }
      System.arraycopy(dataBuffer.Buffer, dataBuffer.LineStart,
          rawBuffer.Buffer, rawBuffer.Position, dataBuffer.Count
              - dataBuffer.LineStart);
      rawBuffer.Position += dataBuffer.Count - dataBuffer.LineStart;
    }
    try {
      dataBuffer.Count = inputStream.read(dataBuffer.Buffer, 0,
          dataBuffer.Buffer.length);
    } catch (IOException ex) {
      close();
      throw ex;
    }
    // if no more data could be found, set flag stating that
    // the end of the data was found
    if (dataBuffer.Count == -1) {
      hasMoreData = false;
    }
    dataBuffer.Position = 0;
    dataBuffer.LineStart = 0;
    dataBuffer.ColumnStart = 0;
  }
  /**
   * Read the first record of data as column headers.
   * 
   * @return Whether the header record was successfully read or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean readHeaders() throws IOException {
    boolean result = readRecord();
    // copy the header data from the column array
    // to the header string array
    headersHolder.Length = columnsCount;
    headersHolder.Headers = new String[columnsCount];
    for (int i = 0; i < headersHolder.Length; i++) {
      String columnValue = get(i);
      headersHolder.Headers[i] = columnValue;
      // if there are duplicate header names, we will save the last one
      headersHolder.IndexByName.put(columnValue, new Integer(i));
    }
    if (result) {
      currentRecord--;
    }
    columnsCount = 0;
    return result;
  }
  /**
   * Returns the column header value for a given column index.
   * 
   * @param columnIndex
   *            The index of the header column being requested.
   * @return The value of the column header at the given column index.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String getHeader(int columnIndex) throws IOException {
    checkClosed();
    // check to see if we have read the header record yet
    // check to see if the column index is within the bounds
    // of our header array
    if (columnIndex > -1 && columnIndex < headersHolder.Length) {
      // return the processed header data for this column
      return headersHolder.Headers[columnIndex];
    } else {
      return "";
    }
  }
  public boolean isQualified(int columnIndex) throws IOException {
    checkClosed();
    if (columnIndex < columnsCount && columnIndex > -1) {
      return isQualified[columnIndex];
    } else {
      return false;
    }
  }
  /**
   * @exception IOException
   *                Thrown if a very rare extreme exception occurs during
   *                parsing, normally resulting from improper data format.
   */
  private void endColumn() throws IOException {
    String currentValue = "";
    // must be called before setting startedColumn = false
    if (startedColumn) {
      if (columnBuffer.Position == 0) {
        if (dataBuffer.ColumnStart < dataBuffer.Position) {
          int lastLetter = dataBuffer.Position - 1;
          if (userSettings.TrimWhitespace && !startedWithQualifier) {
            while (lastLetter >= dataBuffer.ColumnStart
                && (dataBuffer.Buffer[lastLetter] == Letters.SPACE || dataBuffer.Buffer[lastLetter] == Letters.TAB)) {
              lastLetter--;
            }
          }
          currentValue = new String(dataBuffer.Buffer,
              dataBuffer.ColumnStart, lastLetter
                  - dataBuffer.ColumnStart + 1);
        }
      } else {
        updateCurrentValue();
        int lastLetter = columnBuffer.Position - 1;
        if (userSettings.TrimWhitespace && !startedWithQualifier) {
          while (lastLetter >= 0
              && (columnBuffer.Buffer[lastLetter] == Letters.SPACE || columnBuffer.Buffer[lastLetter] == Letters.SPACE)) {
            lastLetter--;
          }
        }
        currentValue = new String(columnBuffer.Buffer, 0,
            lastLetter + 1);
      }
    }
    columnBuffer.Position = 0;
    startedColumn = false;
    if (columnsCount >= 100000 && userSettings.SafetySwitch) {
      close();
      throw new IOException(
          "Maximum column count of 100,000 exceeded in record "
              + NumberFormat.getIntegerInstance().format(
                  currentRecord)
              + ". Set the SafetySwitch property to false"
              + " if you"re expecting more than 100,000 columns per record to"
              + " avoid this error.");
    }
    // check to see if our current holder array for
    // column chunks is still big enough to handle another
    // column chunk
    if (columnsCount == values.length) {
      // holder array needs to grow to be able to hold another column
      int newLength = values.length * 2;
      String[] holder = new String[newLength];
      System.arraycopy(values, 0, holder, 0, values.length);
      values = holder;
      boolean[] qualifiedHolder = new boolean[newLength];
      System.arraycopy(isQualified, 0, qualifiedHolder, 0,
          isQualified.length);
      isQualified = qualifiedHolder;
    }
    values[columnsCount] = currentValue;
    isQualified[columnsCount] = startedWithQualifier;
    currentValue = "";
    columnsCount++;
  }
  private void appendLetter(char letter) {
    if (columnBuffer.Position == columnBuffer.Buffer.length) {
      int newLength = columnBuffer.Buffer.length * 2;
      char[] holder = new char[newLength];
      System.arraycopy(columnBuffer.Buffer, 0, holder, 0,
          columnBuffer.Position);
      columnBuffer.Buffer = holder;
    }
    columnBuffer.Buffer[columnBuffer.Position++] = letter;
    dataBuffer.ColumnStart = dataBuffer.Position + 1;
  }
  private void updateCurrentValue() {
    if (startedColumn && dataBuffer.ColumnStart < dataBuffer.Position) {
      if (columnBuffer.Buffer.length - columnBuffer.Position < dataBuffer.Position
          - dataBuffer.ColumnStart) {
        int newLength = columnBuffer.Buffer.length
            + Math.max(
                dataBuffer.Position - dataBuffer.ColumnStart,
                columnBuffer.Buffer.length);
        char[] holder = new char[newLength];
        System.arraycopy(columnBuffer.Buffer, 0, holder, 0,
            columnBuffer.Position);
        columnBuffer.Buffer = holder;
      }
      System.arraycopy(dataBuffer.Buffer, dataBuffer.ColumnStart,
          columnBuffer.Buffer, columnBuffer.Position,
          dataBuffer.Position - dataBuffer.ColumnStart);
      columnBuffer.Position += dataBuffer.Position
          - dataBuffer.ColumnStart;
    }
    dataBuffer.ColumnStart = dataBuffer.Position + 1;
  }
  /**
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  private void endRecord() throws IOException {
    // this flag is used as a loop exit condition
    // during parsing
    hasReadNextLine = true;
    currentRecord++;
  }
  /**
   * Gets the corresponding column index for a given column header name.
   * 
   * @param headerName
   *            The header name of the column.
   * @return The column index for the given column header name.&nbsp;Returns
   *         -1 if not found.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public int getIndex(String headerName) throws IOException {
    checkClosed();
    Object indexValue = headersHolder.IndexByName.get(headerName);
    if (indexValue != null) {
      return ((Integer) indexValue).intValue();
    } else {
      return -1;
    }
  }
  /**
   * Skips the next record of data by parsing each column.&nbsp;Does not
   * increment
   * {@link com.csvreader.CsvReader#getCurrentRecord getCurrentRecord()}.
   * 
   * @return Whether another record was successfully skipped or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean skipRecord() throws IOException {
    checkClosed();
    boolean recordRead = false;
    if (hasMoreData) {
      recordRead = readRecord();
      if (recordRead) {
        currentRecord--;
      }
    }
    return recordRead;
  }
  /**
   * Skips the next line of data using the standard end of line characters and
   * does not do any column delimited parsing.
   * 
   * @return Whether a line was successfully skipped or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean skipLine() throws IOException {
    checkClosed();
    // clear public column values for current line
    columnsCount = 0;
    boolean skippedLine = false;
    if (hasMoreData) {
      boolean foundEol = false;
      do {
        if (dataBuffer.Position == dataBuffer.Count) {
          checkDataLength();
        } else {
          skippedLine = true;
          // grab the current letter as a char
          char currentLetter = dataBuffer.Buffer[dataBuffer.Position];
          if (currentLetter == Letters.CR
              || currentLetter == Letters.LF) {
            foundEol = true;
          }
          // keep track of the last letter because we need
          // it for several key decisions
          lastLetter = currentLetter;
          if (!foundEol) {
            dataBuffer.Position++;
          }
        } // end else
      } while (hasMoreData && !foundEol);
      columnBuffer.Position = 0;
      dataBuffer.LineStart = dataBuffer.Position + 1;
    }
    rawBuffer.Position = 0;
    rawRecord = "";
    return skippedLine;
  }
  /**
   * Closes and releases all related resources.
   */
  public void close() {
    if (!closed) {
      close(true);
      closed = true;
    }
  }
  /**
   * 
   */
  private void close(boolean closing) {
    if (!closed) {
      if (closing) {
        charset = null;
        headersHolder.Headers = null;
        headersHolder.IndexByName = null;
        dataBuffer.Buffer = null;
        columnBuffer.Buffer = null;
        rawBuffer.Buffer = null;
      }
      try {
        if (initialized) {
          inputStream.close();
        }
      } catch (Exception e) {
        // just eat the exception
      }
      inputStream = null;
      closed = true;
    }
  }
  /**
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  private void checkClosed() throws IOException {
    if (closed) {
      throw new IOException(
          "This instance of the CsvReader class has already been closed.");
    }
  }
  /**
   * 
   */
  protected void finalize() {
    close(false);
  }
  private class ComplexEscape {
    private static final int UNICODE = 1;
    private static final int OCTAL = 2;
    private static final int DECIMAL = 3;
    private static final int HEX = 4;
  }
  private static char hexToDec(char hex) {
    char result;
    if (hex >= "a") {
      result = (char) (hex - "a" + 10);
    } else if (hex >= "A") {
      result = (char) (hex - "A" + 10);
    } else {
      result = (char) (hex - "0");
    }
    return result;
  }
  private class DataBuffer {
    public char[] Buffer;
    public int Position;
    // / <summary>
    // / How much usable data has been read into the stream,
    // / which will not always be as long as Buffer.Length.
    // / </summary>
    public int Count;
    // / <summary>
    // / The position of the cursor in the buffer when the
    // / current column was started or the last time data
    // / was moved out to the column buffer.
    // / </summary>
    public int ColumnStart;
    public int LineStart;
    public DataBuffer() {
      Buffer = new char[StaticSettings.MAX_BUFFER_SIZE];
      Position = 0;
      Count = 0;
      ColumnStart = 0;
      LineStart = 0;
    }
  }
  private class ColumnBuffer {
    public char[] Buffer;
    public int Position;
    public ColumnBuffer() {
      Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE];
      Position = 0;
    }
  }
  private class RawRecordBuffer {
    public char[] Buffer;
    public int Position;
    public RawRecordBuffer() {
      Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE
          * StaticSettings.INITIAL_COLUMN_COUNT];
      Position = 0;
    }
  }
  private class Letters {
    public static final char LF = "\n";
    public static final char CR = "\r";
    public static final char QUOTE = """;
    public static final char COMMA = ",";
    public static final char SPACE = " ";
    public static final char TAB = "\t";
    public static final char POUND = "#";
    public static final char BACKSLASH = "\\";
    public static final char NULL = "\0";
    public static final char BACKSPACE = "\b";
    public static final char FORM_FEED = "\f";
    public static final char ESCAPE = "\u001B"; // ASCII/ANSI escape
    public static final char VERTICAL_TAB = "\u000B";
    public static final char ALERT = "\u0007";
  }
  private class UserSettings {
    // having these as publicly accessible members will prevent
    // the overhead of the method call that exists on properties
    public boolean CaseSensitive;
    public char TextQualifier;
    public boolean TrimWhitespace;
    public boolean UseTextQualifier;
    public char Delimiter;
    public char RecordDelimiter;
    public char Comment;
    public boolean UseComments;
    public int EscapeMode;
    public boolean SafetySwitch;
    public boolean SkipEmptyRecords;
    public boolean CaptureRawRecord;
    public UserSettings() {
      CaseSensitive = true;
      TextQualifier = Letters.QUOTE;
      TrimWhitespace = true;
      UseTextQualifier = true;
      Delimiter = Letters.ruMA;
      RecordDelimiter = Letters.NULL;
      Comment = Letters.POUND;
      UseComments = false;
      EscapeMode = CsvReader.ESCAPE_MODE_DOUBLED;
      SafetySwitch = true;
      SkipEmptyRecords = true;
      CaptureRawRecord = true;
    }
  }
  private class HeadersHolder {
    public String[] Headers;
    public int Length;
    public HashMap IndexByName;
    public HeadersHolder() {
      Headers = null;
      Length = 0;
      IndexByName = new HashMap();
    }
  }
  private class StaticSettings {
    // these are static instead of final so they can be changed in unit test
    // isn"t visible outside this class and is only accessed once during
    // CsvReader construction
    public static final int MAX_BUFFER_SIZE = 1024;
    public static final int MAX_FILE_BUFFER_SIZE = 4 * 1024;
    public static final int INITIAL_COLUMN_COUNT = 10;
    public static final int INITIAL_COLUMN_BUFFER_SIZE = 50;
  }
}





A utility class that parses a Comma Separated Values (CSV) file

  
/*
Java and XSLT
By Eric M.Burke
1st Edition September 2001 
ISBN: 0-596-00143-6
*/

import java.io.IOException;
import java.util.*;
import org.xml.sax.*;

import java.io.*;
import java.net.URL;
import org.xml.sax.*;
import org.xml.sax.helpers.*;

/**
 * A utility class that parses a Comma Separated Values (CSV) file
 * and outputs its contents using SAX2 events. The format of CSV that
 * this class reads is identical to the export format for Microsoft
 * Excel. For simple values, the CSV file may look like this:
 * <pre>
 * a,b,c
 * d,e,f
 * </pre>
 * Quotes are used as delimiters when the values contain commas:
 * <pre>
 * a,"b,c",d
 * e,"f,g","h,i"
 * </pre>
 * And double quotes are used when the values contain quotes. This parser
 * is smart enough to trim spaces around commas, as well.
 *
 * @author Eric M. Burke
 */
public class CSVXMLReader extends AbstractXMLReader {
    // an empty attribute for use with SAX
    private static final Attributes EMPTY_ATTR = new AttributesImpl();
    /**
     * Parse a CSV file. SAX events are delivered to the ContentHandler
     * that was registered via <code>setContentHandler</code>.
     *
     * @param input the comma separated values file to parse.
     */
    public void parse(InputSource input) throws IOException,
            SAXException {
        // if no handler is registered to receive events, don"t bother
        // to parse the CSV file
        ContentHandler ch = getContentHandler();
        if (ch == null) {
            return;
        }
        // convert the InputSource into a BufferedReader
        BufferedReader br = null;
        if (input.getCharacterStream() != null) {
            br = new BufferedReader(input.getCharacterStream());
        } else if (input.getByteStream() != null) {
            br = new BufferedReader(new InputStreamReader(
                    input.getByteStream()));
        } else if (input.getSystemId() != null) {
            java.net.URL url = new URL(input.getSystemId());
            br = new BufferedReader(new InputStreamReader(url.openStream()));
        } else {
            throw new SAXException("Invalid InputSource object");
        }
        ch.startDocument();
        // emit <csvFile>
        ch.startElement("","","csvFile",EMPTY_ATTR);
        // read each line of the file until EOF is reached
        String curLine = null;
        while ((curLine = br.readLine()) != null) {
            curLine = curLine.trim();
            if (curLine.length() > 0) {
                // create the <line> element
                ch.startElement("","","line",EMPTY_ATTR);
                // output data from this line
                parseLine(curLine, ch);
                // close the </line> element
                ch.endElement("","","line");
            }
        }
        // emit </csvFile>
        ch.endElement("","","csvFile");
        ch.endDocument();
    }
    // Break an individual line into tokens. This is a recursive function
    // that extracts the first token, then recursively parses the
    // remainder of the line.
    private void parseLine(String curLine, ContentHandler ch)
        throws IOException, SAXException {
        String firstToken = null;
        String remainderOfLine = null;
        int commaIndex = locateFirstDelimiter(curLine);
        if (commaIndex > -1) {
            firstToken = curLine.substring(0, commaIndex).trim();
            remainderOfLine = curLine.substring(commaIndex+1).trim();
        } else {
            // no commas, so the entire line is the token
            firstToken = curLine;
        }
        // remove redundant quotes
        firstToken = cleanupQuotes(firstToken);
        // emit the <value> element
        ch.startElement("","","value",EMPTY_ATTR);
        ch.characters(firstToken.toCharArray(), 0, firstToken.length());
        ch.endElement("","","value");
        // recursively process the remainder of the line
        if (remainderOfLine != null) {
            parseLine(remainderOfLine, ch);
        }
    }
    // locate the position of the comma, taking into account that
    // a quoted token may contain ignorable commas.
    private int locateFirstDelimiter(String curLine) {
        if (curLine.startsWith("\"")) {
            boolean inQuote = true;
            int numChars = curLine.length();
            for (int i=1; i<numChars; i++) {
                char curChar = curLine.charAt(i);
                if (curChar == """) {
                    inQuote = !inQuote;
                } else if (curChar == "," && !inQuote) {
                    return i;
                }
            }
            return -1;
        } else {
            return curLine.indexOf(",");
        }
    }
    // remove quotes around a token, as well as pairs of quotes
    // within a token.
    private String cleanupQuotes(String token) {
        StringBuffer buf = new StringBuffer();
        int length = token.length();
        int curIndex = 0;
        if (token.startsWith("\"") && token.endsWith("\"")) {
            curIndex = 1;
            length--;
        }
        boolean oneQuoteFound = false;
        boolean twoQuotesFound = false;
        while (curIndex < length) {
            char curChar = token.charAt(curIndex);
            if (curChar == """) {
                twoQuotesFound = (oneQuoteFound) ? true : false;
                oneQuoteFound = true;
            } else {
                oneQuoteFound = false;
                twoQuotesFound = false;
            }
            if (twoQuotesFound) {
                twoQuotesFound = false;
                oneQuoteFound = false;
                curIndex++;
                continue;
            }
            buf.append(curChar);
            curIndex++;
        }
        return buf.toString();
    }
}
/**
 * An abstract class that implements the SAX2 XMLReader interface. The
 * intent of this class is to make it easy for subclasses to act as
 * SAX2 XMLReader implementations. This makes it possible, for example, for
 * them to emit SAX2 events that can be fed into an XSLT processor for
 * transformation.
 *
 * @author Eric M. Burke
 */
abstract class AbstractXMLReader implements org.xml.sax.XMLReader {
    private Map featureMap = new HashMap();
    private Map propertyMap = new HashMap();
    private EntityResolver entityResolver;
    private DTDHandler dtdHandler;
    private ContentHandler contentHandler;
    private ErrorHandler errorHandler;
    /**
     * The only abstract method in this class. Derived classes can parse
     * any source of data and emit SAX2 events to the ContentHandler.
     */
    public abstract void parse(InputSource input) throws IOException,
            SAXException;
    public boolean getFeature(String name)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        Boolean featureValue = (Boolean) this.featureMap.get(name);
        return (featureValue == null) ? false
                : featureValue.booleanValue();
    }
    public void setFeature(String name, boolean value)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        this.featureMap.put(name, new Boolean(value));
    }
    public Object getProperty(String name)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        return this.propertyMap.get(name);
    }
    public void setProperty(String name, Object value)
            throws SAXNotRecognizedException, SAXNotSupportedException {
        this.propertyMap.put(name, value);
    }
    public void setEntityResolver(EntityResolver entityResolver) {
        this.entityResolver = entityResolver;
    }
    public EntityResolver getEntityResolver() {
        return this.entityResolver;
    }
    public void setDTDHandler(DTDHandler dtdHandler) {
        this.dtdHandler = dtdHandler;
    }
    public DTDHandler getDTDHandler() {
        return this.dtdHandler;
    }
    public void setContentHandler(ContentHandler contentHandler) {
        this.contentHandler = contentHandler;
    }
    public ContentHandler getContentHandler() {
        return this.contentHandler;
    }
    public void setErrorHandler(ErrorHandler errorHandler) {
        this.errorHandler = errorHandler;
    }
    public ErrorHandler getErrorHandler() {
        return this.errorHandler;
    }
    public void parse(String systemId) throws IOException, SAXException {
        parse(new InputSource(systemId));
    }
}





Builds a bracketed CSV list from the array

  
import java.lang.reflect.Array;
/* Copyright (c) 2001-2009, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** Provides a collection of convenience methods for processing and
 * creating objects with <code>String</code> value components.
 *
 * @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net)
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @author Nitin Chauhan
 * @version 1.9.0
 * @since 1.7.0
 */
public class Main {

  /**
   * Builds a bracketed CSV list from the array
   * @param array an array of Objects
   * @return string
   */
  public static String arrayToString(Object array) {
      int          len  = Array.getLength(array);
      int          last = len - 1;
      StringBuffer sb   = new StringBuffer(2 * (len + 1));
      sb.append("{");
      for (int i = 0; i < len; i++) {
          sb.append(Array.get(array, i));
          if (i != last) {
              sb.append(",");
          }
      }
      sb.append("}");
      return sb.toString();
  }

}





Builds a CSV list from the specified String[], separator string and quote string

  
   
   
import java.lang.reflect.Array;
/* Copyright (c) 2001-2009, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** Provides a collection of convenience methods for processing and
 * creating objects with <code>String</code> value components.
 *
 * @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net)
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @author Nitin Chauhan
 * @version 1.9.0
 * @since 1.7.0
 */
public class Main {

  /**
   * Builds a CSV list from the specified String[], separator string and
   * quote string. <p>
   *
   * <ul>
   * <li>All arguments are assumed to be non-null.
   * <li>Separates each list element with the value of the
   * <code>separator</code> argument.
   * <li>Prepends and appends each element with the value of the
   *     <code>quote</code> argument.
   * <li> No attempt is made to escape the quote character sequence if it is
   *      found internal to a list element.
   * <ul>
   * @return a CSV list
   * @param separator the <code>String</code> to use as the list element separator
   * @param quote the <code>String</code> with which to quote the list elements
   * @param s array of <code>String</code> objects
   */
  public static String getList(String[] s, String separator, String quote) {
      int          len = s.length;
      StringBuffer sb   = new StringBuffer(len * 16);
      for (int i = 0; i < len; i++) {
          sb.append(quote);
          sb.append(s[i]);
          sb.append(quote);
          if (i + 1 < len) {
              sb.append(separator);
          }
      }
      return sb.toString();
  }
}





Builds a CSV list from the specified two dimensional String[][], separator string and quote string.

  
   
import java.lang.reflect.Array;
/* Copyright (c) 2001-2009, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

/** Provides a collection of convenience methods for processing and
 * creating objects with <code>String</code> value components.
 *
 * @author Campbell Boucher-Burnett (boucherb@users dot sourceforge.net)
 * @author Fred Toussi (fredt@users dot sourceforge.net)
 * @author Nitin Chauhan
 * @version 1.9.0
 * @since 1.7.0
 */
public class Main {

  /**
   * Builds a CSV list from the specified String[][], separator string and
   * quote string. <p>
   *
   * <ul>
   * <li>All arguments are assumed to be non-null.
   * <li>Uses only the first element in each subarray.
   * <li>Separates each list element with the value of the
   * <code>separator</code> argument.
   * <li>Prepends and appends each element with the value of the
   *     <code>quote</code> argument.
   * <li> No attempt is made to escape the quote character sequence if it is
   *      found internal to a list element.
   * <ul>
   * @return a CSV list
   * @param separator the <code>String</code> to use as the list element separator
   * @param quote the <code>String</code> with which to quote the list elements
   * @param s the array of <code>String</code> array objects
   */
  public static String getList(String[][] s, String separator,
                               String quote) {
      int          len = s.length;
      StringBuffer sb   = new StringBuffer(len * 16);
      for (int i = 0; i < len; i++) {
          sb.append(quote);
          sb.append(s[i][0]);
          sb.append(quote);
          if (i + 1 < len) {
              sb.append(separator);
          }
      }
      return sb.toString();
  }
}





Csv Converter

  
/*
 * 
 * Copyright (c) 2004 SourceTap - www.sourcetap.ru
 *
 *  The contents of this file are subject to the SourceTap Public License 
 * ("License"); You may not use this file except in compliance with the 
 * License. You may obtain a copy of the License at http://www.sourcetap.ru/license.htm
 * Software distributed under the License is distributed on an  "AS IS"  basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License for
 * the specific language governing rights and limitations under the License.
 *
 * The above copyright notice and this permission notice shall be included
 * in all copies or substantial portions of the Software.
 *
 */

import java.io.BufferedReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
import java.util.StringTokenizer;

/**
 *
 * @author  Administrator
 * @version
 */
public class CsvConverter {
    private static String DELIM = ",";
    private String[] headers = null;
    private ArrayList data = new ArrayList();
    public CsvConverter(Reader in) {
        String line = "";
        boolean doHeader = true;
        StringTokenizer st = null;
        try {
            BufferedReader br = new BufferedReader(in);
            while ((line = br.readLine()) != null) {
                if (line == null) {
                    throw new IOException("Empty Data Source");
                }
                if (doHeader) {
                    headers = breakCSVStringApart(line);
                    doHeader = false;
                } else {
                    String[] rowArray = breakCSVStringApart(line);
                    if ((rowArray.length < headers.length) &&
                            (rowArray.length < 2)) {
                        //skip as blank row
                    } else {
                        data.add(rowArray);
                    }
                }
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                in.close();
            } catch (Exception e) {
                ;
            }
        }
    }
    /**
     * DOCUMENT ME!
     *
     * @return 
     *
     * @throws IOException 
     */
    public String[] getHeader() throws IOException {
        return headers;
    }
    /**
     * DOCUMENT ME!
     *
     * @return 
     *
     * @throws IOException 
     */
    public ArrayList getData() throws IOException {
        return data;
    }
    /**
     * DOCUMENT ME!
     *
     * @param fileName 
     */
    public void writeToFile(String fileName) {
        try {
            FileWriter bwOut = new FileWriter(fileName);
            //write headers
            for (int i = 0; i < headers.length; i++) {
                bwOut.write(createCSVField(headers[i]));
                if (i != (headers.length - 1)) {
                    bwOut.write(",");
                }
            }
            bwOut.write("\n");
            //write data
            for (int i = 0; i < data.size(); i++) {
                String[] dataArray = (String[]) data.get(i);
                for (int j = 0; j < dataArray.length; j++) {
                    bwOut.write(createCSVField(dataArray[j]));
                    if (j != (dataArray.length - 1)) {
                        bwOut.write(",");
                    }
                }
                bwOut.write("\n");
            }
            bwOut.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    /**
     * DOCUMENT ME!
     *
     * @param in 
     *
     * @return 
     */
    public String[] breakCSVStringApart(String in) {
        StringBuffer curString = new StringBuffer();
        List strings = new ArrayList();
        boolean escaped = false;
        boolean inquotedstring = false;
        for (int i = 0; i < in.length(); i++) {
            char c = in.charAt(i);
            switch (c) {
            case ",":
                if (inquotedstring) {
                    curString.append(",");
                } else {
                    strings.add(curString.toString().trim());
                    curString = new StringBuffer();
                }
            case " ":
                // end word
                //if (inquotedstring) {
                curString.append(" ");
                //}
                break;
            case "\t":
                // end word
                if (inquotedstring) {
                    curString.append("\t");
                }
                break;
            case """:
                if (escaped) {
                    curString.append(""");
                    escaped = false;
                } else if (inquotedstring) {
                    inquotedstring = false;
                    //strings.add(curString.toString());
                    //curString = new StringBuffer();
                } else {
                    inquotedstring = true;
                }
                break;
            case "\\":
                if (escaped) {
                    curString.append("\\");
                    escaped = false;
                } else {
                    escaped = true;
                }
                break;
            default:
                if (escaped) {
                    switch (c) {
                    case "n":
                        curString.append("\n");
                        break;
                    case "t":
                        curString.append("\t");
                        break;
                    case "r":
                        curString.append("\r");
                        break;
                    default:
                        break;
                    }
                    escaped = false;
                } else {
                    curString.append(c);
                }
            }
        }
        if (curString.length() > 0) {
            strings.add(curString.toString().trim());
        }
        return (String[]) strings.toArray(new String[0]);
    }
    /**
     * DOCUMENT ME!
     *
     * @param in 
     *
     * @return 
     */
    public static String createCSVField(String in) {
        StringBuffer curString = new StringBuffer();
        boolean needsQuotes = false;
        for (int i = 0; i < in.length(); i++) {
            char c = in.charAt(i);
            switch (c) {
            case "\n":
                curString.append("\\n");
                break;
            case "\t":
                curString.append("\\t");
                break;
            case "\r":
                curString.append("\\r");
                break;
            case ",":
                curString.append(",");
                needsQuotes = true;
                break;
            case "\\":
                curString.append("\\\\");
                break;
            case """:
                curString.append("\\\"");
                break;
            default:
                curString.append(c);
                break;
            }
        }
        if (needsQuotes) {
            return "\"" + curString.toString() + "\"";
        } else {
            return curString.toString();
        }
    }
}





CSV in action: lines from a file and print

  
/*
 * Copyright (c) Ian F. Darwin, http://www.darwinsys.ru/, 1996-2002.
 * All rights reserved. Software written by Ian F. Darwin and others.
 * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS""
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Java, the Duke mascot, and all variants of Sun"s Java "steaming coffee
 * cup" logo are trademarks of Sun Microsystems. Sun"s, and James Gosling"s,
 * pioneering role in inventing and promulgating (and standardizing) the Java 
 * language and environment is gratefully acknowledged.
 * 
 * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
 * inventing predecessor languages C and C++ is also gratefully acknowledged.
 */
import java.io.*;
import java.util.*;
/** CSV in action: lines from a file and print. */
public class CSVFile {
  public static void main(String[] args) throws IOException {
  
    // Construct a new CSV parser.
    CSV csv = new CSV();
    if (args.length == 0) {  // read standard input
      BufferedReader is = new BufferedReader(
        new InputStreamReader(System.in));
      process(csv, is);
    } else {
      for (int i=0; i<args.length; i++) {
        process(csv, new BufferedReader(new FileReader(args[i])));
      }
    }
  }
  protected static void process(CSV csv, BufferedReader is) throws IOException {
    String line;
    while ((line = is.readLine()) != null) {
      System.out.println("line = `" + line + """);
      Iterator e = csv.parse(line).iterator();
      int i = 0;
      while (e.hasNext()) 
        System.out.println("field[" + i++ + "] = `" +
          e.next() + """);
    }
  }
}
/** Parse comma-separated values (CSV), a common Windows file format.
 * Sample input: "LU",86.25,"11/4/1998","2:19PM",+4.0625
 * <p>
 * Inner logic adapted from a C++ original that was
 * Copyright (C) 1999 Lucent Technologies
 * Excerpted from "The Practice of Programming"
 * by Brian W. Kernighan and Rob Pike.
 * <p>
 * Included by permission of the http://tpop.awl.ru/ web site, 
 * which says:
 * "You may use this code for any purpose, as long as you leave 
 * the copyright notice and book citation attached." I have done so.
 * @author Brian W. Kernighan and Rob Pike (C++ original)
 * @author Ian F. Darwin (translation into Java and removal of I/O)
 * @author Ben Ballard (rewrote advQuoted to handle """" and for readability)
 */
class CSV {  
  public static final char DEFAULT_SEP = ",";
  /** Construct a CSV parser, with the default separator (`,"). */
  public CSV() {
    this(DEFAULT_SEP);
  }
  /** Construct a CSV parser with a given separator. 
   * @param sep The single char for the separator (not a list of
   * separator characters)
   */
  public CSV(char sep) {
    fieldSep = sep;
  }
  /** The fields in the current String */
  protected List list = new ArrayList();
  /** the separator char for this parser */
  protected char fieldSep;
  /** parse: break the input String into fields
   * @return java.util.Iterator containing each field 
   * from the original as a String, in order.
   */
  public List parse(String line)
  {
    StringBuffer sb = new StringBuffer();
    list.clear();      // recycle to initial state
    int i = 0;
    if (line.length() == 0) {
      list.add(line);
      return list;
    }
    do {
            sb.setLength(0);
            if (i < line.length() && line.charAt(i) == """)
                i = advQuoted(line, sb, ++i);  // skip quote
            else
                i = advPlain(line, sb, i);
            list.add(sb.toString());
      i++;
    } while (i < line.length());
    return list;
  }
  /** advQuoted: quoted field; return index of next separator */
  protected int advQuoted(String s, StringBuffer sb, int i)
  {
    int j;
    int len= s.length();
        for (j=i; j<len; j++) {
            if (s.charAt(j) == """ && j+1 < len) {
                if (s.charAt(j+1) == """) {
                    j++; // skip escape char
                } else if (s.charAt(j+1) == fieldSep) { //next delimeter
                    j++; // skip end quotes
                    break;
                }
            } else if (s.charAt(j) == """ && j+1 == len) { // end quotes at end of line
                break; //done
      }
      sb.append(s.charAt(j));  // regular character.
    }
    return j;
  }
  /** advPlain: unquoted field; return index of next separator */
  protected int advPlain(String s, StringBuffer sb, int i)
  {
    int j;
    j = s.indexOf(fieldSep, i); // look for separator
        if (j == -1) {                 // none found
            sb.append(s.substring(i));
            return s.length();
        } else {
            sb.append(s.substring(i, j));
            return j;
        }
    }
}
//File:CSV.dat
/*
"Hello","World"
123,456
"LU",86.25|"11/4/1998"|"2:19PM"|+4.0625
bad "input",123e01
XYZZY,""|"OReilly & Associates| Inc."|"Darwin| Ian"|"a \"glug\" bit|"|5|"Memory fault| core NOT dumped"
*/





CSV parser

Csv Reader

 
/*
 * Java CSV is a stream based library for reading and writing
 * CSV and other delimited data.
 *   
 * Copyright (C) Bruce Dunwiddie bruce@csvreader.ru
 *
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA
 */
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.Reader;
import java.io.StringReader;
import java.nio.charset.Charset;
import java.text.NumberFormat;
import java.util.HashMap;
/**
 * A stream based parser for parsing delimited text data from a file or a
 * stream.
 */
public class CsvReader {
  private Reader inputStream = null;
  private String fileName = null;
  // this holds all the values for switches that the user is allowed to set
  private UserSettings userSettings = new UserSettings();
  private Charset charset = null;
  private boolean useCustomRecordDelimiter = false;
  // this will be our working buffer to hold data chunks
  // read in from the data file
  private DataBuffer dataBuffer = new DataBuffer();
  private ColumnBuffer columnBuffer = new ColumnBuffer();
  private RawRecordBuffer rawBuffer = new RawRecordBuffer();
  private boolean[] isQualified = null;
  private String rawRecord = "";
  private HeadersHolder headersHolder = new HeadersHolder();
  // these are all more or less global loop variables
  // to keep from needing to pass them all into various
  // methods during parsing
  private boolean startedColumn = false;
  private boolean startedWithQualifier = false;
  private boolean hasMoreData = true;
  private char lastLetter = "\0";
  private boolean hasReadNextLine = false;
  private int columnsCount = 0;
  private long currentRecord = 0;
  private String[] values = new String[StaticSettings.INITIAL_COLUMN_COUNT];
  private boolean initialized = false;
  private boolean closed = false;
  /**
   * Double up the text qualifier to represent an occurance of the text
   * qualifier.
   */
  public static final int ESCAPE_MODE_DOUBLED = 1;
  /**
   * Use a backslash character before the text qualifier to represent an
   * occurance of the text qualifier.
   */
  public static final int ESCAPE_MODE_BACKSLASH = 2;
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file
   * as the data source.
   * 
   * @param fileName
   *            The path to the file to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            parsing the data.
   */
  public CsvReader(String fileName, char delimiter, Charset charset)
      throws FileNotFoundException {
    if (fileName == null) {
      throw new IllegalArgumentException(
          "Parameter fileName can not be null.");
    }
    if (charset == null) {
      throw new IllegalArgumentException(
          "Parameter charset can not be null.");
    }
    if (!new File(fileName).exists()) {
      throw new FileNotFoundException("File " + fileName
          + " does not exist.");
    }
    this.fileName = fileName;
    this.userSettings.Delimiter = delimiter;
    this.charset = charset;
    isQualified = new boolean[values.length];
  }
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file
   * as the data source.&nbsp;Uses ISO-8859-1 as the
   * {@link java.nio.charset.Charset Charset}.
   * 
   * @param fileName
   *            The path to the file to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   */
  public CsvReader(String fileName, char delimiter)
      throws FileNotFoundException {
    this(fileName, delimiter, Charset.forName("ISO-8859-1"));
  }
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a file
   * as the data source.&nbsp;Uses a comma as the column delimiter and
   * ISO-8859-1 as the {@link java.nio.charset.Charset Charset}.
   * 
   * @param fileName
   *            The path to the file to use as the data source.
   */
  public CsvReader(String fileName) throws FileNotFoundException {
    this(fileName, Letters.ruMA);
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a
   * {@link java.io.Reader Reader} object as the data source.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   */
  public CsvReader(Reader inputStream, char delimiter) {
    if (inputStream == null) {
      throw new IllegalArgumentException(
          "Parameter inputStream can not be null.");
    }
    this.inputStream = inputStream;
    this.userSettings.Delimiter = delimiter;
    initialized = true;
    isQualified = new boolean[values.length];
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using a
   * {@link java.io.Reader Reader} object as the data source.&nbsp;Uses a
   * comma as the column delimiter.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   */
  public CsvReader(Reader inputStream) {
    this(inputStream, Letters.ruMA);
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an
   * {@link java.io.InputStream InputStream} object as the data source.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   * @param delimiter
   *            The character to use as the column delimiter.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            parsing the data.
   */
  public CsvReader(InputStream inputStream, char delimiter, Charset charset) {
    this(new InputStreamReader(inputStream, charset), delimiter);
  }
  /**
   * Constructs a {@link com.csvreader.CsvReader CsvReader} object using an
   * {@link java.io.InputStream InputStream} object as the data
   * source.&nbsp;Uses a comma as the column delimiter.
   * 
   * @param inputStream
   *            The stream to use as the data source.
   * @param charset
   *            The {@link java.nio.charset.Charset Charset} to use while
   *            parsing the data.
   */
  public CsvReader(InputStream inputStream, Charset charset) {
    this(new InputStreamReader(inputStream, charset));
  }
  public boolean getCaptureRawRecord() {
    return userSettings.CaptureRawRecord;
  }
  public void setCaptureRawRecord(boolean captureRawRecord) {
    userSettings.CaptureRawRecord = captureRawRecord;
  }
  public String getRawRecord() {
    return rawRecord;
  }
  /**
   * Gets whether leading and trailing whitespace characters are being trimmed
   * from non-textqualified column data. Default is true.
   * 
   * @return Whether leading and trailing whitespace characters are being
   *         trimmed from non-textqualified column data.
   */
  public boolean getTrimWhitespace() {
    return userSettings.TrimWhitespace;
  }
  /**
   * Sets whether leading and trailing whitespace characters should be trimmed
   * from non-textqualified column data or not. Default is true.
   * 
   * @param trimWhitespace
   *            Whether leading and trailing whitespace characters should be
   *            trimmed from non-textqualified column data or not.
   */
  public void setTrimWhitespace(boolean trimWhitespace) {
    userSettings.TrimWhitespace = trimWhitespace;
  }
  /**
   * Gets the character being used as the column delimiter. Default is comma,
   * ",".
   * 
   * @return The character being used as the column delimiter.
   */
  public char getDelimiter() {
    return userSettings.Delimiter;
  }
  /**
   * Sets the character to use as the column delimiter. Default is comma, ",".
   * 
   * @param delimiter
   *            The character to use as the column delimiter.
   */
  public void setDelimiter(char delimiter) {
    userSettings.Delimiter = delimiter;
  }
  public char getRecordDelimiter() {
    return userSettings.RecordDelimiter;
  }
  /**
   * Sets the character to use as the record delimiter.
   * 
   * @param recordDelimiter
   *            The character to use as the record delimiter. Default is
   *            combination of standard end of line characters for Windows,
   *            Unix, or Mac.
   */
  public void setRecordDelimiter(char recordDelimiter) {
    useCustomRecordDelimiter = true;
    userSettings.RecordDelimiter = recordDelimiter;
  }
  /**
   * Gets the character to use as a text qualifier in the data.
   * 
   * @return The character to use as a text qualifier in the data.
   */
  public char getTextQualifier() {
    return userSettings.TextQualifier;
  }
  /**
   * Sets the character to use as a text qualifier in the data.
   * 
   * @param textQualifier
   *            The character to use as a text qualifier in the data.
   */
  public void setTextQualifier(char textQualifier) {
    userSettings.TextQualifier = textQualifier;
  }
  /**
   * Whether text qualifiers will be used while parsing or not.
   * 
   * @return Whether text qualifiers will be used while parsing or not.
   */
  public boolean getUseTextQualifier() {
    return userSettings.UseTextQualifier;
  }
  /**
   * Sets whether text qualifiers will be used while parsing or not.
   * 
   * @param useTextQualifier
   *            Whether to use a text qualifier while parsing or not.
   */
  public void setUseTextQualifier(boolean useTextQualifier) {
    userSettings.UseTextQualifier = useTextQualifier;
  }
  /**
   * Gets the character being used as a comment signal.
   * 
   * @return The character being used as a comment signal.
   */
  public char getComment() {
    return userSettings.rument;
  }
  /**
   * Sets the character to use as a comment signal.
   * 
   * @param comment
   *            The character to use as a comment signal.
   */
  public void setComment(char comment) {
    userSettings.rument = comment;
  }
  /**
   * Gets whether comments are being looked for while parsing or not.
   * 
   * @return Whether comments are being looked for while parsing or not.
   */
  public boolean getUseComments() {
    return userSettings.UseComments;
  }
  /**
   * Sets whether comments are being looked for while parsing or not.
   * 
   * @param useComments
   *            Whether comments are being looked for while parsing or not.
   */
  public void setUseComments(boolean useComments) {
    userSettings.UseComments = useComments;
  }
  /**
   * Gets the current way to escape an occurance of the text qualifier inside
   * qualified data.
   * 
   * @return The current way to escape an occurance of the text qualifier
   *         inside qualified data.
   */
  public int getEscapeMode() {
    return userSettings.EscapeMode;
  }
  /**
   * Sets the current way to escape an occurance of the text qualifier inside
   * qualified data.
   * 
   * @param escapeMode
   *            The way to escape an occurance of the text qualifier inside
   *            qualified data.
   * @exception IllegalArgumentException
   *                When an illegal value is specified for escapeMode.
   */
  public void setEscapeMode(int escapeMode) throws IllegalArgumentException {
    if (escapeMode != ESCAPE_MODE_DOUBLED
        && escapeMode != ESCAPE_MODE_BACKSLASH) {
      throw new IllegalArgumentException(
          "Parameter escapeMode must be a valid value.");
    }
    userSettings.EscapeMode = escapeMode;
  }
  public boolean getSkipEmptyRecords() {
    return userSettings.SkipEmptyRecords;
  }
  public void setSkipEmptyRecords(boolean skipEmptyRecords) {
    userSettings.SkipEmptyRecords = skipEmptyRecords;
  }
  /**
   * Safety caution to prevent the parser from using large amounts of memory
   * in the case where parsing settings like file encodings don"t end up
   * matching the actual format of a file. This switch can be turned off if
   * the file format is known and tested. With the switch off, the max column
   * lengths and max column count per record supported by the parser will
   * greatly increase. Default is true.
   * 
   * @return The current setting of the safety switch.
   */
  public boolean getSafetySwitch() {
    return userSettings.SafetySwitch;
  }
  /**
   * Safety caution to prevent the parser from using large amounts of memory
   * in the case where parsing settings like file encodings don"t end up
   * matching the actual format of a file. This switch can be turned off if
   * the file format is known and tested. With the switch off, the max column
   * lengths and max column count per record supported by the parser will
   * greatly increase. Default is true.
   * 
   * @param safetySwitch
   */
  public void setSafetySwitch(boolean safetySwitch) {
    userSettings.SafetySwitch = safetySwitch;
  }
  /**
   * Gets the count of columns found in this record.
   * 
   * @return The count of columns found in this record.
   */
  public int getColumnCount() {
    return columnsCount;
  }
  /**
   * Gets the index of the current record.
   * 
   * @return The index of the current record.
   */
  public long getCurrentRecord() {
    return currentRecord - 1;
  }
  /**
   * Gets the count of headers read in by a previous call to
   * {@link com.csvreader.CsvReader#readHeaders readHeaders()}.
   * 
   * @return The count of headers read in by a previous call to
   *         {@link com.csvreader.CsvReader#readHeaders readHeaders()}.
   */
  public int getHeaderCount() {
    return headersHolder.Length;
  }
  /**
   * Returns the header values as a string array.
   * 
   * @return The header values as a String array.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String[] getHeaders() throws IOException {
    checkClosed();
    if (headersHolder.Headers == null) {
      return null;
    } else {
      // use clone here to prevent the outside code from
      // setting values on the array directly, which would
      // throw off the index lookup based on header name
      String[] clone = new String[headersHolder.Length];
      System.arraycopy(headersHolder.Headers, 0, clone, 0,
          headersHolder.Length);
      return clone;
    }
  }
  public void setHeaders(String[] headers) {
    headersHolder.Headers = headers;
    headersHolder.IndexByName.clear();
    if (headers != null) {
      headersHolder.Length = headers.length;
    } else {
      headersHolder.Length = 0;
    }
    // use headersHolder.Length here in case headers is null
    for (int i = 0; i < headersHolder.Length; i++) {
      headersHolder.IndexByName.put(headers[i], Integer.valueOf(i));
    }
  }
  public String[] getValues() throws IOException {
    checkClosed();
    // need to return a clone, and can"t use clone because values.Length
    // might be greater than columnsCount
    String[] clone = new String[columnsCount];
    System.arraycopy(values, 0, clone, 0, columnsCount);
    return clone;
  }
  /**
   * Returns the current column value for a given column index.
   * 
   * @param columnIndex
   *            The index of the column.
   * @return The current column value.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String get(int columnIndex) throws IOException {
    checkClosed();
    if (columnIndex > -1 && columnIndex < columnsCount) {
      return values[columnIndex];
    } else {
      return "";
    }
  }
  /**
   * Returns the current column value for a given column header name.
   * 
   * @param headerName
   *            The header name of the column.
   * @return The current column value.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String get(String headerName) throws IOException {
    checkClosed();
    return get(getIndex(headerName));
  }
  /**
   * Creates a {@link com.csvreader.CsvReader CsvReader} object using a string
   * of data as the source.&nbsp;Uses ISO-8859-1 as the
   * {@link java.nio.charset.Charset Charset}.
   * 
   * @param data
   *            The String of data to use as the source.
   * @return A {@link com.csvreader.CsvReader CsvReader} object using the
   *         String of data as the source.
   */
  public static CsvReader parse(String data) {
    if (data == null) {
      throw new IllegalArgumentException(
          "Parameter data can not be null.");
    }
    return new CsvReader(new StringReader(data));
  }
  /**
   * Reads another record.
   * 
   * @return Whether another record was successfully read or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean readRecord() throws IOException {
    checkClosed();
    columnsCount = 0;
    rawBuffer.Position = 0;
    dataBuffer.LineStart = dataBuffer.Position;
    hasReadNextLine = false;
    // check to see if we"ve already found the end of data
    if (hasMoreData) {
      // loop over the data stream until the end of data is found
      // or the end of the record is found
      do {
        if (dataBuffer.Position == dataBuffer.Count) {
          checkDataLength();
        } else {
          startedWithQualifier = false;
          // grab the current letter as a char
          char currentLetter = dataBuffer.Buffer[dataBuffer.Position];
          if (userSettings.UseTextQualifier
              && currentLetter == userSettings.TextQualifier) {
            // this will be a text qualified column, so
            // we need to set startedWithQualifier to make it
            // enter the seperate branch to handle text
            // qualified columns
            lastLetter = currentLetter;
            // read qualified
            startedColumn = true;
            dataBuffer.ColumnStart = dataBuffer.Position + 1;
            startedWithQualifier = true;
            boolean lastLetterWasQualifier = false;
            char escapeChar = userSettings.TextQualifier;
            if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
              escapeChar = Letters.BACKSLASH;
            }
            boolean eatingTrailingJunk = false;
            boolean lastLetterWasEscape = false;
            boolean readingComplexEscape = false;
            int escape = ComplexEscape.UNICODE;
            int escapeLength = 0;
            char escapeValue = (char) 0;
            dataBuffer.Position++;
            do {
              if (dataBuffer.Position == dataBuffer.Count) {
                checkDataLength();
              } else {
                // grab the current letter as a char
                currentLetter = dataBuffer.Buffer[dataBuffer.Position];
                if (eatingTrailingJunk) {
                  dataBuffer.ColumnStart = dataBuffer.Position + 1;
                  if (currentLetter == userSettings.Delimiter) {
                    endColumn();
                  } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))
                      || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {
                    endColumn();
                    endRecord();
                  }
                } else if (readingComplexEscape) {
                  escapeLength++;
                  switch (escape) {
                  case ComplexEscape.UNICODE:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 4) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.OCTAL:
                    escapeValue *= (char) 8;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.DECIMAL:
                    escapeValue *= (char) 10;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.HEX:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 2) {
                      readingComplexEscape = false;
                    }
                    break;
                  }
                  if (!readingComplexEscape) {
                    appendLetter(escapeValue);
                  } else {
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                  }
                } else if (currentLetter == userSettings.TextQualifier) {
                  if (lastLetterWasEscape) {
                    lastLetterWasEscape = false;
                    lastLetterWasQualifier = false;
                  } else {
                    updateCurrentValue();
                    if (userSettings.EscapeMode == ESCAPE_MODE_DOUBLED) {
                      lastLetterWasEscape = true;
                    }
                    lastLetterWasQualifier = true;
                  }
                } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH
                    && lastLetterWasEscape) {
                  switch (currentLetter) {
                  case "n":
                    appendLetter(Letters.LF);
                    break;
                  case "r":
                    appendLetter(Letters.CR);
                    break;
                  case "t":
                    appendLetter(Letters.TAB);
                    break;
                  case "b":
                    appendLetter(Letters.BACKSPACE);
                    break;
                  case "f":
                    appendLetter(Letters.FORM_FEED);
                    break;
                  case "e":
                    appendLetter(Letters.ESCAPE);
                    break;
                  case "v":
                    appendLetter(Letters.VERTICAL_TAB);
                    break;
                  case "a":
                    appendLetter(Letters.ALERT);
                    break;
                  case "0":
                  case "1":
                  case "2":
                  case "3":
                  case "4":
                  case "5":
                  case "6":
                  case "7":
                    escape = ComplexEscape.OCTAL;
                    readingComplexEscape = true;
                    escapeLength = 1;
                    escapeValue = (char) (currentLetter - "0");
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  case "u":
                  case "x":
                  case "o":
                  case "d":
                  case "U":
                  case "X":
                  case "O":
                  case "D":
                    switch (currentLetter) {
                    case "u":
                    case "U":
                      escape = ComplexEscape.UNICODE;
                      break;
                    case "x":
                    case "X":
                      escape = ComplexEscape.HEX;
                      break;
                    case "o":
                    case "O":
                      escape = ComplexEscape.OCTAL;
                      break;
                    case "d":
                    case "D":
                      escape = ComplexEscape.DECIMAL;
                      break;
                    }
                    readingComplexEscape = true;
                    escapeLength = 0;
                    escapeValue = (char) 0;
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  default:
                    break;
                  }
                  lastLetterWasEscape = false;
                  // can only happen for ESCAPE_MODE_BACKSLASH
                } else if (currentLetter == escapeChar) {
                  updateCurrentValue();
                  lastLetterWasEscape = true;
                } else {
                  if (lastLetterWasQualifier) {
                    if (currentLetter == userSettings.Delimiter) {
                      endColumn();
                    } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))
                        || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {
                      endColumn();
                      endRecord();
                    } else {
                      dataBuffer.ColumnStart = dataBuffer.Position + 1;
                      eatingTrailingJunk = true;
                    }
                    // make sure to clear the flag for next
                    // run of the loop
                    lastLetterWasQualifier = false;
                  }
                }
                // keep track of the last letter because we need
                // it for several key decisions
                lastLetter = currentLetter;
                if (startedColumn) {
                  dataBuffer.Position++;
                  if (userSettings.SafetySwitch
                      && dataBuffer.Position
                          - dataBuffer.ColumnStart
                          + columnBuffer.Position > 100000) {
                    close();
                    throw new IOException(
                        "Maximum column length of 100,000 exceeded in column "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    columnsCount)
                            + " in record "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    currentRecord)
                            + ". Set the SafetySwitch property to false"
                            + " if you"re expecting column lengths greater than 100,000 characters to"
                            + " avoid this error.");
                  }
                }
              } // end else
            } while (hasMoreData && startedColumn);
          } else if (currentLetter == userSettings.Delimiter) {
            // we encountered a column with no data, so
            // just send the end column
            lastLetter = currentLetter;
            endColumn();
          } else if (useCustomRecordDelimiter
              && currentLetter == userSettings.RecordDelimiter) {
            // this will skip blank lines
            if (startedColumn || columnsCount > 0
                || !userSettings.SkipEmptyRecords) {
              endColumn();
              endRecord();
            } else {
              dataBuffer.LineStart = dataBuffer.Position + 1;
            }
            lastLetter = currentLetter;
          } else if (!useCustomRecordDelimiter
              && (currentLetter == Letters.CR || currentLetter == Letters.LF)) {
            // this will skip blank lines
            if (startedColumn
                || columnsCount > 0
                || (!userSettings.SkipEmptyRecords && (currentLetter == Letters.CR || lastLetter != Letters.CR))) {
              endColumn();
              endRecord();
            } else {
              dataBuffer.LineStart = dataBuffer.Position + 1;
            }
            lastLetter = currentLetter;
          } else if (userSettings.UseComments && columnsCount == 0
              && currentLetter == userSettings.rument) {
            // encountered a comment character at the beginning of
            // the line so just ignore the rest of the line
            lastLetter = currentLetter;
            skipLine();
          } else if (userSettings.TrimWhitespace
              && (currentLetter == Letters.SPACE || currentLetter == Letters.TAB)) {
            // do nothing, this will trim leading whitespace
            // for both text qualified columns and non
            startedColumn = true;
            dataBuffer.ColumnStart = dataBuffer.Position + 1;
          } else {
            // since the letter wasn"t a special letter, this
            // will be the first letter of our current column
            startedColumn = true;
            dataBuffer.ColumnStart = dataBuffer.Position;
            boolean lastLetterWasBackslash = false;
            boolean readingComplexEscape = false;
            int escape = ComplexEscape.UNICODE;
            int escapeLength = 0;
            char escapeValue = (char) 0;
            boolean firstLoop = true;
            do {
              if (!firstLoop
                  && dataBuffer.Position == dataBuffer.Count) {
                checkDataLength();
              } else {
                if (!firstLoop) {
                  // grab the current letter as a char
                  currentLetter = dataBuffer.Buffer[dataBuffer.Position];
                }
                if (!userSettings.UseTextQualifier
                    && userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH
                    && currentLetter == Letters.BACKSLASH) {
                  if (lastLetterWasBackslash) {
                    lastLetterWasBackslash = false;
                  } else {
                    updateCurrentValue();
                    lastLetterWasBackslash = true;
                  }
                } else if (readingComplexEscape) {
                  escapeLength++;
                  switch (escape) {
                  case ComplexEscape.UNICODE:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 4) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.OCTAL:
                    escapeValue *= (char) 8;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.DECIMAL:
                    escapeValue *= (char) 10;
                    escapeValue += (char) (currentLetter - "0");
                    if (escapeLength == 3) {
                      readingComplexEscape = false;
                    }
                    break;
                  case ComplexEscape.HEX:
                    escapeValue *= (char) 16;
                    escapeValue += hexToDec(currentLetter);
                    if (escapeLength == 2) {
                      readingComplexEscape = false;
                    }
                    break;
                  }
                  if (!readingComplexEscape) {
                    appendLetter(escapeValue);
                  } else {
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                  }
                } else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH
                    && lastLetterWasBackslash) {
                  switch (currentLetter) {
                  case "n":
                    appendLetter(Letters.LF);
                    break;
                  case "r":
                    appendLetter(Letters.CR);
                    break;
                  case "t":
                    appendLetter(Letters.TAB);
                    break;
                  case "b":
                    appendLetter(Letters.BACKSPACE);
                    break;
                  case "f":
                    appendLetter(Letters.FORM_FEED);
                    break;
                  case "e":
                    appendLetter(Letters.ESCAPE);
                    break;
                  case "v":
                    appendLetter(Letters.VERTICAL_TAB);
                    break;
                  case "a":
                    appendLetter(Letters.ALERT);
                    break;
                  case "0":
                  case "1":
                  case "2":
                  case "3":
                  case "4":
                  case "5":
                  case "6":
                  case "7":
                    escape = ComplexEscape.OCTAL;
                    readingComplexEscape = true;
                    escapeLength = 1;
                    escapeValue = (char) (currentLetter - "0");
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  case "u":
                  case "x":
                  case "o":
                  case "d":
                  case "U":
                  case "X":
                  case "O":
                  case "D":
                    switch (currentLetter) {
                    case "u":
                    case "U":
                      escape = ComplexEscape.UNICODE;
                      break;
                    case "x":
                    case "X":
                      escape = ComplexEscape.HEX;
                      break;
                    case "o":
                    case "O":
                      escape = ComplexEscape.OCTAL;
                      break;
                    case "d":
                    case "D":
                      escape = ComplexEscape.DECIMAL;
                      break;
                    }
                    readingComplexEscape = true;
                    escapeLength = 0;
                    escapeValue = (char) 0;
                    dataBuffer.ColumnStart = dataBuffer.Position + 1;
                    break;
                  default:
                    break;
                  }
                  lastLetterWasBackslash = false;
                } else {
                  if (currentLetter == userSettings.Delimiter) {
                    endColumn();
                  } else if ((!useCustomRecordDelimiter && (currentLetter == Letters.CR || currentLetter == Letters.LF))
                      || (useCustomRecordDelimiter && currentLetter == userSettings.RecordDelimiter)) {
                    endColumn();
                    endRecord();
                  }
                }
                // keep track of the last letter because we need
                // it for several key decisions
                lastLetter = currentLetter;
                firstLoop = false;
                if (startedColumn) {
                  dataBuffer.Position++;
                  if (userSettings.SafetySwitch
                      && dataBuffer.Position
                          - dataBuffer.ColumnStart
                          + columnBuffer.Position > 100000) {
                    close();
                    throw new IOException(
                        "Maximum column length of 100,000 exceeded in column "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    columnsCount)
                            + " in record "
                            + NumberFormat
                                .getIntegerInstance()
                                .format(
                                    currentRecord)
                            + ". Set the SafetySwitch property to false"
                            + " if you"re expecting column lengths greater than 100,000 characters to"
                            + " avoid this error.");
                  }
                }
              } // end else
            } while (hasMoreData && startedColumn);
          }
          if (hasMoreData) {
            dataBuffer.Position++;
          }
        } // end else
      } while (hasMoreData && !hasReadNextLine);
      // check to see if we hit the end of the file
      // without processing the current record
      if (startedColumn || lastLetter == userSettings.Delimiter) {
        endColumn();
        endRecord();
      }
    }
    if (userSettings.CaptureRawRecord) {
      if (hasMoreData) {
        if (rawBuffer.Position == 0) {
          rawRecord = new String(dataBuffer.Buffer,
              dataBuffer.LineStart, dataBuffer.Position
                  - dataBuffer.LineStart - 1);
        } else {
          rawRecord = new String(rawBuffer.Buffer, 0,
              rawBuffer.Position)
              + new String(dataBuffer.Buffer,
                  dataBuffer.LineStart, dataBuffer.Position
                      - dataBuffer.LineStart - 1);
        }
      } else {
        // for hasMoreData to ever be false, all data would have had to
        // have been
        // copied to the raw buffer
        rawRecord = new String(rawBuffer.Buffer, 0, rawBuffer.Position);
      }
    } else {
      rawRecord = "";
    }
    return hasReadNextLine;
  }
  /**
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  private void checkDataLength() throws IOException {
    if (!initialized) {
      if (fileName != null) {
        inputStream = new BufferedReader(new InputStreamReader(
            new FileInputStream(fileName), charset),
            StaticSettings.MAX_FILE_BUFFER_SIZE);
      }
      charset = null;
      initialized = true;
    }
    updateCurrentValue();
    if (userSettings.CaptureRawRecord && dataBuffer.Count > 0) {
      if (rawBuffer.Buffer.length - rawBuffer.Position < dataBuffer.Count
          - dataBuffer.LineStart) {
        int newLength = rawBuffer.Buffer.length
            + Math.max(dataBuffer.Count - dataBuffer.LineStart,
                rawBuffer.Buffer.length);
        char[] holder = new char[newLength];
        System.arraycopy(rawBuffer.Buffer, 0, holder, 0,
            rawBuffer.Position);
        rawBuffer.Buffer = holder;
      }
      System.arraycopy(dataBuffer.Buffer, dataBuffer.LineStart,
          rawBuffer.Buffer, rawBuffer.Position, dataBuffer.Count
              - dataBuffer.LineStart);
      rawBuffer.Position += dataBuffer.Count - dataBuffer.LineStart;
    }
    try {
      dataBuffer.Count = inputStream.read(dataBuffer.Buffer, 0,
          dataBuffer.Buffer.length);
    } catch (IOException ex) {
      close();
      throw ex;
    }
    // if no more data could be found, set flag stating that
    // the end of the data was found
    if (dataBuffer.Count == -1) {
      hasMoreData = false;
    }
    dataBuffer.Position = 0;
    dataBuffer.LineStart = 0;
    dataBuffer.ColumnStart = 0;
  }
  /**
   * Read the first record of data as column headers.
   * 
   * @return Whether the header record was successfully read or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean readHeaders() throws IOException {
    boolean result = readRecord();
    // copy the header data from the column array
    // to the header string array
    headersHolder.Length = columnsCount;
    headersHolder.Headers = new String[columnsCount];
    for (int i = 0; i < headersHolder.Length; i++) {
      String columnValue = get(i);
      headersHolder.Headers[i] = columnValue;
      // if there are duplicate header names, we will save the last one
      headersHolder.IndexByName.put(columnValue, Integer.valueOf(i));
    }
    if (result) {
      currentRecord--;
    }
    columnsCount = 0;
    return result;
  }
  /**
   * Returns the column header value for a given column index.
   * 
   * @param columnIndex
   *            The index of the header column being requested.
   * @return The value of the column header at the given column index.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public String getHeader(int columnIndex) throws IOException {
    checkClosed();
    // check to see if we have read the header record yet
    // check to see if the column index is within the bounds
    // of our header array
    if (columnIndex > -1 && columnIndex < headersHolder.Length) {
      // return the processed header data for this column
      return headersHolder.Headers[columnIndex];
    } else {
      return "";
    }
  }
  public boolean isQualified(int columnIndex) throws IOException {
    checkClosed();
    if (columnIndex < columnsCount && columnIndex > -1) {
      return isQualified[columnIndex];
    } else {
      return false;
    }
  }
  /**
   * @exception IOException
   *                Thrown if a very rare extreme exception occurs during
   *                parsing, normally resulting from improper data format.
   */
  private void endColumn() throws IOException {
    String currentValue = "";
    // must be called before setting startedColumn = false
    if (startedColumn) {
      if (columnBuffer.Position == 0) {
        if (dataBuffer.ColumnStart < dataBuffer.Position) {
          int lastLetter = dataBuffer.Position - 1;
          if (userSettings.TrimWhitespace && !startedWithQualifier) {
            while (lastLetter >= dataBuffer.ColumnStart
                && (dataBuffer.Buffer[lastLetter] == Letters.SPACE || dataBuffer.Buffer[lastLetter] == Letters.TAB)) {
              lastLetter--;
            }
          }
          currentValue = new String(dataBuffer.Buffer,
              dataBuffer.ColumnStart, lastLetter
                  - dataBuffer.ColumnStart + 1);
        }
      } else {
        updateCurrentValue();
        int lastLetter = columnBuffer.Position - 1;
        if (userSettings.TrimWhitespace && !startedWithQualifier) {
          while (lastLetter >= 0
              && (columnBuffer.Buffer[lastLetter] == Letters.SPACE || columnBuffer.Buffer[lastLetter] == Letters.SPACE)) {
            lastLetter--;
          }
        }
        currentValue = new String(columnBuffer.Buffer, 0,
            lastLetter + 1);
      }
    }
    columnBuffer.Position = 0;
    startedColumn = false;
    if (columnsCount >= 100000 && userSettings.SafetySwitch) {
      close();
      throw new IOException(
          "Maximum column count of 100,000 exceeded in record "
              + NumberFormat.getIntegerInstance().format(
                  currentRecord)
              + ". Set the SafetySwitch property to false"
              + " if you"re expecting more than 100,000 columns per record to"
              + " avoid this error.");
    }
    // check to see if our current holder array for
    // column chunks is still big enough to handle another
    // column chunk
    if (columnsCount == values.length) {
      // holder array needs to grow to be able to hold another column
      int newLength = values.length * 2;
      String[] holder = new String[newLength];
      System.arraycopy(values, 0, holder, 0, values.length);
      values = holder;
      boolean[] qualifiedHolder = new boolean[newLength];
      System.arraycopy(isQualified, 0, qualifiedHolder, 0,
          isQualified.length);
      isQualified = qualifiedHolder;
    }
    values[columnsCount] = currentValue;
    isQualified[columnsCount] = startedWithQualifier;
    currentValue = "";
    columnsCount++;
  }
  private void appendLetter(char letter) {
    if (columnBuffer.Position == columnBuffer.Buffer.length) {
      int newLength = columnBuffer.Buffer.length * 2;
      char[] holder = new char[newLength];
      System.arraycopy(columnBuffer.Buffer, 0, holder, 0,
          columnBuffer.Position);
      columnBuffer.Buffer = holder;
    }
    columnBuffer.Buffer[columnBuffer.Position++] = letter;
    dataBuffer.ColumnStart = dataBuffer.Position + 1;
  }
  private void updateCurrentValue() {
    if (startedColumn && dataBuffer.ColumnStart < dataBuffer.Position) {
      if (columnBuffer.Buffer.length - columnBuffer.Position < dataBuffer.Position
          - dataBuffer.ColumnStart) {
        int newLength = columnBuffer.Buffer.length
            + Math.max(
                dataBuffer.Position - dataBuffer.ColumnStart,
                columnBuffer.Buffer.length);
        char[] holder = new char[newLength];
        System.arraycopy(columnBuffer.Buffer, 0, holder, 0,
            columnBuffer.Position);
        columnBuffer.Buffer = holder;
      }
      System.arraycopy(dataBuffer.Buffer, dataBuffer.ColumnStart,
          columnBuffer.Buffer, columnBuffer.Position,
          dataBuffer.Position - dataBuffer.ColumnStart);
      columnBuffer.Position += dataBuffer.Position
          - dataBuffer.ColumnStart;
    }
    dataBuffer.ColumnStart = dataBuffer.Position + 1;
  }
  /**
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  private void endRecord() throws IOException {
    // this flag is used as a loop exit condition
    // during parsing
    hasReadNextLine = true;
    currentRecord++;
  }
  /**
   * Gets the corresponding column index for a given column header name.
   * 
   * @param headerName
   *            The header name of the column.
   * @return The column index for the given column header name.&nbsp;Returns
   *         -1 if not found.
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  public int getIndex(String headerName) throws IOException {
    checkClosed();
    Integer indexValue = headersHolder.IndexByName.get(headerName);
    if (indexValue != null) {
      return indexValue.intValue();
    } else {
      return -1;
    }
  }
  /**
   * Skips the next record of data by parsing each column.&nbsp;Does not
   * increment
   * {@link com.csvreader.CsvReader#getCurrentRecord getCurrentRecord()}.
   * 
   * @return Whether another record was successfully skipped or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean skipRecord() throws IOException {
    checkClosed();
    boolean recordRead = false;
    if (hasMoreData) {
      recordRead = readRecord();
      if (recordRead) {
        currentRecord--;
      }
    }
    return recordRead;
  }
  /**
   * Skips the next line of data using the standard end of line characters and
   * does not do any column delimited parsing.
   * 
   * @return Whether a line was successfully skipped or not.
   * @exception IOException
   *                Thrown if an error occurs while reading data from the
   *                source stream.
   */
  public boolean skipLine() throws IOException {
    checkClosed();
    // clear public column values for current line
    columnsCount = 0;
    boolean skippedLine = false;
    if (hasMoreData) {
      boolean foundEol = false;
      do {
        if (dataBuffer.Position == dataBuffer.Count) {
          checkDataLength();
        } else {
          skippedLine = true;
          // grab the current letter as a char
          char currentLetter = dataBuffer.Buffer[dataBuffer.Position];
          if (currentLetter == Letters.CR
              || currentLetter == Letters.LF) {
            foundEol = true;
          }
          // keep track of the last letter because we need
          // it for several key decisions
          lastLetter = currentLetter;
          if (!foundEol) {
            dataBuffer.Position++;
          }
        } // end else
      } while (hasMoreData && !foundEol);
      columnBuffer.Position = 0;
      dataBuffer.LineStart = dataBuffer.Position + 1;
    }
    rawBuffer.Position = 0;
    rawRecord = "";
    return skippedLine;
  }
  /**
   * Closes and releases all related resources.
   */
  public void close() {
    if (!closed) {
      close(true);
      closed = true;
    }
  }
  /**
   * 
   */
  private void close(boolean closing) {
    if (!closed) {
      if (closing) {
        charset = null;
        headersHolder.Headers = null;
        headersHolder.IndexByName = null;
        dataBuffer.Buffer = null;
        columnBuffer.Buffer = null;
        rawBuffer.Buffer = null;
      }
      try {
        if (initialized) {
          inputStream.close();
        }
      } catch (Exception e) {
        // just eat the exception
      }
      inputStream = null;
      closed = true;
    }
  }
  /**
   * @exception IOException
   *                Thrown if this object has already been closed.
   */
  private void checkClosed() throws IOException {
    if (closed) {
      throw new IOException(
          "This instance of the CsvReader class has already been closed.");
    }
  }
  /**
   * 
   */
  protected void finalize() {
    close(false);
  }
  private class ComplexEscape {
    private static final int UNICODE = 1;
    private static final int OCTAL = 2;
    private static final int DECIMAL = 3;
    private static final int HEX = 4;
  }
  private static char hexToDec(char hex) {
    char result;
    if (hex >= "a") {
      result = (char) (hex - "a" + 10);
    } else if (hex >= "A") {
      result = (char) (hex - "A" + 10);
    } else {
      result = (char) (hex - "0");
    }
    return result;
  }
  private class DataBuffer {
    public char[] Buffer;
    public int Position;
    // / <summary>
    // / How much usable data has been read into the stream,
    // / which will not always be as long as Buffer.Length.
    // / </summary>
    public int Count;
    // / <summary>
    // / The position of the cursor in the buffer when the
    // / current column was started or the last time data
    // / was moved out to the column buffer.
    // / </summary>
    public int ColumnStart;
    public int LineStart;
    public DataBuffer() {
      Buffer = new char[StaticSettings.MAX_BUFFER_SIZE];
      Position = 0;
      Count = 0;
      ColumnStart = 0;
      LineStart = 0;
    }
  }
  private class ColumnBuffer {
    public char[] Buffer;
    public int Position;
    public ColumnBuffer() {
      Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE];
      Position = 0;
    }
  }
  private class RawRecordBuffer {
    public char[] Buffer;
    public int Position;
    public RawRecordBuffer() {
      Buffer = new char[StaticSettings.INITIAL_COLUMN_BUFFER_SIZE
          * StaticSettings.INITIAL_COLUMN_COUNT];
      Position = 0;
    }
  }
  private class Letters {
    public static final char LF = "\n";
    public static final char CR = "\r";
    public static final char QUOTE = """;
    public static final char COMMA = ",";
    public static final char SPACE = " ";
    public static final char TAB = "\t";
    public static final char POUND = "#";
    public static final char BACKSLASH = "\\";
    public static final char NULL = "\0";
    public static final char BACKSPACE = "\b";
    public static final char FORM_FEED = "\f";
    public static final char ESCAPE = "\u001B"; // ASCII/ANSI escape
    public static final char VERTICAL_TAB = "\u000B";
    public static final char ALERT = "\u0007";
  }
  private class UserSettings {
    // having these as publicly accessible members will prevent
    // the overhead of the method call that exists on properties
    public boolean CaseSensitive;
    public char TextQualifier;
    public boolean TrimWhitespace;
    public boolean UseTextQualifier;
    public char Delimiter;
    public char RecordDelimiter;
    public char Comment;
    public boolean UseComments;
    public int EscapeMode;
    public boolean SafetySwitch;
    public boolean SkipEmptyRecords;
    public boolean CaptureRawRecord;
    public UserSettings() {
      CaseSensitive = true;
      TextQualifier = Letters.QUOTE;
      TrimWhitespace = true;
      UseTextQualifier = true;
      Delimiter = Letters.ruMA;
      RecordDelimiter = Letters.NULL;
      Comment = Letters.POUND;
      UseComments = false;
      EscapeMode = CsvReader.ESCAPE_MODE_DOUBLED;
      SafetySwitch = true;
      SkipEmptyRecords = true;
      CaptureRawRecord = true;
    }
  }
  private class HeadersHolder {
    public String[] Headers;
    public int Length;
    public HashMap<String, Integer> IndexByName;
    public HeadersHolder() {
      Headers = null;
      Length = 0;
      IndexByName = new HashMap<String, Integer>();
    }
  }
  private class StaticSettings {
    // these are static instead of final so they can be changed in unit test
    // isn"t visible outside this class and is only accessed once during
    // CsvReader construction
    public static final int MAX_BUFFER_SIZE = 1024;
    public static final int MAX_FILE_BUFFER_SIZE = 4 * 1024;
    public static final int INITIAL_COLUMN_COUNT = 10;
    public static final int INITIAL_COLUMN_BUFFER_SIZE = 50;
  }
}





CSV Writer

  

/**
 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.IOException;
import java.io.PrintWriter;
import java.io.Reader;
import java.io.Writer;
import java.math.BigDecimal;
import java.sql.Clob;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.sql.Time;
import java.sql.Timestamp;
import java.sql.Types;
import java.text.SimpleDateFormat;
import java.util.Iterator;
import java.util.List;
/**
 * A very simple CSV writer released under a commercial-friendly license.
 *
 * @author Glen Smith
 *
 */
public class CSVWriter {
    
    private Writer rawWriter;
    private PrintWriter pw;
    private char separator;
    private char quotechar;
    
    private char escapechar;
    
    private String lineEnd;
    /** The character used for escaping quotes. */
    public static final char DEFAULT_ESCAPE_CHARACTER = """;
    /** The default separator to use if none is supplied to the constructor. */
    public static final char DEFAULT_SEPARATOR = ",";
    /**
     * The default quote character to use if none is supplied to the
     * constructor.
     */
    public static final char DEFAULT_QUOTE_CHARACTER = """;
    
    /** The quote constant to use when you wish to suppress all quoting. */
    public static final char NO_QUOTE_CHARACTER = "\u0000";
    
    /** The escape constant to use when you wish to suppress all escaping. */
    public static final char NO_ESCAPE_CHARACTER = "\u0000";
    
    /** Default line terminator uses platform encoding. */
    public static final String DEFAULT_LINE_END = "\n";
    private static final SimpleDateFormat
      TIMESTAMP_FORMATTER = 
        new SimpleDateFormat("dd-MMM-yyyy HH:mm:ss");
    private static final SimpleDateFormat
      DATE_FORMATTER = 
        new SimpleDateFormat("dd-MMM-yyyy");
    
    /**
     * Constructs CSVWriter using a comma for the separator.
     *
     * @param writer
     *            the writer to an underlying CSV source.
     */
    public CSVWriter(Writer writer) {
        this(writer, DEFAULT_SEPARATOR);
    }
    /**
     * Constructs CSVWriter with supplied separator.
     *
     * @param writer
     *            the writer to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries.
     */
    public CSVWriter(Writer writer, char separator) {
        this(writer, separator, DEFAULT_QUOTE_CHARACTER);
    }
    /**
     * Constructs CSVWriter with supplied separator and quote char.
     *
     * @param writer
     *            the writer to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries
     * @param quotechar
     *            the character to use for quoted elements
     */
    public CSVWriter(Writer writer, char separator, char quotechar) {
      this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER);
    }
    /**
     * Constructs CSVWriter with supplied separator and quote char.
     *
     * @param writer
     *            the writer to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries
     * @param quotechar
     *            the character to use for quoted elements
     * @param escapechar
     *            the character to use for escaping quotechars or escapechars
     */
    public CSVWriter(Writer writer, char separator, char quotechar, char escapechar) {
        this(writer, separator, quotechar, escapechar, DEFAULT_LINE_END);
    }
    
    
    /**
     * Constructs CSVWriter with supplied separator and quote char.
     *
     * @param writer
     *            the writer to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries
     * @param quotechar
     *            the character to use for quoted elements
     * @param lineEnd
     *        the line feed terminator to use
     */
    public CSVWriter(Writer writer, char separator, char quotechar, String lineEnd) {
        this(writer, separator, quotechar, DEFAULT_ESCAPE_CHARACTER, lineEnd);
    }   
    
    
    
    /**
     * Constructs CSVWriter with supplied separator, quote char, escape char and line ending.
     *
     * @param writer
     *            the writer to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries
     * @param quotechar
     *            the character to use for quoted elements
     * @param escapechar
     *            the character to use for escaping quotechars or escapechars
     * @param lineEnd
     *        the line feed terminator to use
     */
    public CSVWriter(Writer writer, char separator, char quotechar, char escapechar, String lineEnd) {
        this.rawWriter = writer;
        this.pw = new PrintWriter(writer);
        this.separator = separator;
        this.quotechar = quotechar;
        this.escapechar = escapechar;
        this.lineEnd = lineEnd;
    }
    
    /**
     * 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 (Iterator iter = allLines.iterator(); iter.hasNext();) {
            String[] nextLine = (String[]) iter.next();
            writeNext(nextLine);
        }
    }
    protected void writeColumnNames(ResultSetMetaData metadata)
      throws SQLException {
      
      int columnCount =  metadata.getColumnCount();
      
      String[] nextLine = new String[columnCount];
    for (int i = 0; i < columnCount; i++) {
      nextLine[i] = metadata.getColumnName(i + 1);
    }
      writeNext(nextLine);
    }
    
    /**
     * Writes the entire ResultSet to a CSV file.
     *
     * The caller is responsible for closing the ResultSet.
     *
     * @param rs the recordset to write
     * @param includeColumnNames true if you want column names in the output, false otherwise
     *
     */
    public void writeAll(java.sql.ResultSet rs, boolean includeColumnNames)  throws SQLException, IOException {
      
      ResultSetMetaData metadata = rs.getMetaData();
      
      
      if (includeColumnNames) {
      writeColumnNames(metadata);
    }
      int columnCount =  metadata.getColumnCount();
      
      while (rs.next())
      {
          String[] nextLine = new String[columnCount];
          
          for (int i = 0; i < columnCount; i++) {
        nextLine[i] = getColumnValue(rs, metadata.getColumnType(i + 1), i + 1);
      }
          
        writeNext(nextLine);
      }
    }
    
    private static String getColumnValue(ResultSet rs, int colType, int colIndex)
        throws SQLException, IOException {
      String value = "";
      
    switch (colType)
    {
      case Types.BIT:
        Object bit = rs.getObject(colIndex);
        if (bit != null) {
          value = String.valueOf(bit);
        }
      break;
      case Types.BOOLEAN:
        boolean b = rs.getBoolean(colIndex);
        if (!rs.wasNull()) {
          value = Boolean.valueOf(b).toString();
        }
      break;
      case Types.CLOB:
        Clob c = rs.getClob(colIndex);
        if (c != null) {
          value = read(c);
        }
      break;
      case Types.BIGINT:
      case Types.DECIMAL:
      case Types.DOUBLE:
      case Types.FLOAT:
      case Types.REAL:
      case Types.NUMERIC:
        BigDecimal bd = rs.getBigDecimal(colIndex);
        if (bd != null) {
          value = "" + bd.doubleValue();
        }
      break;
      case Types.INTEGER:
      case Types.TINYINT:
      case Types.SMALLINT:
        int intValue = rs.getInt(colIndex);
        if (!rs.wasNull()) {
          value = "" + intValue;
        }
      break;
      case Types.JAVA_OBJECT:
        Object obj = rs.getObject(colIndex);
        if (obj != null) {
          value = String.valueOf(obj);
        }
      break;
      case Types.DATE:
        java.sql.Date date = rs.getDate(colIndex);
        if (date != null) {
          value = DATE_FORMATTER.format(date);;
        }
      break;
      case Types.TIME:
        Time t = rs.getTime(colIndex);
        if (t != null) {
          value = t.toString();
        }
      break;
      case Types.TIMESTAMP:
        Timestamp tstamp = rs.getTimestamp(colIndex);
        if (tstamp != null) {
          value = TIMESTAMP_FORMATTER.format(tstamp);
        }
      break;
      case Types.LONGVARCHAR:
      case Types.VARCHAR:
      case Types.CHAR:
        value = rs.getString(colIndex);
      break;
      default:
        value = "";
    }
    
    if (value == null)
    {
      value = "";
    }
    
    return value;
      
    }
  private static String read(Clob c) throws SQLException, IOException
  {
    StringBuffer sb = new StringBuffer( (int) c.length());
    Reader r = c.getCharacterStream();
    char[] cbuf = new char[2048];
    int n = 0;
    while ((n = r.read(cbuf, 0, cbuf.length)) != -1) {
      if (n > 0) {
        sb.append(cbuf, 0, n);
      }
    }
    return sb.toString();
  }
    
    /**
     * 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;
      
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < nextLine.length; i++) {
            if (i != 0) {
                sb.append(separator);
            }
            String nextElement = nextLine[i];
            if (nextElement == null)
                continue;
            if (quotechar !=  NO_QUOTE_CHARACTER)
              sb.append(quotechar);
            for (int j = 0; j < nextElement.length(); j++) {
                char nextChar = nextElement.charAt(j);
                if (escapechar != NO_ESCAPE_CHARACTER && nextChar == quotechar) {
                  sb.append(escapechar).append(nextChar);
                } else if (escapechar != NO_ESCAPE_CHARACTER && nextChar == escapechar) {
                  sb.append(escapechar).append(nextChar);
                } else {
                    sb.append(nextChar);
                }
            }
            if (quotechar != NO_QUOTE_CHARACTER)
              sb.append(quotechar);
        }
        
        sb.append(lineEnd);
        pw.write(sb.toString());
    }
    /**
     * 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
     *
     */
    public void close() throws IOException {
        pw.flush();
        pw.close();
        rawWriter.close();
    }
}
////////////////////////////////////////

/**
 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.File;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.FileWriter;
import java.io.IOException;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.List;
import junit.framework.TestCase;
public class CSVWriterTest extends TestCase {

    /**
     * Test routine for converting output to a string.
     *
     * @param args
     *            the elements of a line of the cvs file
     * @return a String version
     * @throws IOException
     *             if there are problems writing
     */
    private String invokeWriter(String[] args) throws IOException {
        StringWriter sw = new StringWriter();
        CSVWriter csvw = new CSVWriter(sw,",","\"");
        csvw.writeNext(args);
        return sw.toString();
    }
    
    /**
     * Tests parsing individual lines.
     *
     * @throws IOException
     *             if the reader fails.
     */
    public void testParseLine() throws IOException {
        // test normal case
        String[] normal = { "a", "b", "c" };
        String output = invokeWriter(normal);
        assertEquals(""a","b","c"\n", output);
        // test quoted commas
        String[] quoted = { "a", "b,b,b", "c" };
        output = invokeWriter(quoted);
        assertEquals(""a","b,b,b","c"\n", output);
        // test empty elements
        String[] empty = { , };
        output = invokeWriter(empty);
        assertEquals("\n", output);
        // test multiline quoted
        String[] multiline = { "This is a \n multiline entry", "so is \n this" };
        output = invokeWriter(multiline);
        assertEquals(""This is a \n multiline entry","so is \n this"\n", output);
    }
    /**
     * Test parsing from to a list.
     *
     * @throws IOException
     *             if the reader fails.
     */
    public void testParseAll() throws IOException {
        List allElements = new ArrayList();
        String[] line1 = "Name#Phone#Email".split("#");
        String[] line2 = "Glen#1234#glen@abcd.ru".split("#");
        String[] line3 = "John#5678#john@efgh.ru".split("#");
        allElements.add(line1);
        allElements.add(line2);
        allElements.add(line3);
        StringWriter sw = new StringWriter();
        CSVWriter csvw = new CSVWriter(sw);
        csvw.writeAll(allElements);
        String result = sw.toString();
        String[] lines = result.split("\n");
        assertEquals(3, lines.length);
    }
    /**
     * Tests the option of having omitting quotes in the output stream.
     * 
     * @throws IOException if bad things happen
     */
    public void testNoQuoteChars() throws IOException {
      
        String[] line = {"Foo","Bar","Baz"};
        StringWriter sw = new StringWriter();
        CSVWriter csvw = new CSVWriter(sw, CSVWriter.DEFAULT_SEPARATOR, CSVWriter.NO_QUOTE_CHARACTER);
        csvw.writeNext(line);
        String result = sw.toString();
        assertEquals("Foo,Bar,Baz\n",result);
    }
    
    /**
     * Test null values.
     *
     * @throws IOException if bad things happen
     */
    public void testNullValues() throws IOException {
        String[] line = {"Foo",null,"Bar","baz"};
        StringWriter sw = new StringWriter();
        CSVWriter csvw = new CSVWriter(sw);
        csvw.writeNext(line);
        String result = sw.toString();
        assertEquals("\"Foo\",,\"Bar\",\"baz\"\n",result);
    }
    public void testStreamFlushing() throws IOException {
        String WRITE_FILE = "myfile.csv";
        String[] nextLine = new String[]{"aaaa", "bbbb","cccc","dddd"};
        FileWriter fileWriter = new FileWriter(WRITE_FILE);
        CSVWriter writer = new CSVWriter(fileWriter);
        writer.writeNext(nextLine);
        // If this line is not executed, it is not written in the file.
        writer.close();
    }
    public void testAlternateEscapeChar() {
        String[] line = {"Foo","bar"s"};
        StringWriter sw = new StringWriter();
        CSVWriter csvw = new CSVWriter(sw,CSVWriter.DEFAULT_SEPARATOR,CSVWriter.DEFAULT_QUOTE_CHARACTER,"\"");
        csvw.writeNext(line);
        assertEquals("\"Foo\",\"bar""s\"\n",sw.toString());
    }
    
    public void testNoQuotingNoEscaping() {
        String[] line = {"\"Foo\",\"Bar\""};
        StringWriter sw = new StringWriter();
        CSVWriter csvw = new CSVWriter(sw,CSVWriter.DEFAULT_SEPARATOR,CSVWriter.NO_QUOTE_CHARACTER,CSVWriter.NO_ESCAPE_CHARACTER);
        csvw.writeNext(line);
        assertEquals("\"Foo\",\"Bar\"\n",sw.toString());
    }
    
    public void testNestedQuotes(){
        String[] data = new String[]{"\"\"", "test"};
        String oracle = new String("\"\"\"\"\"\",\"test\"\n");
        CSVWriter writer=null;
        File tempFile=null;
        FileWriter fwriter=null;
        try{
            tempFile = File.createTempFile("csvWriterTest", ".csv");
            tempFile.deleteOnExit();
            fwriter = new FileWriter(tempFile);
            writer = new CSVWriter(fwriter);
        }catch(IOException e){
            fail();
        }
        // write the test data:
        writer.writeNext(data);
        try{
            writer.close();
        }catch(IOException e){
            fail();
        }
        try{
            // assert that the writer was also closed.
            fwriter.flush();
            fail();
        }catch(IOException e){
            // we should go through here..
        }
        // read the data and compare.
        FileReader in=null;
        try{
            in = new FileReader(tempFile);
        }catch(FileNotFoundException e){
            fail();
        }
        StringBuffer fileContents = new StringBuffer();
        try{
            int ch;
            while((ch = in.read()) != -1){
                fileContents.append((char)ch);
            }
            in.close();
        }catch(IOException e){
            fail();
        }
        assertTrue(oracle.equals(fileContents.toString()));
    }
    
    public void testAlternateLineFeeds() {
        String[] line = {"Foo","Bar","baz"};
        StringWriter sw = new StringWriter();
        CSVWriter csvw = new CSVWriter(sw, CSVWriter.DEFAULT_SEPARATOR, CSVWriter.DEFAULT_QUOTE_CHARACTER, "\r");
        csvw.writeNext(line);
        String result = sw.toString();
        
        assertTrue(result.endsWith("\r"));
      
    }
    /**
     * The Test Runner for commandline use.
     *
     * @param args
     *            no args required
     */
    public static void main(String args[]) {
        junit.textui.TestRunner.run(CSVWriterTest.class);
    }
}





CVS reader

  
package au.ru.bytecode.opencsv;
/**
 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.BufferedReader;
import java.io.IOException;
import java.io.Reader;
import java.util.ArrayList;
import java.util.List;
/**
 * A very simple CSV reader released under a commercial-friendly license.
 * 
 * @author Glen Smith
 * 
 */
public class CSVReader {
    private BufferedReader br;
    private boolean hasNext = true;
    private char separator;
    private char quotechar;
    
    private int skipLines;
    private boolean linesSkiped;
    /** The default separator to use if none is supplied to the constructor. */
    public static final char DEFAULT_SEPARATOR = ",";
    /**
     * The default quote character to use if none is supplied to the
     * constructor.
     */
    public static final char DEFAULT_QUOTE_CHARACTER = """;
    
    /**
     * The default line to start reading.
     */
    public static final int DEFAULT_SKIP_LINES = 0;
    /**
     * Constructs CSVReader using a comma for the separator.
     * 
     * @param reader
     *            the reader to an underlying CSV source.
     */
    public CSVReader(Reader reader) {
        this(reader, DEFAULT_SEPARATOR);
    }
    /**
     * Constructs CSVReader with supplied separator.
     * 
     * @param reader
     *            the reader to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries.
     */
    public CSVReader(Reader reader, char separator) {
        this(reader, separator, DEFAULT_QUOTE_CHARACTER);
    }
    
    
    /**
     * Constructs CSVReader with supplied separator and quote char.
     * 
     * @param reader
     *            the reader to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries
     * @param quotechar
     *            the character to use for quoted elements
     */
    public CSVReader(Reader reader, char separator, char quotechar) {
        this(reader, separator, quotechar, DEFAULT_SKIP_LINES);
    }
    
    /**
     * Constructs CSVReader with supplied separator and quote char.
     * 
     * @param reader
     *            the reader to an underlying CSV source.
     * @param separator
     *            the delimiter to use for separating entries
     * @param quotechar
     *            the character to use for quoted elements
     * @param line
     *            the line number to skip for start reading 
     */
    public CSVReader(Reader reader, char separator, char quotechar, int line) {
        this.br = new BufferedReader(reader);
        this.separator = separator;
        this.quotechar = quotechar;
        this.skipLines = line;
    }
    /**
     * Reads the entire file into a List with each element being a String[] of
     * tokens.
     * 
     * @return a List of String[], with each String[] representing a line of the
     *         file.
     * 
     * @throws IOException
     *             if bad things happen during the read
     */
    public List readAll() throws IOException {
        List allElements = new ArrayList();
        while (hasNext) {
            String[] nextLineAsTokens = readNext();
            if (nextLineAsTokens != null)
                allElements.add(nextLineAsTokens);
        }
        return allElements;
    }
    /**
     * Reads the next line from the buffer and converts to a string array.
     * 
     * @return a string array with each comma-separated element as a separate
     *         entry.
     * 
     * @throws IOException
     *             if bad things happen during the read
     */
    public String[] readNext() throws IOException {
        String nextLine = getNextLine();
        return hasNext ? parseLine(nextLine) : null;
    }
    /**
     * Reads the next line from the file.
     * 
     * @return the next line from the file without trailing newline
     * @throws IOException
     *             if bad things happen during the read
     */
    private String getNextLine() throws IOException {
      if (!this.linesSkiped) {
            for (int i = 0; i < skipLines; i++) {
                br.readLine();
            }
            this.linesSkiped = true;
        }
        String nextLine = br.readLine();
        if (nextLine == null) {
            hasNext = false;
        }
        return hasNext ? nextLine : null;
    }
    /**
     * Parses an incoming String and returns an array of elements.
     * 
     * @param nextLine
     *            the string to parse
     * @return the comma-tokenized list of elements, or null if nextLine is null
     * @throws IOException if bad things happen during the read
     */
    private String[] parseLine(String nextLine) throws IOException {
        if (nextLine == null) {
            return null;
        }
        List tokensOnThisLine = new ArrayList();
        StringBuffer sb = new StringBuffer();
        boolean inQuotes = false;
        do {
          if (inQuotes) {
                // continuing a quoted section, reappend newline
                sb.append("\n");
                nextLine = getNextLine();
                if (nextLine == null)
                    break;
            }
            for (int i = 0; i < nextLine.length(); i++) {
                char c = nextLine.charAt(i);
                if (c == quotechar) {
                  // this gets complex... the quote may end a quoted block, or escape another quote.
                  // do a 1-char lookahead:
                  if( inQuotes  // we are in quotes, therefore there can be escaped quotes in here.
                      && nextLine.length() > (i+1)  // there is indeed another character to check.
                      && nextLine.charAt(i+1) == quotechar ){ // ..and that char. is a quote also.
                    // we have two quote chars in a row == one quote char, so consume them both and
                    // put one on the token. we do *not* exit the quoted text.
                    sb.append(nextLine.charAt(i+1));
                    i++;
                  }else{
                    inQuotes = !inQuotes;
                    // the tricky case of an embedded quote in the middle: a,bc"d"ef,g
                    if(i>2 //not on the begining of the line
                        && nextLine.charAt(i-1) != this.separator //not at the begining of an escape sequence 
                        && nextLine.length()>(i+1) &&
                        nextLine.charAt(i+1) != this.separator //not at the end of an escape sequence
                    ){
                      sb.append(c);
                    }
                  }
                } else if (c == separator && !inQuotes) {
                    tokensOnThisLine.add(sb.toString());
                    sb = new StringBuffer(); // start work on next token
                } else {
                    sb.append(c);
                }
            }
        } while (inQuotes);
        tokensOnThisLine.add(sb.toString());
        return (String[]) tokensOnThisLine.toArray(new String[0]);
    }
    /**
     * Closes the underlying reader.
     * 
     * @throws IOException if the close fails
     */
    public void close() throws IOException{
      br.close();
    }
    
}
///////////////////////////////////////////
package au.ru.bytecode.opencsv;
/**
 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.IOException;
import java.io.StringReader;
import java.util.List;
import junit.framework.TestCase;
public class CSVReaderTest extends TestCase {
  CSVReader csvr;
  
  /**
   * Setup the test.
   */
  protected void setUp() throws Exception {
    StringBuffer sb = new StringBuffer();
    sb.append("a,b,c").append("\n");   // standard case
    sb.append("a,\"b,b,b\",c").append("\n");  // quoted elements
    sb.append(",,").append("\n"); // empty elements
    sb.append("a,\"PO Box 123,\nKippax,ACT. 2615.\nAustralia\",d.\n");
    sb.append("\"Glen \"\"The Man\"\" Smith\",Athlete,Developer\n"); // Test quoted quote chars
    sb.append("\"\"\"\"\"\",\"test\"\n"); // """""","test"  representing:  "", test
    sb.append("\"a\nb\",b,\"\nd\",e\n");
    csvr = new CSVReader(new StringReader(sb.toString()));
  }

  /**
   * Tests iterating over a reader.
   * 
   * @throws IOException
   *             if the reader fails.
   */
  public void testParseLine() throws IOException {
    // test normal case
    String[] nextLine = csvr.readNext();
    assertEquals("a", nextLine[0]);
    assertEquals("b", nextLine[1]);
    assertEquals("c", nextLine[2]);
    // test quoted commas
    nextLine = csvr.readNext();
    assertEquals("a", nextLine[0]);
    assertEquals("b,b,b", nextLine[1]);
    assertEquals("c", nextLine[2]);
    // test empty elements
    nextLine = csvr.readNext();
    assertEquals(3, nextLine.length);
    
    // test multiline quoted
    nextLine = csvr.readNext();
    assertEquals(3, nextLine.length);
    
    // test quoted quote chars
    nextLine = csvr.readNext();
    assertEquals("Glen \"The Man\" Smith", nextLine[0]);
    
    nextLine = csvr.readNext();
    assertTrue(nextLine[0].equals("\"\"")); // check the tricky situation
    assertTrue(nextLine[1].equals("test")); // make sure we didn"t ruin the next field..
    
    nextLine = csvr.readNext();
    assertEquals(4, nextLine.length);
    
    //test end of stream
    assertEquals(null, csvr.readNext());
  }
  /**
   * Test parsing to a list.
   * 
   * @throws IOException
   *             if the reader fails.
   */
  public void testParseAll() throws IOException {
    List allElements = csvr.readAll();
    assertEquals(7, allElements.size());
  }
  
  /**
   * Tests constructors with optional delimiters and optional quote char.
   * 
   * @throws IOException if the reader fails.
   */
  public void testOptionalConstructors() throws IOException {
    
    StringBuffer sb = new StringBuffer();
    sb.append("a\tb\tc").append("\n");   // tab separated case
    sb.append("a\t"b\tb\tb"\tc").append("\n");  // single quoted elements
    CSVReader c = new CSVReader(new StringReader(sb.toString()), "\t", "\"");
    
    String[] nextLine = c.readNext();
    assertEquals(3, nextLine.length);
    nextLine = c.readNext();
    assertEquals(3, nextLine.length);
    
  }
  
  /**
   * Tests option to skip the first few lines of a file.
   * 
   * @throws IOException if bad things happen
   */
  public void testSkippingLines() throws IOException {
    
    StringBuffer sb = new StringBuffer();
    sb.append("Skip this line\t with tab").append("\n");   // should skip this
    sb.append("And this line too").append("\n");   // and this
    sb.append("a\t"b\tb\tb"\tc").append("\n");  // single quoted elements
    CSVReader c = new CSVReader(new StringReader(sb.toString()), "\t", "\"", 2);
    
    String[] nextLine = c.readNext();
    assertEquals(3, nextLine.length);
    
    assertEquals("a", nextLine[0]);
  }
  
  /**
   * Tests quotes in the middle of an element.
   * 
   * @throws IOException if bad things happen
   */
  public void testParsedLineWithInternalQuota() throws IOException {
    StringBuffer sb = new StringBuffer();
    sb.append("a,123\"4\"567,c").append("\n");// a,123"4",c
    CSVReader c = new CSVReader(new StringReader(sb.toString()));
    String[] nextLine = c.readNext();
    assertEquals(3, nextLine.length);
    System.out.println(nextLine[1]);
    assertEquals("123\"4\"567", nextLine[1]);
  }
  /**
   * The Test Runner for commandline use.
   * 
   * @param args
   *            no args required
   */
  public static void main(String args[]) {
    junit.textui.TestRunner.run(CSVReaderTest.class);
  }
}





Helper class to write table data to a csv-file (comma separated values).

  
 
/* Copyright (c) 2001-2009, The HSQL Development Group
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are met:
 *
 * Redistributions of source code must retain the above copyright notice, this
 * list of conditions and the following disclaimer.
 *
 * Redistributions in binary form must reproduce the above copyright notice,
 * this list of conditions and the following disclaimer in the documentation
 * and/or other materials provided with the distribution.
 *
 * Neither the name of the HSQL Development Group nor the names of its
 * contributors may be used to endorse or promote products derived from this
 * software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED. IN NO EVENT SHALL HSQL DEVELOPMENT GROUP, HSQLDB.ORG,
 * OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStreamWriter;
/**
 * helper class to write table data to a csv-file (comma separated values).
 * the first line in file is a list of fieldnames, all following lines
 * are data lines.
 * a descptiontion of file format can be found on: http://www.wotsit.org/
 * usage: create a object using the constructor. call writeHeader
 * for writing the filename header then add data with writeData.
 * at the end close() closes the file.
 *
 *@author jeberle@users
 */
public class CSVWriter {
    private String             newline = System.getProperty("line.separator");
    private OutputStreamWriter writer  = null;
    private int                nbrCols = 0;
    private int                nbrRows = 0;
    /**
     * constructor.
     * creates a csv file for writing data to it
     * @param file the file to write data to
     * @param encoding encoding to use or null (=defualt)
     */
    public CSVWriter(File file, String encoding) throws IOException {
        if (encoding == null) {
            encoding = System.getProperty("file.encoding");
        }
        FileOutputStream fout = new FileOutputStream(file);
        writer = new OutputStreamWriter(fout, encoding);
    }
    /**
     * writes the csv header (fieldnames). should be called after
     * construction one time.
     * @param header String[] with fieldnames
     */
    public void writeHeader(String[] header) throws IOException {
        this.nbrCols = header.length;
        doWriteData(header);
    }
    /**
     * writes a data-record to the file. note that data[] must have
     * same number of elements as the header had.
     *
     * @param data data to write to csv-file
     */
    public void writeData(String[] data) throws IOException {
        doWriteData(data);
    }
    /**
     * closes the csv file.
     */
    public void close() throws IOException {
        this.writer.close();
    }
    private void doWriteData(String[] values) throws IOException {
        for (int i = 0; i < values.length; i++) {
            if (i > 0) {
                this.writer.write(";");
            }
            if (values[i] != null) {
                this.writer.write("\"");
                this.writer.write(this.toCsvValue(values[i]));
                this.writer.write("\"");
            }
        }
        this.writer.write(newline);
        this.nbrRows++;
    }
    private String toCsvValue(String str) {
        StringBuffer sb = new StringBuffer();
        for (int i = 0; i < str.length(); i++) {
            char c = str.charAt(i);
            sb.append(c);
            switch (c) {
                case """ :
                    sb.append(""");
                    break;
            }
        }
        return sb.toString();
    }
}





Reads CSV (Comma Separated Value) files

  
/*------------------------------------------------------------------------------
Name:      CSVReader.java
Project:   jutils.org
Comment:   Reads CSV (Comma Separated Value) files
Version:   $Id: CSVReader.java,v 1.1 2004/04/07 07:40:45 laurent Exp $
Author:    Roedy Green roedy@mindprod.ru, Heinrich Goetzger goetzger@gmx.net
------------------------------------------------------------------------------*/

import java.util.Vector;
import java.io.BufferedReader;
import java.io.EOFException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
/**
 * Reads CSV (Comma Separated Value) files.
 *
 * This format is mostly used my Microsoft Word and Excel.
 * Fields are separated by commas, and enclosed in
 * quotes if they contain commas or quotes.
 * Embedded quotes are doubled.
 * Embedded spaces do not normally require surrounding quotes.
 * The last field on the line is not followed by a comma.
 * Null fields are represented by two commas in a row.
 * We ignore leading and trailing spaces on fields, even inside quotes.
 *
 * @author copyright (c) 2002 Roedy Green  Canadian Mind Products
 * Roedy posted this code on Newsgroups:comp.lang.java.programmer on 27th March 2002.
 *
 * Heinrich added some stuff like comment ability and linewise working.
 *
 */
public class CSVReader {
   /**
    * Constructor
    *
    * @param r     input Reader source of CSV Fields to read.
    * @param separator
    *               field separator character, usually "," in North America,
    *               ";" in Europe and sometimes "\t" for tab.
    */
   public CSVReader (Reader r, char separator) {
      /* convert Reader to BufferedReader if necessary */
      if ( r instanceof BufferedReader ) {
         this.r = (BufferedReader) r;
      } else {
         this.r = new BufferedReader(r);
      }
      this.separator = separator;
   } // end of CSVReader
   /**
    * Constructor with default field separator ",".
    *
    * @param r     input Reader source of CSV Fields to read.
    */
   public CSVReader (Reader r) {
      /* convert Reader to BufferedReader if necessary */
      if ( r instanceof BufferedReader ) {
         this.r = (BufferedReader) r;
      } else {
         this.r = new BufferedReader(r);
      }
      this.separator = ",";
   } // end of CSVReader
   private static final boolean debugging = true;
   /**
    * Reader source of the CSV fields to be read.
    */
   private BufferedReader r;
   /*
   * field separator character, usually "," in North America,
   * ";" in Europe and sometimes "\t" for tab.
   */
   private char separator;
   /**
    * category of end of line char.
    */
   private static final int EOL = 0;
   /**
    * category of ordinary character
    */
   private static final int ORDINARY = 1;
   /**
    * categotory of the quote mark "
    */
   private static final int QUOTE = 2;
   /**
    * category of the separator, e.g. comma, semicolon
    * or tab.
    */
   private static final int SEPARATOR = 3;
   /**
    * category of characters treated as white space.
    */
   private static final int WHITESPACE = 4;
   /**
    * categorise a character for the finite state machine.
    *
    * @param c      the character to categorise
    * @return integer representing the character"s category.
    */
   private int categorise ( char c ) {
      switch ( c ) {
         case " ":
         case "\r":
         case 0xff:
            return WHITESPACE;
//         case ";":
//         case "!":
         case "#":
            //return EOL;
         case "\n":
            return EOL; /* artificially applied to end of line */
         case "\"":
            return QUOTE;
         default:
            if (c == separator) {
               /* dynamically determined so can"t use as case label */
               return SEPARATOR;
            } else if ( "!" <= c && c <= "~" ) {
               /* do our tests in crafted order, hoping for an early return */
               return ORDINARY;
            } else if ( 0x00 <= c && c <= 0x20 ) {
               return WHITESPACE;
            } else if ( Character.isWhitespace(c) ) {
               return WHITESPACE;
            } else {
               return ORDINARY;
            }
      } // end of switch
   } // end of categorise

   /**
    * parser: We are in blanks before the field.
    */
   private static final int SEEKINGSTART = 0;
   /**
    * parser: We are in the middle of an ordinary field.
    */
   private static final int INPLAIN = 1;
   /**
    * parser: e are in middle of field surrounded in quotes.
    */
   private static final int INQUOTED = 2;
   /**
    * parser: We have just hit a quote, might be doubled
    * or might be last one.
    */
   private static final int AFTERENDQUOTE = 3;
   /**
   * parser: We are in blanks after the field looking for the separator
   */
   private static final int SKIPPINGTAIL = 4;
   /**
    * state of the parser"s finite state automaton.
    */
   /**
    * The line we are parsing.
    * null means none read yet.
    * Line contains unprocessed chars. Processed ones are removed.
    */
   private String line = null;
   /**
    * How many lines we have read so far.
    * Used in error messages.
    */
   private int lineCount = 0;
   public String[] getLine() {
      Vector lineArray = new Vector();
      String token = null;
      String returnArray [] = null;
      // reading values from line until null comes
      try {
         while (lineArray.size() == 0) {
            while ( (token = get() ) != null ) {
               lineArray.add(token);
            } // end of while
         } // end of while
      } catch (EOFException e) {
         return null;
      } catch (IOException e) {
      }
      returnArray = new String[lineArray.size()];
      for(int ii=0; ii < lineArray.size(); ii++) {
         returnArray[ii] = lineArray.elementAt(ii).toString();
      } // end of for
      return returnArray;
   }
   /**
    * Read one field from the CSV file
    *
    * @return String value, even if the field is numeric.  Surrounded
    *         and embedded double quotes are stripped.
    *         possibly "".  null means end of line.
    *
    * @exception EOFException
    *                   at end of file after all the fields have
    *                   been read.
    *
    * @exception IOException
    *                   Some problem reading the file, possibly malformed data.
    */
   private String get() throws EOFException, IOException {
      StringBuffer field = new StringBuffer(50);
      /* we implement the parser as a finite state automaton with five states. */
      readLine();
      int state = SEEKINGSTART; /* start seeking, even if partway through a line */
      /* don"t need to maintain state between fields. */
      /* loop for each char in the line to find a field */
      /* guaranteed to leave early by hitting EOL */
      for ( int i=0; i<line.length(); i++ ) {
         char c = line.charAt(i);
         int category = categorise(c);
         switch ( state ) {
            case SEEKINGSTART: {
               /* in blanks before field */
               switch ( category ) {
                  case WHITESPACE:
                     /* ignore */
                     break;
                  case QUOTE:
                     state = INQUOTED;
                     break;
                  case SEPARATOR:
                     /* end of empty field */
                     line = line.substring(i+1);
                     return "";
                  case EOL:
                     /* end of line */
                     line = null;
                     return null;
                  case ORDINARY:
                     field.append(c);
                     state = INPLAIN;
                     break;
               }
               break;
            } // end of SEEKINGSTART
            case INPLAIN: {
               /* in middle of ordinary field */
               switch ( category ) {
                  case QUOTE:
                     throw new IOException("Malformed CSV stream. Missing quote at start of field on line " + lineCount);
                  case SEPARATOR:
                     /* done */
                     line = line.substring(i+1);
                     return field.toString().trim();
                  case EOL:
                     line = line.substring(i); /* push EOL back */
                     return field.toString().trim();
                  case WHITESPACE:
                     field.append(" ");
                     break;
                  case ORDINARY:
                     field.append(c);
                     break;
               }
               break;
            } // end of INPLAIN
            case INQUOTED: {
               /* in middle of field surrounded in quotes */
               switch ( category ) {
                  case QUOTE:
                     state = AFTERENDQUOTE;
                     break;
                  case EOL:
                     throw new IOException ("Malformed CSV stream. Missing quote after field on line "+lineCount);
                  case WHITESPACE:
                     field.append(" ");
                     break;
                  case SEPARATOR:
                  case ORDINARY:
                     field.append(c);
                     break;
               }
                break;
            } // end of INQUOTED
            case AFTERENDQUOTE: {
               /* In situation like this "xxx" which may
                  turn out to be xxx""xxx" or "xxx",
                  We find out here. */
               switch ( category ) {
                     case QUOTE:
                        field.append(c);
                        state = INQUOTED;
                        break;
                     case SEPARATOR :
                        /* we are done.*/
                        line = line.substring(i+1);
                        return field.toString().trim();
                     case EOL:
                        line = line.substring(i); /* push back eol */
                        return field.toString().trim();
                     case WHITESPACE:
                        /* ignore trailing spaces up to separator */
                        state = SKIPPINGTAIL;
                        break;
                     case ORDINARY:
                        throw new IOException("Malformed CSV stream, missing separator after field on line " + lineCount);
               }
               break;
            } // end of AFTERENDQUOTE
            case SKIPPINGTAIL: {
               /* in spaces after field seeking separator */
               switch ( category ) {
                  case SEPARATOR :
                     /* we are done.*/
                     line = line.substring(i+1);
                     return field.toString().trim();
                  case EOL:
                     line = line.substring(i); /* push back eol */
                     return field.toString().trim();
                  case WHITESPACE:
                     /* ignore trailing spaces up to separator */
                     break;
                  case QUOTE:
                  case ORDINARY:
                     throw new IOException("Malformed CSV stream, missing separator after field on line " + lineCount);
               } // end of switch
               break;
            } // end of SKIPPINGTAIL
         } // end switch(state)
      } // end for
      throw new IOException("Program logic bug. Should not reach here. Processing line " + lineCount);
   } // end get
   /**
    * Make sure a line is available for parsing.
    * Does nothing if there already is one.
    *
    * @exception EOFException
    */
   private void readLine() throws EOFException, IOException {
      if ( line == null ) {
         line = r.readLine();  /* this strips platform specific line ending */
         if ( line == null ) {
                /* null means EOF, yet another inconsistent Java convention. */
            throw new EOFException();
         } else {
            line += "\n"; /* apply standard line end for parser to find */
            lineCount++;
         }
      }
   } // end of readLine

   /**
    * Skip over fields you don"t want to process.
    *
    * @param fields How many field you want to bypass reading.
    *               The newline counts as one field.
    * @exception EOFException
    *                   at end of file after all the fields have
    *                   been read.
    * @exception IOException
    *                   Some problem reading the file, possibly malformed data.
    */
   public void skip(int fields) throws EOFException, IOException {
      if ( fields <= 0 ) {
         return;
      }
      for ( int i=0; i<fields; i++ ) {
         // throw results away
         get();
      }
   } // end of skip
   /**
    * Skip over remaining fields on this line you don"t want to process.
    *
    * @exception EOFException
    *                   at end of file after all the fields have
    *                   been read.
    * @exception IOException
    *                   Some problem reading the file, possibly malformed data.
    */
   public void skipToNextLine() throws EOFException, IOException {
      if ( line == null ) {
         readLine();
      }
      line = null;
   } // end of skipToNextLine
   /**
    * Close the Reader.
    */
   public void close() throws IOException {
      if ( r != null ) {
         r.close();
         r = null;
      }
   } // end of close
   /**
    * @param args  [0]: The name of the file.
    */
   private static void testSingleTokens(String[] args) {
      if ( debugging ) {
         try {
            // read test file
              CSVReader csv = new CSVReader(new FileReader(args[0]), ",");
           try {
               while ( true ) {
                  System.out.println(csv.get());
               }
            } catch ( EOFException  e ) {
                }
                csv.close();
         } catch ( IOException  e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
         }
      } // end if
   } // end of testSingleTokens
   /**
    * @param args  [0]: The name of the file.
    */
   private static void testLines(String[] args) {
      int lineCounter = 0;
      String loadLine[] = null;
      String DEL = ",";
      if ( debugging ) {
         try {
            // read test file
            CSVReader csv = new CSVReader(new FileReader(args[0]), ",");
            while( (loadLine = csv.getLine()) != null) {
               lineCounter++;
               StringBuffer logBuffer = new StringBuffer();
               String logLine;
               //log.debug("#" + lineCounter +" : "" + loadLine.length + """);
               logBuffer.append(loadLine[0]); // write first token, then write DEL in loop and the whole rest.
               for(int i=1; i < loadLine.length; i++) {
                  logBuffer.append(DEL).append(loadLine[i]);
               }
               logLine = logBuffer.toString();
               logLine.substring(0, logLine.lastIndexOf(DEL));
               //logLine.delete(logLine.lastIndexOf(DEL), logLine.length()); // is supported since JDK 1.4
               //System.out.println("#" + lineCounter +" : "" + loadLine.length + "" " + logLine);
               System.out.println(logLine);
            } // end of while
                csv.close();
         } catch ( IOException  e ) {
            e.printStackTrace();
            System.out.println(e.getMessage());
         }
      } // end if
   } // end of testLines
   /**
    * Test driver
    *
    * @param args  [0]: The name of the file.
    */
   static public void main(String[] args) {
      //testSingleTokens(args);
      testLines(args);
   } // end main
} // end CSVReader
// end of file





Simple demo of CSV matching using Regular Expressions

  
/*
 * Copyright (c) Ian F. Darwin, http://www.darwinsys.ru/, 1996-2002.
 * All rights reserved. Software written by Ian F. Darwin and others.
 * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS""
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Java, the Duke mascot, and all variants of Sun"s Java "steaming coffee
 * cup" logo are trademarks of Sun Microsystems. Sun"s, and James Gosling"s,
 * pioneering role in inventing and promulgating (and standardizing) the Java 
 * language and environment is gratefully acknowledged.
 * 
 * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
 * inventing predecessor languages C and C++ is also gratefully acknowledged.
 */
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/* Simple demo of CSV matching using Regular Expressions.
 * Does NOT use the "CSV" class defined in the Java CookBook, but uses
 * a regex pattern simplified from Chapter 7 of <em>Mastering Regular 
 * Expressions</em> (p. 205, first edn.)
 * @version $Id: CSVRE.java,v 1.16 2004/04/25 19:43:32 ian Exp $
 */
public class CSVRE {  
  /** The rather involved pattern used to match CSV"s consists of three
   * alternations: the first matches aquoted field, the second unquoted,
   * the third a null field.
   */
  public static final String CSV_PATTERN = "\"([^\"]+?)\",?|([^,]+),?|,";
  private static Pattern csvRE;
  public static void main(String[] argv) throws IOException {
    System.out.println(CSV_PATTERN);
    new CSVRE().process(new BufferedReader(new InputStreamReader(System.in)));
  }
  
  /** Construct a regex-based CSV parser. */
  public CSVRE() {
    csvRE = Pattern.rupile(CSV_PATTERN);
  }
  
  /** Process one file. Delegates to parse() a line at a time */
  public void process(BufferedReader in) throws IOException {
    String line;
    // For each line...
    while ((line = in.readLine()) != null) {
      System.out.println("line = `" + line + """);
      List l = parse(line);
      System.out.println("Found " + l.size() + " items.");
      for (int i = 0; i < l.size(); i++) {
        System.out.print(l.get(i) + ",");
      }
      System.out.println();
    }
  }
  
  /** Parse one line.
   * @return List of Strings, minus their double quotes
   */
  public List parse(String line) {
    List list = new ArrayList();
    Matcher m = csvRE.matcher(line);
    // For each field
    while (m.find()) {
      String match = m.group();
      if (match == null)
        break;
      if (match.endsWith(",")) {  // trim trailing ,
        match = match.substring(0, match.length() - 1);
      }
      if (match.startsWith("\"")) { // assume also ends with
        match = match.substring(1, match.length() - 1);
      }
      if (match.length() == 0)
        match = null;
      list.add(match);
    }
    return list;
  }
}





Simple demo of CSV parser class

  
/*
 * Copyright (c) Ian F. Darwin, http://www.darwinsys.ru/, 1996-2002.
 * All rights reserved. Software written by Ian F. Darwin and others.
 * $Id: LICENSE,v 1.8 2004/02/09 03:33:38 ian Exp $
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS""
 * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
 * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS
 * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
 * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
 * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
 * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 * 
 * Java, the Duke mascot, and all variants of Sun"s Java "steaming coffee
 * cup" logo are trademarks of Sun Microsystems. Sun"s, and James Gosling"s,
 * pioneering role in inventing and promulgating (and standardizing) the Java 
 * language and environment is gratefully acknowledged.
 * 
 * The pioneering role of Dennis Ritchie and Bjarne Stroustrup, of AT&T, for
 * inventing predecessor languages C and C++ is also gratefully acknowledged.
 */
import java.util.*;
/* Simple demo of CSV parser class.
 */
public class CSVSimple {  
  public static void main(String[] args) {
    CSV parser = new CSV();
    List list = parser.parse(
      "\"LU\",86.25,\"11/4/1998\",\"2:19PM\",+4.0625");
    Iterator it = list.iterator();
    while (it.hasNext()) {
      System.out.println(it.next());
    }
    // Now test with a non-default separator
    parser = new CSV("|");
    list = parser.parse(
      "\"LU\"|86.25|\"11/4/1998\"|\"2:19PM\"|+4.0625");
    it = list.iterator();
    while (it.hasNext()) {
      System.out.println(it.next());
    }
  }
}
/** Parse comma-separated values (CSV), a common Windows file format.
 * Sample input: "LU",86.25,"11/4/1998","2:19PM",+4.0625
 * <p>
 * Inner logic adapted from a C++ original that was
 * Copyright (C) 1999 Lucent Technologies
 * Excerpted from "The Practice of Programming"
 * by Brian W. Kernighan and Rob Pike.
 * <p>
 * Included by permission of the http://tpop.awl.ru/ web site, 
 * which says:
 * "You may use this code for any purpose, as long as you leave 
 * the copyright notice and book citation attached." I have done so.
 * @author Brian W. Kernighan and Rob Pike (C++ original)
 * @author Ian F. Darwin (translation into Java and removal of I/O)
 * @author Ben Ballard (rewrote advQuoted to handle """" and for readability)
 */
class CSV {  
  public static final char DEFAULT_SEP = ",";
  /** Construct a CSV parser, with the default separator (`,"). */
  public CSV() {
    this(DEFAULT_SEP);
  }
  /** Construct a CSV parser with a given separator. 
   * @param sep The single char for the separator (not a list of
   * separator characters)
   */
  public CSV(char sep) {
    fieldSep = sep;
  }
  /** The fields in the current String */
  protected List list = new ArrayList();
  /** the separator char for this parser */
  protected char fieldSep;
  /** parse: break the input String into fields
   * @return java.util.Iterator containing each field 
   * from the original as a String, in order.
   */
  public List parse(String line)
  {
    StringBuffer sb = new StringBuffer();
    list.clear();      // recycle to initial state
    int i = 0;
    if (line.length() == 0) {
      list.add(line);
      return list;
    }
    do {
            sb.setLength(0);
            if (i < line.length() && line.charAt(i) == """)
                i = advQuoted(line, sb, ++i);  // skip quote
            else
                i = advPlain(line, sb, i);
            list.add(sb.toString());
      i++;
    } while (i < line.length());
    return list;
  }
  /** advQuoted: quoted field; return index of next separator */
  protected int advQuoted(String s, StringBuffer sb, int i)
  {
    int j;
    int len= s.length();
        for (j=i; j<len; j++) {
            if (s.charAt(j) == """ && j+1 < len) {
                if (s.charAt(j+1) == """) {
                    j++; // skip escape char
                } else if (s.charAt(j+1) == fieldSep) { //next delimeter
                    j++; // skip end quotes
                    break;
                }
            } else if (s.charAt(j) == """ && j+1 == len) { // end quotes at end of line
                break; //done
      }
      sb.append(s.charAt(j));  // regular character.
    }
    return j;
  }
  /** advPlain: unquoted field; return index of next separator */
  protected int advPlain(String s, StringBuffer sb, int i)
  {
    int j;
    j = s.indexOf(fieldSep, i); // look for separator
        if (j == -1) {                 // none found
            sb.append(s.substring(i));
            return s.length();
        } else {
            sb.append(s.substring(i, j));
            return j;
        }
    }
}





The CSVQuoter is a helper class to encode a string for the CSV file format.

  
/**
 * 
 * JFreeReport : a free Java reporting library
 * 
 *
 * Project Info:  http://reporting.pentaho.org/
 *
 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
 *
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ------------
 * CSVQuoter.java
 * ------------
 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
 */

/**
 * The <code>CSVQuoter</code> is a helper class to encode a string for the CSV file
 * format.
 *
 * @author Thomas Morgner.
 */
public class CSVQuoter
{
  /**
   * The separator used in the CSV file.
   */
  private char separator;
  /**
   * The quoting character or a single quote.
   */
  private char quate;
  /**
   * The double quote. This is a string containing the quate two times.
   */
  private String doubleQuate;
  /**
   * Creates a new CSVQuoter, which uses a comma as the default separator.
   */
  public CSVQuoter ()
  {
    this(",", """);
  }
  /**
   * Creates a new <code>CSVQuoter</code>, which uses the defined separator.
   *
   * @param separator the separator.
   * @throws NullPointerException if the given separator is <code>null</code>.
   */
  public CSVQuoter (final char separator)
  {
    this(separator,  """);
  }
  /**
   * Creates a new CSVQuoter with the given separator and quoting character.
   *
   * @param separator the separator
   * @param quate the quoting character
   */
  public CSVQuoter (final char separator, final char quate)
  {
    this.separator = separator;
    this.quate = quate;
    this.doubleQuate = String.valueOf(quate) + quate;
  }
  /**
   * Encodes the string, so that the string can safely be used in CSV files. If the string
   * does not need quoting, the original string is returned unchanged.
   *
   * @param original the unquoted string.
   * @return The quoted string
   */
  public String doQuoting (final String original)
  {
    if (isQuotingNeeded(original))
    {
      final StringBuffer retval = new StringBuffer();
      retval.append(quate);
      applyQuote(retval, original);
      retval.append(quate);
      return retval.toString();
    }
    else
    {
      return original;
    }
  }
  /**
   * Decodes the string, so that all escape sequences get removed. If the string was not
   * quoted, then the string is returned unchanged.
   *
   * @param nativeString the quoted string.
   * @return The unquoted string.
   */
  public String undoQuoting (final String nativeString)
  {
    if (isQuotingNeeded(nativeString))
    {
      final StringBuffer b = new StringBuffer(nativeString.length());
      final int length = nativeString.length() - 1;
      int start = 1;
      int pos = start;
      while (pos != -1)
      {
        pos = nativeString.indexOf(doubleQuate, start);
        if (pos == -1)
        {
          b.append(nativeString.substring(start, length));
        }
        else
        {
          b.append(nativeString.substring(start, pos));
          start = pos + 1;
        }
      }
      return b.toString();
    }
    else
    {
      return nativeString;
    }
  }
  /**
   * Tests, whether this string needs to be quoted. A string is encoded if the string
   * contains a newline character, a quote character or the defined separator.
   *
   * @param str the string that should be tested.
   * @return true, if quoting needs to be applied, false otherwise.
   */
  private boolean isQuotingNeeded (final String str)
  {
    if (str.indexOf(separator) != -1)
    {
      return true;
    }
    if (str.indexOf("\n") != -1)
    {
      return true;
    }
    if (str.indexOf(quate, 1) != -1)
    {
      return true;
    }
    return false;
  }
  /**
   * Applies the quoting to a given string, and stores the result in the StringBuffer
   * <code>b</code>.
   *
   * @param b        the result buffer
   * @param original the string, that should be quoted.
   */
  private void applyQuote (final StringBuffer b, final String original)
  {
    // This solution needs improvements. Copy blocks instead of single
    // characters.
    final int length = original.length();
    for (int i = 0; i < length; i++)
    {
      final char c = original.charAt(i);
      if (c == quate)
      {
        b.append(doubleQuate);
      }
      else
      {
        b.append(c);
      }
    }
  }
  /**
   * Gets the separator used in this quoter and the CSV file.
   *
   * @return the separator (never <code>null</code>).
   */
  public char getSeparator ()
  {
    return separator;
  }
  /**
   * Returns the quoting character.
   *
   * @return the quote character.
   */
  public char getQuate ()
  {
    return quate;
  }
}





The csv tokenizer class allows an application to break a Comma Separated Value format into tokens.

  
/**
 * 
 * JFreeReport : a free Java reporting library
 * 
 *
 * Project Info:  http://reporting.pentaho.org/
 *
 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
 *
 * This library is free software; you can redistribute it and/or modify it under the terms
 * of the GNU Lesser General Public License as published by the Free Software Foundation;
 * either version 2.1 of the License, or (at your option) any later version.
 *
 * This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY;
 * without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
 * See the GNU Lesser General Public License for more details.
 *
 * You should have received a copy of the GNU Lesser General Public License along with this
 * library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330,
 * Boston, MA 02111-1307, USA.
 *
 * [Java is a trademark or registered trademark of Sun Microsystems, Inc.
 * in the United States and other countries.]
 *
 * ------------
 * CSVTokenizer.java
 * ------------
 * (C) Copyright 2001-2007, by Object Refinery Ltd, Pentaho Corporation and Contributors.
 */
import java.util.Enumeration;
import java.util.NoSuchElementException;
/**
 * The csv tokenizer class allows an application to break a Comma Separated Value format into tokens. The tokenization
 * method is much simpler than the one used by the <code>StringTokenizer</code> class. The <code>CSVTokenizer</code>
 * methods do not distinguish among identifiers, numbers, and quoted strings, nor do they recognize and skip comments.
 * <p/>
 * The set of separator (the characters that separate tokens) may be specified either at creation time or on a per-token
 * basis.
 * <p/>
 * An instance of <code>CSVTokenizer</code> behaves in one of two ways, depending on whether it was created with the
 * <code>returnSeparators</code> flag having the value <code>true</code> or <code>false</code>: <ul> <li>If the flag is
 * <code>false</code>, delimiter characters serve to separate tokens. A token is a maximal sequence of consecutive
 * characters that are not separator. <li>If the flag is <code>true</code>, delimiter characters are themselves
 * considered to be tokens. A token is thus either one delimiter character, or a maximal sequence of consecutive
 * characters that are not separator. </ul><p> A <tt>CSVTokenizer</tt> object internally maintains a current position
 * within the string to be tokenized. Some operations advance this current position past the characters processed.<p> A
 * token is returned by taking a substring of the string that was used to create the <tt>CSVTokenizer</tt> object.
 * <p/>
 * The following is one example of the use of the tokenizer. The code:
 * <blockquote><pre>
 *     CSVTokenizer csvt = new CSVTokenizer("this,is,a,test");
 *     while (csvt.hasMoreTokens()) {
 *         println(csvt.nextToken());
 *     }
 * </pre></blockquote>
 * <p/>
 * prints the following output:
 * <blockquote><pre>
 *     this
 *     is
 *     a
 *     test
 * </pre></blockquote>
 *
 * @author abupon
 */
public class CSVTokenizer implements Enumeration
{
  /**
   * The complete record that should be separated into elements.
   */
  private String record;
  /**
   * The separator.
   */
  private String separator;
  /**
   * The quoting char.
   */
  private String quate;
  /**
   * the current parsing position.
   */
  private int currentIndex;
  /**
   * A flag indicating that the current parse position is before the start.
   */
  private boolean beforeStart;
  /**
   * A possible separator constant.
   */
  public static final String SEPARATOR_COMMA = ",";
  /**
   * A possible separator constant.
   */
  public static final String SEPARATOR_TAB = "\t";
  /**
   * A possible separator constant.
   */
  public static final String SEPARATOR_SPACE = " ";
  /**
   * A possible quote character constant.
   */
  public static final String DOUBLE_QUATE = "\"";
  /**
   * A possible quote character constant.
   */
  public static final String SINGLE_QUATE = """;
  /**
   * Constructs a csv tokenizer for the specified string. <code>theSeparator</code> argument is the separator for
   * separating tokens.
   * <p/>
   * If the <code>returnSeparators</code> flag is <code>true</code>, then the separator string is also returned as
   * tokens. separator is returned as a string. If the flag is <code>false</code>, the separator string is skipped and
   * only serve as separator between tokens.
   *
   * @param aString      a string to be parsed.
   * @param theSeparator the separator (CSVTokenizer.SEPARATOR_COMMA, CSVTokenizer.TAB, CSVTokenizer.SPACE, etc.).
   * @param theQuate     the quate (CSVTokenizer.SINGLE_QUATE, CSVTokenizer.DOUBLE_QUATE, etc.).
   */
  public CSVTokenizer(final String aString, final String theSeparator,
                      final String theQuate)
  {
    if (aString == null)
    {
      throw new NullPointerException("The given string is null");
    }
    if (theSeparator == null)
    {
      throw new NullPointerException("The given separator is null");
    }
    if (theQuate == null)
    {
      throw new NullPointerException("The given quate is null");
    }
    this.record = aString.trim();
    this.separator = theSeparator;
    this.quate = theQuate;
    this.currentIndex = 0;
    this.beforeStart = true;
  }
  /**
   * Constructs a csv tokenizer for the specified string. The characters in the <code>theSeparator</code> argument are
   * the separator for separating tokens. Separator string themselves will not be treated as tokens.
   *
   * @param aString      a string to be parsed.
   * @param theSeparator the separator (CSVTokenizer.SEPARATOR_COMMA, CSVTokenizer.TAB, CSVTokenizer.SPACE, etc.).
   */
  public CSVTokenizer(final String aString, final String theSeparator)
  {
    this(aString, theSeparator, CSVTokenizer.DOUBLE_QUATE);
  }
  /**
   * Constructs a string tokenizer for the specified string. The tokenizer uses the default separator set, which is
   * <code>CSVTokenizer.SEPARATOR_COMMA</code>. Separator string themselves will not be treated as tokens.
   *
   * @param aString a string to be parsed.
   */
  public CSVTokenizer(final String aString)
  {
    this(aString, CSVTokenizer.SEPARATOR_COMMA);
  }
  /**
   * Tests if there are more tokens available from this tokenizer"s string. If this method returns <tt>true</tt>, then a
   * subsequent call to <tt>nextToken</tt> with no argument will successfully return a token.
   *
   * @return <code>true</code> if and only if there is at least one token in the string after the current position;
   *         <code>false</code> otherwise.
   */
  public boolean hasMoreTokens()
  {
    return (this.currentIndex < this.record.length());
  }
  /**
   * Returns the next token from this string tokenizer.
   *
   * @return the next token from this string tokenizer.
   * @throws NoSuchElementException   if there are no more tokens in this tokenizer"s string.
   * @throws IllegalArgumentException if given parameter string format was wrong
   */
  public String nextToken()
      throws NoSuchElementException, IllegalArgumentException
  {
    if (!this.hasMoreTokens())
    {
      throw new NoSuchElementException();
    }
    if (beforeStart == false)
    {
      currentIndex += this.separator.length();
    }
    else
    {
      beforeStart = false;
    }
    if (this.record.startsWith(this.quate, this.currentIndex))
    {
      final StringBuffer token = new StringBuffer();
      String rec = this.record.substring(this.currentIndex + this.quate.length());
      while (true)
      {
        final int end = rec.indexOf(this.quate);
        if (end < 0)
        {
          throw new IllegalArgumentException("Illegal format");
        }
        if (!rec.startsWith(this.quate, end + 1))
        {
          token.append(rec.substring(0, end));
          break;
        }
        token.append(rec.substring(0, end + 1));
        rec = rec.substring(end + this.quate.length() * 2);
        this.currentIndex++;
      }
      this.currentIndex += (token.length() + this.quate.length() * 2);
      return token.toString();
    }
    final int end = this.record.indexOf(this.separator, this.currentIndex);
    if (end >= 0)
    {
      final int start = this.currentIndex;
      final String token = this.record.substring(start, end);
      this.currentIndex = end;
      return token;
    }
    else
    {
      final int start = this.currentIndex;
      final String token = this.record.substring(start);
      this.currentIndex = this.record.length();
      return token;
    }
  }
  /**
   * Returns the next token in this string tokenizer"s string. First, the set of characters considered to be separator
   * by this <tt>CSVTokenizer</tt> object is changed to be the characters in the string <tt>separator</tt>. Then the
   * next token in the string after the current position is returned. The current position is advanced beyond the
   * recognized token.  The new delimiter set remains the default after this call.
   *
   * @param theSeparator the new separator.
   * @return the next token, after switching to the new delimiter set.
   * @throws java.util.NoSuchElementException
   *          if there are no more tokens in this tokenizer"s string.
   */
  public String nextToken(final String theSeparator)
  {
    separator = theSeparator;
    return nextToken();
  }
  /**
   * Returns the same value as the <code>hasMoreTokens</code> method. It exists so that this class can implement the
   * <code>Enumeration</code> interface.
   *
   * @return <code>true</code> if there are more tokens; <code>false</code> otherwise.
   * @see java.util.Enumeration
   * @see org.jfree.report.util.CSVTokenizer#hasMoreTokens()
   */
  public boolean hasMoreElements()
  {
    return hasMoreTokens();
  }
  /**
   * Returns the same value as the <code>nextToken</code> method, except that its declared return value is
   * <code>Object</code> rather than <code>String</code>. It exists so that this class can implement the
   * <code>Enumeration</code> interface.
   *
   * @return the next token in the string.
   * @throws java.util.NoSuchElementException
   *          if there are no more tokens in this tokenizer"s string.
   * @see java.util.Enumeration
   * @see org.jfree.report.util.CSVTokenizer#nextToken()
   */
  public Object nextElement()
  {
    return nextToken();
  }
  /**
   * Calculates the number of times that this tokenizer"s <code>nextToken</code> method can be called before it
   * generates an exception. The current position is not advanced.
   *
   * @return the number of tokens remaining in the string using the current delimiter set.
   * @see org.jfree.report.util.CSVTokenizer#nextToken()
   */
  public int countTokens()
  {
    int count = 0;
    final int preserve = this.currentIndex;
    final boolean preserveStart = this.beforeStart;
    while (this.hasMoreTokens())
    {
      this.nextToken();
      count++;
    }
    this.currentIndex = preserve;
    this.beforeStart = preserveStart;
    return count;
  }
  /**
   * Returns the quate.
   *
   * @return char
   */
  public String getQuate()
  {
    return this.quate;
  }
  /**
   * Sets the quate.
   *
   * @param quate The quate to set
   */
  public void setQuate(final String quate)
  {
    this.quate = quate;
  }
}





Writes CSV (Comma Separated Value) files

  
/*------------------------------------------------------------------------------
Name:      CSVWriter.java
Project:   jutils.org
Comment:   writes CSV (Comma Separated Value) files
Version:   $Id: CSVWriter.java,v 1.2 2004/04/07 08:04:24 laurent Exp $
Author:    Roedy Green roedy@mindprod.ru, Heinrich Goetzger goetzger@gmx.net
------------------------------------------------------------------------------*/

import java.io.EOFException;
import java.io.FileWriter;
import java.io.IOException;
import java.io.PrintWriter;
import java.io.Writer;
/**
 * Writes CSV (Comma Separated Value) files.
 *
 * This format is mainly used my Microsoft Word and Excel.
 * Fields are separated by commas, and enclosed in
 * quotes if they contain commas or quotes.
 * Embedded quotes are doubled.
 * Embedded spaces do not normally require surrounding quotes.
 * The last field on the line is not followed by a comma.
 * Null fields are represented by two commas in a row.
 *
 * @author copyright (c) 2002 Roedy Green  Canadian Mind Products
 * Roedy posted this code on Newsgroups:comp.lang.java.programmer on 27th March 2002.
 *
 * Heinrich added some stuff like comment ability and linewise working.
 *
 */
public class CSVWriter {
   /**
    * Constructor
    *
    * @param pw     PrintWriter where fields will be written.
    * @param forceQuotes
    *               true if you want all fields surrounded in quotes,
    *               whether or not they contain commas, quotes or spaces.
    * @param separator
    *               field separator character, usually "," in North America,
    *               ";" in Europe and sometimes "\t" for tab.
    * @param lineSeparator
    *               gives the delimiter for the line; is per default set to
    *               the system property "line.separator"
    */
   public CSVWriter(PrintWriter pw, boolean forceQuotes, char separator, String lineSeparator) {
      this.pw = pw;
      this.forceQuotes = forceQuotes;
      this.separator = separator;
      this.rument = "# ";
      this.lineSeparator = lineSeparator;
   } // end of CSVWriter
   public CSVWriter(Writer w, boolean forceQuotes, char separator, String lineSeparator) {
       this(new PrintWriter(w),forceQuotes,separator,lineSeparator);
   }
   /**
    * Constructor with default field separator ",".
    *
    * @param pw     PrintWriter where fields will be written.
    */
   public CSVWriter(PrintWriter pw) {
      this.pw = pw;
      this.forceQuotes = false;
      this.separator = ",";
      this.rument = "# ";
      this.lineSeparator = System.getProperty("line.separator");
   } // end of CSVWriter
    
   public CSVWriter(Writer w) {
       this(new PrintWriter(w));
   }
   /**
    * Constructor with default field separator ",".
    *
    * @param pw     PrintWriter where fields will be written.
    * @param comment Character used to start a comment line
    */
   public CSVWriter(PrintWriter pw, char comment) {
      this.pw = pw;
      this.forceQuotes = false;
      this.separator = ",";
      this.rument = String.valueOf(comment) + " ";
      this.lineSeparator = System.getProperty("line.separator");
   } // end of CSVWriter
   public CSVWriter(Writer w, char comment) {
       this(new PrintWriter(w),comment);
   }
   /**
    * PrintWriter where CSV fields will be written.
    */
   PrintWriter pw;
   /**
    * true if you want all fields surrounded in quotes,
    * whether or not they contain commas, quotes or spaces.
    */
   boolean forceQuotes;
   /*
    * field separator character, usually "," in North America,
    * ";" in Europe and sometimes "\t" for tab.
    */
   char separator;
   /**
    * true if there has was a field previously written to
    * this line, meaning there is a comma pending to
    * be written.
    */
   boolean wasPreviousField = false;
   /**
    * Character to start a comment line with. May be "#" for example.
    */
   String comment;
   /**
    * Line separator.
    */
   String lineSeparator;
   /**
    * Writes a single coment line to the file given by the <code>text</code>.
    * This is the text leaded by the <code>comment char + " "</code>, given in the constructor.
    * @param text contains the comment text.
    */
   public void writeCommentln(String text) {
      if (wasPreviousField) writeln(); // close open line since we need to start a new one for comment
      pw.print(comment);
      //wasPreviousField = false; // to prevent a comma after the comment sign
      write(text);
      writeln();
   } // end of writeComentln
   /**
    * Writes a single value in a line suited by a newline to the file given by the <code>token</code>.
    * @param token contains the value.
    */
   public void writeln(String token) {
      write(token);
      writeln();
   } // end of writeln
   /**
    * Writes a new line in the CVS output file to demark the end of record.
    */
   public void writeln() {
      /* don"t bother to write last pending comma on the line */
      wasPreviousField = false;
      pw.print(lineSeparator);
   } // end of writeln
   /**
    * Writes a single line of comma separated values from the array given by <code>line</code>.
    * @param line containig an array of tokens.
    */
   public void writeln(String[] line) {
      for(int ii=0; ii < line.length; ii++) {
         write(line[ii]);
      } // end of for
      writeln(); // write newLine
   } // end of writeln
   /**
     * Write one csv field to the file, followed by a separator
     * unless it is the last field on the line. Lead and trailing
     * blanks will be removed.
     *
     * @param s      The string to write.  Any additional quotes or
     *               embedded quotes will be provided by write.
     */
   public void write(String s) {
      if ( wasPreviousField ) {
         pw.print(separator);
      }
      if ( s == null ) {
         pw.print("");
         return;
      } // end of if s == null
      s = s.trim();
      if ( s.indexOf("\"") >= 0 ) {
         /* worst case, needs surrounding quotes and internal quotes doubled */
         pw.print ("\"");
         for ( int i=0; i<s.length(); i++ ) {
            char c = s.charAt(i);
            if ( c == "\"" ) {
               pw.print("\"\"");
            } else {
               pw.print(c);
            }
         }
         pw.print ("\"");
         // end of if \"
      } else if ( s.indexOf("\n") >=0 ) {
         // bad case as well: having a new line in the token: \n
         pw.print ("\"");
         for ( int i=0; i<s.length(); i++ ) {
            char c = s.charAt(i);
            if ( c == "\n" ) {
               pw.print("\\n");
            } else {
               pw.print(c);
            }
         }
         pw.print ("\"");
         // end of if \n
      } else if ( forceQuotes || s.indexOf(separator) >= 0 ) {
         /* need surrounding quotes */
         pw.print ("\"");
         pw.print(s);
         pw.print ("\"");
      } else {
         /* ordinary case, no surrounding quotes needed */
         pw.print(s);
      }
      /* make a note to print trailing comma later */
      wasPreviousField = true;
   } // end of write
   /**
    * Close the PrintWriter.
    */
   public void close() {
      if ( pw != null ) {
         pw.close();
         pw = null;
      } // end of if
   } // end of close
   /**
    * Test driver
    *
    * @param args  [0]: The name of the file.
    */
   static public void main(String[] args) {
      try {
         // write out a test file
         PrintWriter pw = new PrintWriter( new FileWriter(args[0]));
         CSVWriter csv = new CSVWriter(pw, false, ",", System.getProperty("line.separator") );
         csv.writeCommentln("This is a test csv-file: "" + args[0] + """);
         csv.write("abc");
         csv.write("def");
         csv.write("g h i");
         csv.write("jk,l");
         csv.write("m\"n\"o ");
         csv.writeln();
         csv.write("m\"n\"o ");
         csv.write("    ");
         csv.write("a");
         csv.write("x,y,z");
         csv.write("x;y;z");
         csv.writeln();
         csv.writeln(new String[] {"This", "is", "an", "array."});
         csv.close();
      } catch ( IOException  e ) {
         e.printStackTrace();
         System.out.println(e.getMessage());
      }
   } // end main
} // end CSVWriter
// end of file