/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
package org.apache.commons.csv;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.io.Writer;
/**
* Print values as a comma separated list.
*/
public class CSVPrinter {
/** The place that the values get written. */
protected PrintWriter out;
/** True if we just began a new line. */
protected boolean newLine = true;
private CSVStrategy strategy = CSVStrategy.DEFAULT_STRATEGY;
/**
* Create a printer that will print values to the given
* stream. Character to byte conversion is done using
* the default character encoding. Comments will be
* written using the default comment character '#'.
*
* @param out stream to which to print.
*/
public CSVPrinter(OutputStream out) {
this.out = new PrintWriter(out);
}
/**
* Create a printer that will print values to the given
* stream. Comments will be
* written using the default comment character '#'.
*
* @param out stream to which to print.
*/
public CSVPrinter(Writer out) {
if (out instanceof PrintWriter) {
this.out = (PrintWriter) out;
} else {
this.out = new PrintWriter(out);
}
}
// ======================================================
// strategies
// ======================================================
/**
* Sets the specified CSV Strategy
*
* @return current instance of CSVParser to allow chained method calls
*/
public CSVPrinter setStrategy(CSVStrategy strategy) {
this.strategy = strategy;
return this;
}
/**
* Obtain the specified CSV Strategy
*
* @return strategy currently being used
*/
public CSVStrategy getStrategy() {
return this.strategy;
}
// ======================================================
// printing implementation
// ======================================================
/**
* Print the string as the last value on the line. The value
* will be quoted if needed.
*
* @param value value to be outputted.
*/
public void println(String value) {
print(value);
out.println();
out.flush();
newLine = true;
}
/**
* Output a blank line
*/
public void println() {
out.println();
out.flush();
newLine = true;
}
/**
* Print a single line of comma separated values.
* The values will be quoted if needed. Quotes and
* newLine characters will be escaped.
*
* @param values values to be outputted.
*/
public void println(String[] values) {
for (int i = 0; i < values.length; i++) {
print(values[i]);
}
out.println();
out.flush();
newLine = true;
}
/**
* Print several lines of comma separated values.
* The values will be quoted if needed. Quotes and
* newLine characters will be escaped.
*
* @param values values to be outputted.
*/
public void println(String[][] values) {
for (int i = 0; i < values.length; i++) {
println(values[i]);
}
if (values.length == 0) {
out.println();
}
out.flush();
newLine = true;
}
/**
* Put a comment among the comma separated values.
* Comments will always begin on a new line and occupy a
* least one full line. The character specified to star
* comments and a space will be inserted at the beginning of
* each new line in the comment.
*
* @param comment the comment to output
*/
public void printlnComment(String comment) {
if(this.strategy.isCommentingDisabled()) {
return;
}
if (!newLine) {
out.println();
}
out.print(this.strategy.getCommentStart());
out.print(' ');
for (int i = 0; i < comment.length(); i++) {
char c = comment.charAt(i);
switch (c) {
case '\r' :
if (i + 1 < comment.length() && comment.charAt(i + 1) == '\n') {
i++;
}
// break intentionally excluded.
case '\n' :
out.println();
out.print(this.strategy.getCommentStart());
out.print(' ');
break;
default :
out.print(c);
break;
}
}
out.println();
out.flush();
newLine = true;
}
/**
* Print the string as the next value on the line. The value
* will be quoted if needed.
*
* @param value value to be outputted.
*/
public void print(String value) {
boolean quote = false;
if (value.length() > 0) {
char c = value.charAt(0);
if (newLine
&& (c < '0'
|| (c > '9' && c < 'A')
|| (c > 'Z' && c < 'a')
|| (c > 'z'))) {
quote = true;
}
if (c == ' ' || c == '\f' || c == '\t') {
quote = true;
}
for (int i = 0; i < value.length(); i++) {
c = value.charAt(i);
if (c == '"' || c == this.strategy.getDelimiter() || c == '\n' || c == '\r') {
quote = true;
c = value.charAt( value.length() - 1 );
break;
}
}
if (c == ' ' || c == '\f' || c == '\t') {
quote = true;
}
} else if (newLine) {
// always quote an empty token that is the first
// on the line, as it may be the only thing on the
// line. If it were not quoted in that case,
// an empty line has no tokens.
quote = true;
}
if (newLine) {
newLine = false;
} else {
out.print(this.strategy.getDelimiter());
}
if (quote) {
out.print(escapeAndQuote(value));
} else {
out.print(value);
}
out.flush();
}
/**
* Enclose the value in quotes and escape the quote
* and comma characters that are inside.
*
* @param value needs to be escaped and quoted
* @return the value, escaped and quoted
*/
private String escapeAndQuote(String value) {
// the initial count is for the preceding and trailing quotes
int count = 2;
for (int i = 0; i < value.length(); i++) {
switch (value.charAt(i)) {
case '\"' :
case '\n' :
case '\r' :
case '\\' :
count++;
break;
default:
break;
}
}
StringBuffer sb = new StringBuffer(value.length() + count);
sb.append(strategy.getEncapsulator());
for (int i = 0; i < value.length(); i++) {
char c = value.charAt(i);
if (c == strategy.getEncapsulator()) {
sb.append('\\').append(c);
continue;
}
switch (c) {
case '\n' :
sb.append("\\n");
break;
case '\r' :
sb.append("\\r");
break;
case '\\' :
sb.append("\\\\");
break;
default :
sb.append(c);
}
}
sb.append(strategy.getEncapsulator());
return sb.toString();
}
}