Writing delimited text data to 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.com
*
* 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.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.Writer;
import java.nio.charset.Charset;
/**
* A stream based writer for writing delimited text data to a file or a stream.
*/
public class CsvWriter {
private PrintWriter outputStream = null;
private String fileName = null;
private boolean firstColumn = true;
private boolean useCustomRecordDelimiter = false;
private Charset charset = null;
// this holds all the values for switches that the user is allowed to set
private UserSettings userSettings = new UserSettings();
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.CsvWriter CsvWriter} object using a file
* as the data destination.
*
* @param fileName
* The path to the file to output the data.
* @param delimiter
* The character to use as the column delimiter.
* @param charset
* The {@link java.nio.charset.Charset Charset} to use while
* writing the data.
*/
public CsvWriter(String fileName, char delimiter, Charset charset) {
if (fileName == null) {
throw new IllegalArgumentException("Parameter fileName can not be null.");
}
if (charset == null) {
throw new IllegalArgumentException("Parameter charset can not be null.");
}
this.fileName = fileName;
userSettings.Delimiter = delimiter;
this.charset = charset;
}
/**
* Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a file
* as the data destination. 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 output the data.
*/
public CsvWriter(String fileName) {
this(fileName, Letters.COMMA, Charset.forName("ISO-8859-1"));
}
/**
* Creates a {@link com.csvreader.CsvWriter CsvWriter} object using a Writer
* to write data to.
*
* @param outputStream
* The stream to write the column delimited data to.
* @param delimiter
* The character to use as the column delimiter.
*/
public CsvWriter(Writer outputStream, char delimiter) {
if (outputStream == null) {
throw new IllegalArgumentException("Parameter outputStream can not be null.");
}
this.outputStream = new PrintWriter(outputStream);
userSettings.Delimiter = delimiter;
initialized = true;
}
/**
* Creates a {@link com.csvreader.CsvWriter CsvWriter} object using an
* OutputStream to write data to.
*
* @param outputStream
* The stream to write the column delimited data to.
* @param delimiter
* The character to use as the column delimiter.
* @param charset
* The {@link java.nio.charset.Charset Charset} to use while
* writing the data.
*/
public CsvWriter(OutputStream outputStream, char delimiter, Charset charset) {
this(new OutputStreamWriter(outputStream, charset), delimiter);
}
/**
* Gets the character being used as the column delimiter.
*
* @return The character being used as the column delimiter.
*/
public char getDelimiter() {
return userSettings.Delimiter;
}
/**
* Sets the character to use as the column delimiter.
*
* @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 writing data or not.
*
* @return Whether text qualifiers will be used while writing data or not.
*/
public boolean getUseTextQualifier() {
return userSettings.UseTextQualifier;
}
/**
* Sets whether text qualifiers will be used while writing data or not.
*
* @param useTextQualifier
* Whether to use a text qualifier while writing data or not.
*/
public void setUseTextQualifier(boolean useTextQualifier) {
userSettings.UseTextQualifier = useTextQualifier;
}
public int getEscapeMode() {
return userSettings.EscapeMode;
}
public void setEscapeMode(int escapeMode) {
userSettings.EscapeMode = escapeMode;
}
public void setComment(char comment) {
userSettings.Comment = comment;
}
public char getComment() {
return userSettings.Comment;
}
/**
* Whether fields will be surrounded by the text qualifier even if the
* qualifier is not necessarily needed to escape this field.
*
* @return Whether fields will be forced to be qualified or not.
*/
public boolean getForceQualifier() {
return userSettings.ForceQualifier;
}
/**
* Use this to force all fields to be surrounded by the text qualifier even
* if the qualifier is not necessarily needed to escape this field. Default
* is false.
*
* @param forceQualifier
* Whether to force the fields to be qualified or not.
*/
public void setForceQualifier(boolean forceQualifier) {
userSettings.ForceQualifier = forceQualifier;
}
/**
* Writes another column of data to this record.
*
* @param content
* The data for the new column.
* @param preserveSpaces
* Whether to preserve leading and trailing whitespace in this
* column of data.
* @exception IOException
* Thrown if an error occurs while writing data to the
* destination stream.
*/
public void write(String content, boolean preserveSpaces)
throws IOException {
checkClosed();
checkInit();
if (content == null) {
content = "";
}
if (!firstColumn) {
outputStream.write(userSettings.Delimiter);
}
boolean textQualify = userSettings.ForceQualifier;
if (!preserveSpaces && content.length() > 0) {
content = content.trim();
}
if (!textQualify
&& userSettings.UseTextQualifier
&& (content.indexOf(userSettings.TextQualifier) > -1
|| content.indexOf(userSettings.Delimiter) > -1
|| (!useCustomRecordDelimiter && (content
.indexOf(Letters.LF) > -1 || content
.indexOf(Letters.CR) > -1))
|| (useCustomRecordDelimiter && content
.indexOf(userSettings.RecordDelimiter) > -1)
|| (firstColumn && content.length() > 0 && content
.charAt(0) == userSettings.Comment) ||
// check for empty first column, which if on its own line must
// be qualified or the line will be skipped
(firstColumn && content.length() == 0))) {
textQualify = true;
}
if (userSettings.UseTextQualifier && !textQualify
&& content.length() > 0 && preserveSpaces) {
char firstLetter = content.charAt(0);
if (firstLetter == Letters.SPACE || firstLetter == Letters.TAB) {
textQualify = true;
}
if (!textQualify && content.length() > 1) {
char lastLetter = content.charAt(content.length() - 1);
if (lastLetter == Letters.SPACE || lastLetter == Letters.TAB) {
textQualify = true;
}
}
}
if (textQualify) {
outputStream.write(userSettings.TextQualifier);
if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
content = replace(content, "" + Letters.BACKSLASH, ""
+ Letters.BACKSLASH + Letters.BACKSLASH);
content = replace(content, "" + userSettings.TextQualifier, ""
+ Letters.BACKSLASH + userSettings.TextQualifier);
} else {
content = replace(content, "" + userSettings.TextQualifier, ""
+ userSettings.TextQualifier
+ userSettings.TextQualifier);
}
} else if (userSettings.EscapeMode == ESCAPE_MODE_BACKSLASH) {
content = replace(content, "" + Letters.BACKSLASH, ""
+ Letters.BACKSLASH + Letters.BACKSLASH);
content = replace(content, "" + userSettings.Delimiter, ""
+ Letters.BACKSLASH + userSettings.Delimiter);
if (useCustomRecordDelimiter) {
content = replace(content, "" + userSettings.RecordDelimiter,
"" + Letters.BACKSLASH + userSettings.RecordDelimiter);
} else {
content = replace(content, "" + Letters.CR, ""
+ Letters.BACKSLASH + Letters.CR);
content = replace(content, "" + Letters.LF, ""
+ Letters.BACKSLASH + Letters.LF);
}
if (firstColumn && content.length() > 0
&& content.charAt(0) == userSettings.Comment) {
if (content.length() > 1) {
content = "" + Letters.BACKSLASH + userSettings.Comment
+ content.substring(1);
} else {
content = "" + Letters.BACKSLASH + userSettings.Comment;
}
}
}
outputStream.write(content);
if (textQualify) {
outputStream.write(userSettings.TextQualifier);
}
firstColumn = false;
}
/**
* Writes another column of data to this record. Does not preserve
* leading and trailing whitespace in this column of data.
*
* @param content
* The data for the new column.
* @exception IOException
* Thrown if an error occurs while writing data to the
* destination stream.
*/
public void write(String content) throws IOException {
write(content, false);
}
public void writeComment(String commentText) throws IOException {
checkClosed();
checkInit();
outputStream.write(userSettings.Comment);
outputStream.write(commentText);
if (useCustomRecordDelimiter) {
outputStream.write(userSettings.RecordDelimiter);
} else {
outputStream.println();
}
firstColumn = true;
}
/**
* Writes a new record using the passed in array of values.
*
* @param values
* Values to be written.
*
* @param preserveSpaces
* Whether to preserver leading and trailing spaces in columns
* while writing out to the record or not.
*
* @throws IOException
* Thrown if an error occurs while writing data to the
* destination stream.
*/
public void writeRecord(String[] values, boolean preserveSpaces)
throws IOException {
if (values != null && values.length > 0) {
for (int i = 0; i < values.length; i++) {
write(values[i], preserveSpaces);
}
endRecord();
}
}
/**
* Writes a new record using the passed in array of values.
*
* @param values
* Values to be written.
*
* @throws IOException
* Thrown if an error occurs while writing data to the
* destination stream.
*/
public void writeRecord(String[] values) throws IOException {
writeRecord(values, false);
}
/**
* Ends the current record by sending the record delimiter.
*
* @exception IOException
* Thrown if an error occurs while writing data to the
* destination stream.
*/
public void endRecord() throws IOException {
checkClosed();
checkInit();
if (useCustomRecordDelimiter) {
outputStream.write(userSettings.RecordDelimiter);
} else {
outputStream.println();
}
firstColumn = true;
}
/**
*
*/
private void checkInit() throws IOException {
if (!initialized) {
if (fileName != null) {
outputStream = new PrintWriter(new OutputStreamWriter(
new FileOutputStream(fileName), charset));
}
initialized = true;
}
}
/**
* Clears all buffers for the current writer and causes any buffered data to
* be written to the underlying device.
*/
public void flush() {
outputStream.flush();
}
/**
* 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;
}
try {
if (initialized) {
outputStream.close();
}
} catch (Exception e) {
// just eat the exception
}
outputStream = null;
closed = true;
}
}
/**
*
*/
private void checkClosed() throws IOException {
if (closed) {
throw new IOException(
"This instance of the CsvWriter class has already been closed.");
}
}
/**
*
*/
protected void finalize() {
close(false);
}
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';
}
private class UserSettings {
// having these as publicly accessible members will prevent
// the overhead of the method call that exists on properties
public char TextQualifier;
public boolean UseTextQualifier;
public char Delimiter;
public char RecordDelimiter;
public char Comment;
public int EscapeMode;
public boolean ForceQualifier;
public UserSettings() {
TextQualifier = Letters.QUOTE;
UseTextQualifier = true;
Delimiter = Letters.COMMA;
RecordDelimiter = Letters.NULL;
Comment = Letters.POUND;
EscapeMode = ESCAPE_MODE_DOUBLED;
ForceQualifier = false;
}
}
public static String replace(String original, String pattern, String replace) {
final int len = pattern.length();
int found = original.indexOf(pattern);
if (found > -1) {
StringBuffer sb = new StringBuffer();
int start = 0;
while (found != -1) {
sb.append(original.substring(start, found));
sb.append(replace);
start = found + len;
found = original.indexOf(pattern, start);
}
sb.append(original.substring(start));
return sb.toString();
} else {
return original;
}
}
}
Related examples in the same category