1 /*
2 * Licensed to the Apache Software Foundation (ASF) under one or more
3 * contributor license agreements. See the NOTICE file distributed with
4 * this work for additional information regarding copyright ownership.
5 * The ASF licenses this file to You under the Apache License, Version 2.0
6 * (the "License"); you may not use this file except in compliance with
7 * the License. You may obtain a copy of the License at
8 *
9 * https://www.apache.org/licenses/LICENSE-2.0
10 *
11 * Unless required by applicable law or agreed to in writing, software
12 * distributed under the License is distributed on an "AS IS" BASIS,
13 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 * See the License for the specific language governing permissions and
15 * limitations under the License.
16 */
17 package org.apache.commons.text.translate;
18
19 import java.io.IOException;
20 import java.io.Writer;
21
22 import org.apache.commons.lang3.CharUtils;
23 import org.apache.commons.lang3.StringUtils;
24 import org.apache.commons.lang3.Strings;
25
26 /**
27 * Holds inner classes for escaping/unescaping Comma Separated Values.
28 * <p>
29 * In general the use a high level API like <a href="https://commons.apache.org/proper/commons-csv/">Apache Commons
30 * CSV</a> should be preferred over these low level classes.
31 * </p>
32 *
33 * @see <a href="https://commons.apache.org/proper/commons-csv/apidocs/index.html">Apache Commons CSV</a>
34 */
35 public final class CsvTranslators {
36
37 /**
38 * Translator for escaping Comma Separated Values.
39 */
40 public static class CsvEscaper extends SinglePassTranslator {
41
42 /**
43 * Construct a new instance.
44 */
45 public CsvEscaper() {
46 // empty
47 }
48
49 @Override
50 void translateWhole(final CharSequence input, final Writer writer) throws IOException {
51 final String inputString = input.toString();
52 if (StringUtils.containsNone(inputString, CSV_SEARCH_CHARS)) {
53 writer.write(inputString);
54 } else {
55 // input needs quoting
56 writer.write(CSV_QUOTE);
57 writer.write(Strings.CS.replace(inputString, CSV_QUOTE_STR, CSV_ESCAPED_QUOTE_STR));
58 writer.write(CSV_QUOTE);
59 }
60 }
61 }
62 /**
63 * Translator for unescaping escaped Comma Separated Value entries.
64 */
65 public static class CsvUnescaper extends SinglePassTranslator {
66
67 /**
68 * Construct a new instance.
69 */
70 public CsvUnescaper() {
71 // empty
72 }
73
74 @Override
75 void translateWhole(final CharSequence input, final Writer writer) throws IOException {
76 // is input not quoted?
77 if (input.charAt(0) != CSV_QUOTE || input.charAt(input.length() - 1) != CSV_QUOTE) {
78 writer.write(input.toString());
79 return;
80 }
81
82 // strip quotes
83 final String quoteless = input.subSequence(1, input.length() - 1).toString();
84
85 if (StringUtils.containsAny(quoteless, CSV_SEARCH_CHARS)) {
86 // deal with escaped quotes; ie) ""
87 writer.write(Strings.CS.replace(quoteless, CSV_ESCAPED_QUOTE_STR, CSV_QUOTE_STR));
88 } else {
89 writer.write(quoteless);
90 }
91 }
92 }
93 /** Comma character. */
94 private static final char CSV_DELIMITER = ',';
95 /** Quote character. */
96 private static final char CSV_QUOTE = '"';
97 /** Quote character converted to string. */
98 private static final String CSV_QUOTE_STR = String.valueOf(CSV_QUOTE);
99
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 }