/*! * This program is free software; you can redistribute it and/or modify it under the * terms of the GNU Lesser General Public License, version 2.1 as published by the Free Software * Foundation. * * You should have received a copy of the GNU Lesser General Public License along with this * program; if not, you can obtain a copy at http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html * or from the Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. * * This program 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. * * Copyright (c) 2002-2013 Pentaho Corporation.. All rights reserved. */ package org.pentaho.reporting.libraries.base.util; import java.io.IOException; import java.io.Writer; /** * The <code>CSVQuoter</code> is a helper class to encode a string for the CSV file format. * * @author Thomas Morgner. */ public final 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; private boolean forceQuote; /** * 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, quate, false ); } public CSVQuoter( final char separator, final char quate, final boolean forceQuoting ) { this.forceQuote = forceQuoting; 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 ( forceQuote || isQuotingNeeded( original ) ) { final StringBuffer retval = new StringBuffer( original.length() + 5 ); // a safe guess most of the time. retval.append( quate ); applyQuote( retval, original ); retval.append( quate ); return retval.toString(); } else { return original; } } /** * A streaming version of the quoting algorithm for more performance. 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. * @param writer the writer. * @throws IOException if an IO error occured. */ public void doQuoting( final String original, final Writer writer ) throws IOException { if ( isQuotingNeeded( original ) ) { writer.write( quate ); applyQuote( writer, original ); writer.write( quate ); } else { writer.write( 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 StringBuilder b = new StringBuilder( 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 ) { final int length = str.length(); for ( int i = 0; i < length; i++ ) { final char c = str.charAt( i ); if ( c == separator ) { return true; } if ( c == '\n' ) { return true; } if ( c == quate ) { 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 ); } } } /** * 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. * @throws IOException if an IO-Error occured. */ private void applyQuote( final Writer b, final String original ) throws IOException { // 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.write( doubleQuate ); } else { b.write( 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; } }