All Downloads are FREE. Search and download functionalities are using the official Maven repository.

leap.lang.csv.CSVPrinter Maven / Gradle / Ivy

The newest version!
/*
 * 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 leap.lang.csv;

import static leap.lang.csv.Constants.COMMENT;
import static leap.lang.csv.Constants.CR;
import static leap.lang.csv.Constants.LF;
import static leap.lang.csv.Constants.SP;

import java.io.Closeable;
import java.io.Flushable;
import java.io.IOException;
import java.sql.ResultSet;
import java.sql.SQLException;

import leap.lang.Args;

/**
 * Prints values in a CSV format.
 *
 * @version $Id: CSVPrinter.java 1518802 2013-08-29 20:18:13Z britter $
 */
final class CSVPrinter implements Flushable, Closeable {

    /** The place that the values get written. */
    private final Appendable out;
    private final CSVFormat format;

    /** True if we just began a new record. */
    private boolean newRecord = true;

    /**
     * Creates a printer that will print values to the given stream following the CSVFormat.
     * 

* Currently, only a pure encapsulation format or a pure escaping format is supported. Hybrid formats * (encapsulation and escaping with a different character) are not supported. * * @param out * stream to which to print. Must not be null. * @param format * the CSV format. Must not be null. * @throws IllegalArgumentException * thrown if the parameters of the format are inconsistent or if either out or format are null. */ public CSVPrinter(final Appendable out, final CSVFormat format) { Args.notNull(out, "out"); Args.notNull(format, "format"); this.out = out; this.format = format; this.format.validate(); } // ====================================================== // printing implementation // ====================================================== /** * Outputs the record separator. * * @throws IOException * If an I/O error occurs */ public void println() throws IOException { out.append(format.getRecordSeparator()); newRecord = true; } /** * Flushes the underlying stream. * * @throws IOException * If an I/O error occurs */ public void flush() throws IOException { if (out instanceof Flushable) { ((Flushable) out).flush(); } } /** * Prints a single line of delimiter separated values. The values will be quoted if needed. Quotes and newLine * characters will be escaped. * * @param values * values to output. * @throws IOException * If an I/O error occurs */ public void printRecord(final Object... values) throws IOException { for (final Object value : values) { print(value); } println(); } /** * Prints a single line of delimiter separated values. The values will be quoted if needed. Quotes and newLine * characters will be escaped. * * @param values * values to output. * @throws IOException * If an I/O error occurs */ public void printRecord(final Iterable values) throws IOException { for (final Object value : values) { print(value); } println(); } /** * Prints a comment on a new line among the delimiter separated values. Comments will always begin on a new line * and occupy a least one full line. The character specified to start comments and a space will be inserted at the * beginning of each new line in the comment. *

* If comments are disabled in the current CSV format this method does nothing. * * @param comment * the comment to output * @throws IOException * If an I/O error occurs */ public void printComment(final String comment) throws IOException { if (!format.isCommentingEnabled()) { return; } if (!newRecord) { println(); } out.append(format.getCommentStart().charValue()); out.append(SP); for (int i = 0; i < comment.length(); i++) { final char c = comment.charAt(i); switch (c) { case CR: if (i + 1 < comment.length() && comment.charAt(i + 1) == LF) { i++; } //$FALL-THROUGH$ break intentionally excluded. case LF: println(); out.append(format.getCommentStart().charValue()); out.append(SP); break; default: out.append(c); break; } } println(); } private void print(final Object object, final CharSequence value, final int offset, final int len) throws IOException { if (!newRecord) { out.append(format.getDelimiter()); } if (format.isQuoting()) { // the original object is needed so can check for Number printAndQuote(object, value, offset, len); } else if (format.isEscaping()) { printAndEscape(value, offset, len); } else { out.append(value, offset, offset + len); } newRecord = false; } /* * Note: must only be called if escaping is enabled, otherwise will generate NPE */ private void printAndEscape(final CharSequence value, final int offset, final int len) throws IOException { int start = offset; int pos = offset; final int end = offset + len; final char delim = format.getDelimiter(); final char escape = format.getEscape().charValue(); while (pos < end) { char c = value.charAt(pos); if (c == CR || c == LF || c == delim || c == escape) { // write out segment up until this char if (pos > start) { out.append(value, start, pos); } if (c == LF) { c = 'n'; } else if (c == CR) { c = 'r'; } out.append(escape); out.append(c); start = pos + 1; // start on the current char after this one } pos++; } // write last segment if (pos > start) { out.append(value, start, pos); } } /* * Note: must only be called if quoting is enabled, otherwise will generate NPE */ // the original object is needed so can check for Number private void printAndQuote(final Object object, final CharSequence value, final int offset, final int len) throws IOException { boolean quote = false; int start = offset; int pos = offset; final int end = offset + len; final char delimChar = format.getDelimiter(); final char quoteChar = format.getQuoteChar().charValue(); Quote quotePolicy = format.getQuotePolicy(); if (quotePolicy == null) { quotePolicy = Quote.MINIMAL; } switch (quotePolicy) { case ALL: quote = true; break; case NON_NUMERIC: quote = !(object instanceof Number); break; case NONE: // Use the existing escaping code printAndEscape(value, offset, len); return; case MINIMAL: if (len <= 0) { // 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. if (newRecord) { quote = true; } } else { char c = value.charAt(pos); // Hmmm, where did this rule come from? if (newRecord && (c < '0' || (c > '9' && c < 'A') || (c > 'Z' && c < 'a') || (c > 'z'))) { quote = true; // } else if (c == ' ' || c == '\f' || c == '\t') { } else if (c <= COMMENT) { // Some other chars at the start of a value caused the parser to fail, so for now // encapsulate if we start in anything less than '#'. We are being conservative // by including the default comment char too. quote = true; } else { while (pos < end) { c = value.charAt(pos); if (c == LF || c == CR || c == quoteChar || c == delimChar) { quote = true; break; } pos++; } if (!quote) { pos = end - 1; c = value.charAt(pos); // if (c == ' ' || c == '\f' || c == '\t') { // Some other chars at the end caused the parser to fail, so for now // encapsulate if we end in anything less than ' ' if (c <= SP) { quote = true; } } } } if (!quote) { // no encapsulation needed - write out the original value out.append(value, start, end); return; } break; } if (!quote) { // no encapsulation needed - write out the original value out.append(value, start, end); return; } // we hit something that needed encapsulation out.append(quoteChar); // Pick up where we left off: pos should be positioned on the first character that caused // the need for encapsulation. while (pos < end) { final char c = value.charAt(pos); if (c == quoteChar) { // write out the chunk up until this point // add 1 to the length to write out the encapsulator also out.append(value, start, pos + 1); // put the next starting position on the encapsulator so we will // write it out again with the next string (effectively doubling it) start = pos; } pos++; } // write the last segment out.append(value, start, pos); out.append(quoteChar); } /** * Prints the string as the next value on the line. The value will be escaped or encapsulated as needed. * * @param value * value to be output. * @throws IOException * If an I/O error occurs */ public void print(final Object value) throws IOException { // null values are considered empty String strValue; if (value == null) { final String nullString = format.getNullString(); strValue = nullString == null ? Constants.EMPTY : nullString; } else { strValue = value.toString(); } this.print(value, strValue, 0, strValue.length()); } /** * Prints all the objects in the given array. * * @param values * the values to print. * @throws IOException * If an I/O error occurs */ public void printRecords(final Object[] values) throws IOException { for (final Object value : values) { if (value instanceof Object[]) { this.printRecord((Object[]) value); } else if (value instanceof Iterable) { this.printRecord((Iterable) value); } else { this.printRecord(value); } } } /** * Prints all the objects in the given collection. * * @param values * the values to print. * @throws IOException * If an I/O error occurs */ public void printRecords(final Iterable values) throws IOException { for (final Object value : values) { if (value instanceof Object[]) { this.printRecord((Object[]) value); } else if (value instanceof Iterable) { this.printRecord((Iterable) value); } else { this.printRecord(value); } } } /** * Prints all the objects in the given JDBC result set. * * @param resultSet result set * the values to print. * @throws IOException * If an I/O error occurs * @throws SQLException if a database access error occurs */ public void printRecords(final ResultSet resultSet) throws SQLException, IOException { final int columnCount = resultSet.getMetaData().getColumnCount(); while (resultSet.next()) { for (int i = 1; i <= columnCount; i++) { print(resultSet.getString(i)); } println(); } } public void close() throws IOException { if (out instanceof Closeable) { ((Closeable) out).close(); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy