View Javadoc
1   package com.opencsv;
2   
3   import com.opencsv.enums.CSVReaderNullFieldIndicator;
4   import org.apache.commons.lang3.StringUtils;
5   
6   import java.io.IOException;
7   import java.util.regex.Pattern;
8   import java.util.stream.Collectors;
9   import java.util.stream.Stream;
10  
11  /**
12   * The purpose of the AbstractCSVParser is to consolidate the duplicate code amongst the
13   * parsers.
14   */
15  public abstract class AbstractCSVParser implements ICSVParser {
16      /**
17       * This is needed by the split command in case the separator character is a regex special character.
18       */
19      protected static final Pattern SPECIAL_REGEX_CHARS = Pattern.compile("[{}()\\[\\].+*?^$\\\\|]");
20  
21      /**
22       * Empty StringBuilder
23       */
24      protected static final StringBuilder EMPTY_STRINGBUILDER = new StringBuilder("");
25  
26      /**
27       * This is the character that the CSVParser will treat as the separator.
28       */
29      protected final char separator;
30      /**
31       * This is the separator in Stirng form to reduce the number of calls to toString.
32       */
33      protected final String separatorAsString;
34      /**
35       * This is the character that the CSVParser will treat as the quotation character.
36       */
37      protected final char quotechar;
38      /**
39       * This is the quotechar in String form to reduce the number of calls to toString.
40       */
41      protected final String quotecharAsString;
42  
43      /**
44       * This is quotecharAsString+quotecharAsString - used in replaceAll to reduce the number of strings being created.
45       */
46      protected final String quoteDoubledAsString;
47  
48      /**
49       * pattern created to match quotechars - optimizaion of the String.replaceAll.
50       */
51      protected final Pattern quoteMatcherPattern;
52  
53  
54      /**
55       * Determines the handling of null fields.
56       *
57       * @see CSVReaderNullFieldIndicator
58       */
59      protected final CSVReaderNullFieldIndicator nullFieldIndicator;
60  
61      /**
62       * Value to be appended to string to process.
63       */
64      protected String pending;
65  
66      /**
67       * Common constructor.
68       *
69       * @param separator          The delimiter to use for separating entries
70       * @param quotechar          The character to use for quoted elements
71       * @param nullFieldIndicator Indicate what should be considered null
72       */
73      public AbstractCSVParser(char separator, char quotechar, CSVReaderNullFieldIndicator nullFieldIndicator) {
74          this.separator = separator;
75          this.separatorAsString = SPECIAL_REGEX_CHARS.matcher(Character.toString(separator)).replaceAll("\\\\$0");
76  
77          this.quotechar = quotechar;
78          this.quotecharAsString = Character.toString(quotechar);
79          this.quoteDoubledAsString = this.quotecharAsString + this.quotecharAsString;
80          this.quoteMatcherPattern = Pattern.compile(quotecharAsString);
81  
82          this.nullFieldIndicator = nullFieldIndicator;
83      }
84  
85      @Override
86      public char getSeparator() {
87          return separator;
88      }
89  
90      /**
91       * @return String version of separator to reduce number of calls to toString.
92       */
93      public String getSeparatorAsString() {
94          return separatorAsString;
95      }
96  
97      @Override
98      public char getQuotechar() {
99          return quotechar;
100     }
101 
102     /**
103      * @return String version of quotechar to reduce the number of calls to toString.
104      */
105     public String getQuotecharAsString() {
106         return quotecharAsString;
107     }
108 
109     @Override
110     public boolean isPending() {
111         return pending != null;
112     }
113 
114 
115     @Override
116     public String[] parseLineMulti(String nextLine) throws IOException {
117         return parseLine(nextLine, true);
118     }
119 
120     @Override
121     public String[] parseLine(String nextLine) throws IOException {
122         return parseLine(nextLine, false);
123     }
124 
125     @Override
126     public String parseToLine(String[] values, boolean applyQuotesToAll) {
127         return Stream.of(values)
128                 .map(v -> convertToCsvValue(v, applyQuotesToAll))
129                 .collect(Collectors.joining(getSeparatorAsString()));
130     }
131 
132     @Override
133     public void parseToLine(String[] values, boolean applyQuotesToAll, Appendable appendable) throws IOException {
134         boolean first = true;
135         for (String value : values) {
136             if (!first) {
137                 appendable.append(getSeparator());
138             } else {
139                 first = false;
140             }
141             convertToCsvValue(value, applyQuotesToAll, appendable);
142         }
143     }
144 
145     /**
146      * Used when reverse parsing an array of strings to a single string.  Handles the application of quotes around
147      * the string and handling any quotes within the string.
148      *
149      * @param value            String to be converted
150      * @param applyQuotestoAll All values should be surrounded with quotes
151      * @return String that will go into the CSV string
152      */
153     protected abstract String convertToCsvValue(String value, boolean applyQuotestoAll);
154 
155     /**
156      * Used when reverse parsing an array of strings to a single string.  Handles the application of quotes around
157      * the string and handling any quotes within the string.
158      * <p>
159      * NOTE: as of 5.7.2 most objects will be inheriting a solution that calls the existing convertToCsvValue and thus
160      * will not receive much benefit.
161      *
162      * @param value            String to be converted
163      * @param applyQuotesToAll All values should be surrounded with quotes
164      * @param appendable       Appendable object that the converted values are added to.
165      */
166     protected void convertToCsvValue(String value, boolean applyQuotesToAll, Appendable appendable) throws IOException {
167         appendable.append(convertToCsvValue(value, applyQuotesToAll));
168     }
169 
170     /**
171      * Used by reverse parsing to determine if a value should be surrounded by quote characters.
172      *
173      * @param value         String to be tested
174      * @param forceSurround If the value is not {@code null} it will be surrounded with quotes
175      * @return True if the string should be surrounded with quotes, false otherwise
176      */
177     protected boolean isSurroundWithQuotes(String value, boolean forceSurround) {
178         if (value == null) {
179             return nullFieldIndicator.equals(CSVReaderNullFieldIndicator.EMPTY_QUOTES);
180         } else if (value.isEmpty() && nullFieldIndicator.equals(CSVReaderNullFieldIndicator.EMPTY_SEPARATORS)) {
181             return true;
182         }
183 
184         return forceSurround || value.contains(getSeparatorAsString()) || value.contains(NEWLINE);
185     }
186 
187     /**
188      * Parses an incoming {@link java.lang.String} and returns an array of elements.
189      *
190      * @param nextLine The string to parse
191      * @param multi    Whether it takes multiple lines to form a single record
192      * @return The list of elements, or {@code null} if {@code nextLine} is {@code null}
193      * @throws IOException If bad things happen during the read
194      */
195     protected abstract String[] parseLine(String nextLine, boolean multi) throws IOException;
196 
197     @Override
198     public CSVReaderNullFieldIndicator nullFieldIndicator() {
199         return nullFieldIndicator;
200     }
201 
202     @Override
203     public String getPendingText() {
204         return StringUtils.defaultString(pending);
205     }
206 }