Classes in this File | Line Coverage | Branch Coverage | Complexity | ||||
CSVReader |
|
| 3.5555555555555554;3.556 |
1 | package liquibase.util.csv.opencsv; | |
2 | ||
3 | /** | |
4 | * Copyright 2005 Bytecode Pty Ltd. | |
5 | * | |
6 | * Licensed under the Apache License, Version 2.0 (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 | * http://www.apache.org/licenses/LICENSE-2.0 | |
10 | * | |
11 | * Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on | |
12 | * an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the | |
13 | * specific language governing permissions and limitations under the License. | |
14 | */ | |
15 | ||
16 | import java.io.BufferedReader; | |
17 | import java.io.IOException; | |
18 | import java.io.Reader; | |
19 | import java.util.ArrayList; | |
20 | import java.util.List; | |
21 | ||
22 | /** | |
23 | * A very simple CSV reader released under a commercial-friendly license. | |
24 | * | |
25 | * @author Glen Smith | |
26 | * | |
27 | */ | |
28 | public class CSVReader { | |
29 | ||
30 | private BufferedReader br; | |
31 | ||
32 | 10 | private boolean hasNext = true; |
33 | ||
34 | private char separator; | |
35 | ||
36 | private char quotechar; | |
37 | ||
38 | private int skipLines; | |
39 | ||
40 | private boolean linesSkiped; | |
41 | ||
42 | /** The default separator to use if none is supplied to the constructor. */ | |
43 | public static final char DEFAULT_SEPARATOR = ','; | |
44 | ||
45 | /** | |
46 | * The default quote character to use if none is supplied to the constructor. | |
47 | */ | |
48 | public static final char DEFAULT_QUOTE_CHARACTER = '"'; | |
49 | ||
50 | /** | |
51 | * The default line to start reading. | |
52 | */ | |
53 | public static final int DEFAULT_SKIP_LINES = 0; | |
54 | ||
55 | /** | |
56 | * Constructs CSVReader using a comma for the separator. | |
57 | * | |
58 | * @param reader | |
59 | * the reader to an underlying CSV source. | |
60 | */ | |
61 | public CSVReader(Reader reader) { | |
62 | 0 | this(reader, DEFAULT_SEPARATOR); |
63 | 0 | } |
64 | ||
65 | /** | |
66 | * Constructs CSVReader with supplied separator. | |
67 | * | |
68 | * @param reader | |
69 | * the reader to an underlying CSV source. | |
70 | * @param separator | |
71 | * the delimiter to use for separating entries. | |
72 | */ | |
73 | public CSVReader(Reader reader, char separator) { | |
74 | 0 | this(reader, separator, DEFAULT_QUOTE_CHARACTER); |
75 | 0 | } |
76 | ||
77 | /** | |
78 | * Constructs CSVReader with supplied separator and quote char. | |
79 | * | |
80 | * @param reader | |
81 | * the reader to an underlying CSV source. | |
82 | * @param separator | |
83 | * the delimiter to use for separating entries | |
84 | * @param quotechar | |
85 | * the character to use for quoted elements | |
86 | */ | |
87 | public CSVReader(Reader reader, char separator, char quotechar) { | |
88 | 10 | this(reader, separator, quotechar, DEFAULT_SKIP_LINES); |
89 | 10 | } |
90 | ||
91 | /** | |
92 | * Constructs CSVReader with supplied separator and quote char. | |
93 | * | |
94 | * @param reader | |
95 | * the reader to an underlying CSV source. | |
96 | * @param separator | |
97 | * the delimiter to use for separating entries | |
98 | * @param quotechar | |
99 | * the character to use for quoted elements | |
100 | * @param line | |
101 | * the line number to skip for start reading | |
102 | */ | |
103 | 10 | public CSVReader(Reader reader, char separator, char quotechar, int line) { |
104 | 10 | this.br = new BufferedReader(reader); |
105 | 10 | this.separator = separator; |
106 | 10 | this.quotechar = quotechar; |
107 | 10 | this.skipLines = line; |
108 | 10 | } |
109 | ||
110 | /** | |
111 | * Reads the entire file into a List with each element being a String[] of tokens. | |
112 | * | |
113 | * @return a List of String[], with each String[] representing a line of the file. | |
114 | * | |
115 | * @throws IOException | |
116 | * if bad things happen during the read | |
117 | */ | |
118 | public List readAll() throws IOException { | |
119 | ||
120 | 0 | List allElements = new ArrayList(); |
121 | 0 | while (hasNext) { |
122 | 0 | String[] nextLineAsTokens = readNext(); |
123 | 0 | if (nextLineAsTokens != null) |
124 | 0 | allElements.add(nextLineAsTokens); |
125 | 0 | } |
126 | 0 | return allElements; |
127 | ||
128 | } | |
129 | ||
130 | /** | |
131 | * Reads the next line from the buffer and converts to a string array. | |
132 | * | |
133 | * @return a string array with each comma-separated element as a separate entry. | |
134 | * | |
135 | * @throws IOException | |
136 | * if bad things happen during the read | |
137 | */ | |
138 | public String[] readNext() throws IOException { | |
139 | ||
140 | 40 | String nextLine = getNextLine(); |
141 | 40 | return hasNext ? parseLine(nextLine) : null; |
142 | } | |
143 | ||
144 | /** | |
145 | * Reads the next line from the file. | |
146 | * | |
147 | * @return the next line from the file without trailing newline | |
148 | * @throws IOException | |
149 | * if bad things happen during the read | |
150 | */ | |
151 | private String getNextLine() throws IOException { | |
152 | 40 | if (!this.linesSkiped) { |
153 | 10 | for (int i = 0; i < skipLines; i++) { |
154 | 0 | br.readLine(); |
155 | } | |
156 | 10 | this.linesSkiped = true; |
157 | } | |
158 | 40 | String nextLine = br.readLine(); |
159 | 40 | if (nextLine == null) { |
160 | 10 | hasNext = false; |
161 | } | |
162 | 40 | return hasNext ? nextLine : null; |
163 | } | |
164 | ||
165 | /** | |
166 | * Parses an incoming String and returns an array of elements. | |
167 | * | |
168 | * @param nextLine | |
169 | * the string to parse | |
170 | * @return the comma-tokenized list of elements, or null if nextLine is null | |
171 | * @throws IOException | |
172 | * if bad things happen during the read | |
173 | */ | |
174 | private String[] parseLine(String nextLine) throws IOException { | |
175 | ||
176 | 30 | if (nextLine == null) { |
177 | 0 | return null; |
178 | } | |
179 | ||
180 | 30 | List tokensOnThisLine = new ArrayList(); |
181 | 30 | StringBuffer sb = new StringBuffer(); |
182 | 30 | boolean inQuotes = false; |
183 | do { | |
184 | 30 | if (inQuotes) { |
185 | // continuing a quoted section, reappend newline | |
186 | 0 | sb.append("\n"); |
187 | 0 | nextLine = getNextLine(); |
188 | 0 | if (nextLine == null) |
189 | 0 | break; |
190 | } | |
191 | 522 | for (int i = 0; i < nextLine.length(); i++) { |
192 | ||
193 | 492 | char c = nextLine.charAt(i); |
194 | 492 | if (c == quotechar) { |
195 | // this gets complex... the quote may end a quoted block, or escape another quote. | |
196 | // do a 1-char lookahead: | |
197 | 4 | if (inQuotes // we are in quotes, therefore there can be escaped quotes in here. |
198 | && nextLine.length() > (i + 1) // there is indeed another character to check. | |
199 | && nextLine.charAt(i + 1) == quotechar) { // ..and that char. is a quote also. | |
200 | // we have two quote chars in a row == one quote char, so consume them both and | |
201 | // put one on the token. we do *not* exit the quoted text. | |
202 | 0 | sb.append(nextLine.charAt(i + 1)); |
203 | 0 | i++; |
204 | } else { | |
205 | 4 | inQuotes = !inQuotes; |
206 | // the tricky case of an embedded quote in the middle: a,bc"d"ef,g | |
207 | 4 | if (i > 2 // not on the begining of the line |
208 | && nextLine.charAt(i - 1) != this.separator // not at the begining of an escape sequence | |
209 | && nextLine.length() > (i + 1) && nextLine.charAt(i + 1) != this.separator // not at the | |
210 | // end of an | |
211 | // escape | |
212 | // sequence | |
213 | ) { | |
214 | 0 | sb.append(c); |
215 | } | |
216 | } | |
217 | 488 | } else if (c == separator && !inQuotes) { |
218 | 36 | tokensOnThisLine.add(sb.toString()); |
219 | 36 | sb = new StringBuffer(); // start work on next token |
220 | } else { | |
221 | 452 | sb.append(c); |
222 | } | |
223 | } | |
224 | 30 | } while (inQuotes); |
225 | 30 | tokensOnThisLine.add(sb.toString()); |
226 | 30 | return (String[]) tokensOnThisLine.toArray(new String[0]); |
227 | ||
228 | } | |
229 | ||
230 | /** | |
231 | * Closes the underlying reader. | |
232 | * | |
233 | * @throws IOException | |
234 | * if the close fails | |
235 | */ | |
236 | public void close() throws IOException { | |
237 | 10 | br.close(); |
238 | 10 | } |
239 | ||
240 | } |