001/* 002 * Licensed to the Apache Software Foundation (ASF) under one or more 003 * contributor license agreements. See the NOTICE file distributed with 004 * this work for additional information regarding copyright ownership. 005 * The ASF licenses this file to You under the Apache License, Version 2.0 006 * (the "License"); you may not use this file except in compliance with 007 * the License. You may obtain a copy of the License at 008 * 009 * https://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017package org.apache.commons.text.translate; 018 019import java.io.IOException; 020import java.io.Writer; 021 022import org.apache.commons.lang3.CharUtils; 023import org.apache.commons.lang3.StringUtils; 024import org.apache.commons.lang3.Strings; 025 026/** 027 * Holds inner classes for escaping/unescaping Comma Separated Values. 028 * <p> 029 * In general the use a high level API like <a href="https://commons.apache.org/proper/commons-csv/">Apache Commons 030 * CSV</a> should be preferred over these low level classes. 031 * </p> 032 * 033 * @see <a href="https://commons.apache.org/proper/commons-csv/apidocs/index.html">Apache Commons CSV</a> 034 */ 035public final class CsvTranslators { 036 037 /** 038 * Translator for escaping Comma Separated Values. 039 */ 040 public static class CsvEscaper extends SinglePassTranslator { 041 042 /** 043 * Construct a new instance. 044 */ 045 public CsvEscaper() { 046 // empty 047 } 048 049 @Override 050 void translateWhole(final CharSequence input, final Writer writer) throws IOException { 051 final String inputString = input.toString(); 052 if (StringUtils.containsNone(inputString, CSV_SEARCH_CHARS)) { 053 writer.write(inputString); 054 } else { 055 // input needs quoting 056 writer.write(CSV_QUOTE); 057 writer.write(Strings.CS.replace(inputString, CSV_QUOTE_STR, CSV_ESCAPED_QUOTE_STR)); 058 writer.write(CSV_QUOTE); 059 } 060 } 061 } 062 /** 063 * Translator for unescaping escaped Comma Separated Value entries. 064 */ 065 public static class CsvUnescaper extends SinglePassTranslator { 066 067 /** 068 * Construct a new instance. 069 */ 070 public CsvUnescaper() { 071 // empty 072 } 073 074 @Override 075 void translateWhole(final CharSequence input, final Writer writer) throws IOException { 076 // is input not quoted? 077 if (input.charAt(0) != CSV_QUOTE || input.charAt(input.length() - 1) != CSV_QUOTE) { 078 writer.write(input.toString()); 079 return; 080 } 081 082 // strip quotes 083 final String quoteless = input.subSequence(1, input.length() - 1).toString(); 084 085 if (StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS)) { 086 // deal with escaped quotes; ie) "" 087 writer.write(Strings.CS.replace(quoteless, CSV_ESCAPED_QUOTE_STR, CSV_QUOTE_STR)); 088 } else { 089 writer.write(quoteless); 090 } 091 } 092 } 093 /** Comma character. */ 094 private static final char CSV_DELIMITER = ','; 095 /** Quote character. */ 096 private static final char CSV_QUOTE = '"'; 097 /** Quote character converted to string. */ 098 private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE); 099 100 /** Escaped quote string. */ 101 private static final String CSV_ESCAPED_QUOTE_STR = CSV_QUOTE_STR + CSV_QUOTE_STR; 102 103 /** CSV key characters in an array. */ 104 private static final char[] CSV_SEARCH_CHARS = { CSV_DELIMITER, CSV_QUOTE, CharUtils.CR, CharUtils.LF }; 105 106 /** Hidden constructor. */ 107 private CsvTranslators() { 108 // empty 109 } 110}