View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one
3    * or more contributor license agreements.  See the NOTICE file
4    * distributed with this work for additional information
5    * regarding copyright ownership.  The ASF licenses this file
6    * to you under the Apache License, Version 2.0 (the
7    * "License"); you may not use this file except in compliance
8    * with the License.  You may obtain a copy of the License at
9    *
10   *   https://www.apache.org/licenses/LICENSE-2.0
11   *
12   * Unless required by applicable law or agreed to in writing,
13   * software distributed under the License is distributed on an
14   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15   * KIND, either express or implied.  See the License for the
16   * specific language governing permissions and limitations
17   * under the License.
18   */
19  
20  package org.apache.commons.csv;
21  
22  import static org.apache.commons.csv.Constants.CR;
23  import static org.apache.commons.csv.Constants.CRLF;
24  import static org.apache.commons.csv.Constants.LF;
25  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
26  import static org.junit.jupiter.api.Assertions.assertEquals;
27  import static org.junit.jupiter.api.Assertions.assertFalse;
28  import static org.junit.jupiter.api.Assertions.assertInstanceOf;
29  import static org.junit.jupiter.api.Assertions.assertNotNull;
30  import static org.junit.jupiter.api.Assertions.assertNull;
31  import static org.junit.jupiter.api.Assertions.assertThrows;
32  import static org.junit.jupiter.api.Assertions.assertTrue;
33  
34  import java.io.File;
35  import java.io.IOException;
36  import java.io.InputStreamReader;
37  import java.io.PipedReader;
38  import java.io.PipedWriter;
39  import java.io.Reader;
40  import java.io.StringReader;
41  import java.io.StringWriter;
42  import java.io.UncheckedIOException;
43  import java.net.URL;
44  import java.nio.charset.Charset;
45  import java.nio.charset.StandardCharsets;
46  import java.nio.file.Files;
47  import java.nio.file.Path;
48  import java.nio.file.Paths;
49  import java.util.ArrayList;
50  import java.util.Arrays;
51  import java.util.Iterator;
52  import java.util.List;
53  import java.util.Map;
54  import java.util.NoSuchElementException;
55  import java.util.stream.Collectors;
56  import java.util.stream.Stream;
57  
58  import org.apache.commons.io.input.BOMInputStream;
59  import org.apache.commons.io.input.BrokenInputStream;
60  import org.junit.jupiter.api.Assertions;
61  import org.junit.jupiter.api.Disabled;
62  import org.junit.jupiter.api.Test;
63  import org.junit.jupiter.params.ParameterizedTest;
64  import org.junit.jupiter.params.provider.EnumSource;
65  
66  /**
67   * CSVParserTest
68   *
69   * The test are organized in three different sections: The 'setter/getter' section, the lexer section and finally the parser section. In case a test fails, you
70   * should follow a top-down approach for fixing a potential bug (its likely that the parser itself fails if the lexer has problems...).
71   */
72  public class CSVParserTest {
73  
74      private static final CSVFormat EXCEL_WITH_HEADER = CSVFormat.EXCEL.withHeader();
75  
76      private static final Charset UTF_8 = StandardCharsets.UTF_8;
77  
78      private static final String UTF_8_NAME = UTF_8.name();
79  
80      private static final String CSV_INPUT = "a,b,c,d\n" + " a , b , 1 2 \n" + "\"foo baar\", b,\n" +
81              // + " \"foo\n,,\n\"\",,\n\\\"\",d,e\n";
82              "   \"foo\n,,\n\"\",,\n\"\"\",d,e\n"; // changed to use standard CSV escaping
83  
84      private static final String CSV_INPUT_1 = "a,b,c,d";
85  
86      private static final String CSV_INPUT_2 = "a,b,1 2";
87  
88      private static final String[][] RESULT = { { "a", "b", "c", "d" }, { "a", "b", "1 2" }, { "foo baar", "b", "" }, { "foo\n,,\n\",,\n\"", "d", "e" } };
89  
90      // CSV with no header comments
91      private static final String CSV_INPUT_NO_COMMENT = "A,B" + CRLF + "1,2" + CRLF;
92  
93      // CSV with a header comment
94      private static final String CSV_INPUT_HEADER_COMMENT = "# header comment" + CRLF + "A,B" + CRLF + "1,2" + CRLF;
95  
96      // CSV with a single line header and trailer comment
97      private static final String CSV_INPUT_HEADER_TRAILER_COMMENT = "# header comment" + CRLF + "A,B" + CRLF + "1,2" + CRLF + "# comment";
98  
99      // CSV with a multi-line header and trailer comment
100     private static final String CSV_INPUT_MULTILINE_HEADER_TRAILER_COMMENT = "# multi-line" + CRLF + "# header comment" + CRLF + "A,B" + CRLF + "1,2" + CRLF +
101             "# multi-line" + CRLF + "# comment";
102 
103     // Format with auto-detected header
104     private static final CSVFormat FORMAT_AUTO_HEADER = CSVFormat.Builder.create(CSVFormat.DEFAULT).setCommentMarker('#').setHeader().get();
105 
106     // Format with explicit header
107     // @formatter:off
108     private static final CSVFormat FORMAT_EXPLICIT_HEADER = CSVFormat.Builder.create(CSVFormat.DEFAULT)
109             .setSkipHeaderRecord(true)
110             .setCommentMarker('#')
111             .setHeader("A", "B")
112             .get();
113     // @formatter:on
114 
115     // Format with explicit header that does not skip the header line
116     // @formatter:off
117     CSVFormat FORMAT_EXPLICIT_HEADER_NOSKIP = CSVFormat.Builder.create(CSVFormat.DEFAULT)
118             .setCommentMarker('#')
119             .setHeader("A", "B")
120             .get();
121     // @formatter:on
122 
123     @SuppressWarnings("resource") // caller releases
124     private BOMInputStream createBOMInputStream(final String resource) throws IOException {
125         return new BOMInputStream(ClassLoader.getSystemClassLoader().getResource(resource).openStream());
126     }
127 
128     CSVRecord parse(final CSVParser parser, final int failParseRecordNo) throws IOException {
129         if (parser.getRecordNumber() + 1 == failParseRecordNo) {
130             assertThrows(IOException.class, () -> parser.nextRecord());
131             return null;
132         }
133         return parser.nextRecord();
134     }
135 
136     private void parseFully(final CSVParser parser) {
137         parser.forEach(Assertions::assertNotNull);
138     }
139 
140     @Test
141     public void testBackslashEscaping() throws IOException {
142         // To avoid confusion over the need for escaping chars in java code,
143         // We will test with a forward slash as the escape char, and a single
144         // quote as the encapsulator.
145 
146         // @formatter:off
147         final String code = "one,two,three\n" + // 0
148             "'',''\n" + // 1) empty encapsulators
149             "/',/'\n" + // 2) single encapsulators
150             "'/'','/''\n" + // 3) single encapsulators encapsulated via escape
151             "'''',''''\n" + // 4) single encapsulators encapsulated via doubling
152             "/,,/,\n" + // 5) separator escaped
153             "//,//\n" + // 6) escape escaped
154             "'//','//'\n" + // 7) escape escaped in encapsulation
155             "   8   ,   \"quoted \"\" /\" // string\"   \n" + // don't eat spaces
156             "9,   /\n   \n" + // escaped newline
157             "";
158         final String[][] res = {{"one", "two", "three"}, // 0
159             {"", ""}, // 1
160             {"'", "'"}, // 2
161             {"'", "'"}, // 3
162             {"'", "'"}, // 4
163             {",", ","}, // 5
164             {"/", "/"}, // 6
165             {"/", "/"}, // 7
166             {"   8   ", "   \"quoted \"\" /\" / string\"   "}, {"9", "   \n   "} };
167         // @formatter:on
168         final CSVFormat format = CSVFormat.newFormat(',').withQuote('\'').withRecordSeparator(CRLF).withEscape('/').withIgnoreEmptyLines();
169         try (CSVParser parser = CSVParser.parse(code, format)) {
170             final List<CSVRecord> records = parser.getRecords();
171             assertFalse(records.isEmpty());
172             Utils.compare("Records do not match expected result", res, records);
173         }
174     }
175 
176     @Test
177     public void testBackslashEscaping2() throws IOException {
178         // To avoid confusion over the need for escaping chars in java code,
179         // We will test with a forward slash as the escape char, and a single
180         // quote as the encapsulator.
181         // @formatter:off
182         final String code = "" + " , , \n" + // 1)
183             " \t ,  , \n" + // 2)
184             " // , /, , /,\n" + // 3)
185             "";
186         final String[][] res = {{" ", " ", " "}, // 1
187             {" \t ", "  ", " "}, // 2
188             {" / ", " , ", " ,"}, // 3
189         };
190         // @formatter:on
191         final CSVFormat format = CSVFormat.newFormat(',').withRecordSeparator(CRLF).withEscape('/').withIgnoreEmptyLines();
192         try (CSVParser parser = CSVParser.parse(code, format)) {
193             final List<CSVRecord> records = parser.getRecords();
194             assertFalse(records.isEmpty());
195             Utils.compare("", res, records);
196         }
197     }
198 
199     @Test
200     @Disabled
201     public void testBackslashEscapingOld() throws IOException {
202         final String code = "one,two,three\n" + "on\\\"e,two\n" + "on\"e,two\n" + "one,\"tw\\\"o\"\n" + "one,\"t\\,wo\"\n" + "one,two,\"th,ree\"\n" +
203                 "\"a\\\\\"\n" + "a\\,b\n" + "\"a\\\\,b\"";
204         final String[][] res = { { "one", "two", "three" }, { "on\\\"e", "two" }, { "on\"e", "two" }, { "one", "tw\"o" }, { "one", "t\\,wo" }, // backslash in
205                                                                                                                                                // quotes only
206                                                                                                                                                // escapes a
207                                                                                                                                                // delimiter
208                                                                                                                                                // (",")
209                 { "one", "two", "th,ree" }, { "a\\\\" }, // backslash in quotes only escapes a delimiter (",")
210                 { "a\\", "b" }, // a backslash must be returned
211                 { "a\\\\,b" } // backslash in quotes only escapes a delimiter (",")
212         };
213         try (CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
214             final List<CSVRecord> records = parser.getRecords();
215             assertEquals(res.length, records.size());
216             assertFalse(records.isEmpty());
217             for (int i = 0; i < res.length; i++) {
218                 assertArrayEquals(res[i], records.get(i).values());
219             }
220         }
221     }
222 
223     @Test
224     @Disabled("CSV-107")
225     public void testBOM() throws IOException {
226         final URL url = ClassLoader.getSystemClassLoader().getResource("org/apache/commons/csv/CSVFileParser/bom.csv");
227         try (CSVParser parser = CSVParser.parse(url, StandardCharsets.UTF_8, EXCEL_WITH_HEADER)) {
228             parser.forEach(record -> assertNotNull(record.get("Date")));
229         }
230     }
231 
232     @Test
233     public void testBOMInputStreamParserWithInputStream() throws IOException {
234         try (BOMInputStream inputStream = createBOMInputStream("org/apache/commons/csv/CSVFileParser/bom.csv");
235                 CSVParser parser = CSVParser.parse(inputStream, UTF_8, EXCEL_WITH_HEADER)) {
236             parser.forEach(record -> assertNotNull(record.get("Date")));
237         }
238     }
239 
240     @Test
241     public void testBOMInputStreamParserWithReader() throws IOException {
242         try (Reader reader = new InputStreamReader(createBOMInputStream("org/apache/commons/csv/CSVFileParser/bom.csv"), UTF_8_NAME);
243                 CSVParser parser = CSVParser.builder()
244                         .setReader(reader)
245                         .setFormat(EXCEL_WITH_HEADER)
246                         .get()) {
247             parser.forEach(record -> assertNotNull(record.get("Date")));
248         }
249     }
250 
251     @Test
252     public void testBOMInputStreamParseWithReader() throws IOException {
253         try (Reader reader = new InputStreamReader(createBOMInputStream("org/apache/commons/csv/CSVFileParser/bom.csv"), UTF_8_NAME);
254                 CSVParser parser = CSVParser.builder()
255                         .setReader(reader)
256                         .setFormat(EXCEL_WITH_HEADER)
257                         .get()) {
258             parser.forEach(record -> assertNotNull(record.get("Date")));
259         }
260     }
261 
262     @Test
263     public void testCarriageReturnEndings() throws IOException {
264         final String string = "foo\rbaar,\rhello,world\r,kanu";
265         try (CSVParser parser = CSVParser.builder().setCharSequence(string).get()) {
266             final List<CSVRecord> records = parser.getRecords();
267             assertEquals(4, records.size());
268         }
269     }
270 
271     @Test
272     public void testCarriageReturnLineFeedEndings() throws IOException {
273         final String string = "foo\r\nbaar,\r\nhello,world\r\n,kanu";
274         try (CSVParser parser = CSVParser.builder().setCharSequence(string).get()) {
275             final List<CSVRecord> records = parser.getRecords();
276             assertEquals(4, records.size());
277         }
278     }
279 
280     @Test
281     public void testClose() throws Exception {
282         final Reader in = new StringReader("# comment\na,b,c\n1,2,3\nx,y,z");
283         final Iterator<CSVRecord> records;
284         try (CSVParser parser = CSVFormat.DEFAULT.withCommentMarker('#').withHeader().parse(in)) {
285             records = parser.iterator();
286             assertTrue(records.hasNext());
287         }
288         assertFalse(records.hasNext());
289         assertThrows(NoSuchElementException.class, records::next);
290     }
291 
292     @Test
293     public void testCSV141CSVFormat_DEFAULT() throws Exception {
294         testCSV141Failure(CSVFormat.DEFAULT, 3);
295     }
296 
297     @Test
298     public void testCSV141CSVFormat_INFORMIX_UNLOAD() throws Exception {
299         testCSV141Failure(CSVFormat.INFORMIX_UNLOAD, 1);
300     }
301 
302     @Test
303     public void testCSV141CSVFormat_INFORMIX_UNLOAD_CSV() throws Exception {
304         testCSV141Failure(CSVFormat.INFORMIX_UNLOAD_CSV, 3);
305     }
306 
307     @Test
308     public void testCSV141CSVFormat_ORACLE() throws Exception {
309         testCSV141Failure(CSVFormat.ORACLE, 2);
310     }
311 
312     @Test
313     public void testCSV141CSVFormat_POSTGRESQL_CSV() throws Exception {
314         testCSV141Failure(CSVFormat.POSTGRESQL_CSV, 3);
315     }
316 
317     @Test
318     public void testCSV141Excel() throws Exception {
319         testCSV141Ok(CSVFormat.EXCEL);
320     }
321 
322     private void testCSV141Failure(final CSVFormat format, final int failParseRecordNo) throws IOException {
323         final Path path = Paths.get("src/test/resources/org/apache/commons/csv/CSV-141/csv-141.csv");
324         try (CSVParser parser = CSVParser.parse(path, StandardCharsets.UTF_8, format)) {
325             // row 1
326             CSVRecord record = parse(parser, failParseRecordNo);
327             if (record == null) {
328                 return; // expected failure
329             }
330             assertEquals("1414770317901", record.get(0));
331             assertEquals("android.widget.EditText", record.get(1));
332             assertEquals("pass sem1 _84*|*", record.get(2));
333             assertEquals("0", record.get(3));
334             assertEquals("pass sem1 _8", record.get(4));
335             assertEquals(5, record.size());
336             // row 2
337             record = parse(parser, failParseRecordNo);
338             if (record == null) {
339                 return; // expected failure
340             }
341             assertEquals("1414770318470", record.get(0));
342             assertEquals("android.widget.EditText", record.get(1));
343             assertEquals("pass sem1 _84:|", record.get(2));
344             assertEquals("0", record.get(3));
345             assertEquals("pass sem1 _84:\\", record.get(4));
346             assertEquals(5, record.size());
347             // row 3: Fail for certain
348             assertThrows(IOException.class, () -> parser.nextRecord());
349         }
350     }
351 
352     private void testCSV141Ok(final CSVFormat format) throws IOException {
353         final Path path = Paths.get("src/test/resources/org/apache/commons/csv/CSV-141/csv-141.csv");
354         try (CSVParser parser = CSVParser.parse(path, StandardCharsets.UTF_8, format)) {
355             // row 1
356             CSVRecord record = parser.nextRecord();
357             assertEquals("1414770317901", record.get(0));
358             assertEquals("android.widget.EditText", record.get(1));
359             assertEquals("pass sem1 _84*|*", record.get(2));
360             assertEquals("0", record.get(3));
361             assertEquals("pass sem1 _8", record.get(4));
362             assertEquals(5, record.size());
363             // row 2
364             record = parser.nextRecord();
365             assertEquals("1414770318470", record.get(0));
366             assertEquals("android.widget.EditText", record.get(1));
367             assertEquals("pass sem1 _84:|", record.get(2));
368             assertEquals("0", record.get(3));
369             assertEquals("pass sem1 _84:\\", record.get(4));
370             assertEquals(5, record.size());
371             // row 3
372             record = parser.nextRecord();
373             assertEquals("1414770318327", record.get(0));
374             assertEquals("android.widget.EditText", record.get(1));
375             assertEquals("pass sem1\n1414770318628\"", record.get(2));
376             assertEquals("android.widget.EditText", record.get(3));
377             assertEquals("pass sem1 _84*|*", record.get(4));
378             assertEquals("0", record.get(5));
379             assertEquals("pass sem1\n", record.get(6));
380             assertEquals(7, record.size());
381             // EOF
382             record = parser.nextRecord();
383             assertNull(record);
384         }
385     }
386 
387     @Test
388     public void testCSV141RFC4180() throws Exception {
389         testCSV141Failure(CSVFormat.RFC4180, 3);
390     }
391 
392     @Test
393     public void testCSV235() throws IOException {
394         final String dqString = "\"aaa\",\"b\"\"bb\",\"ccc\""; // "aaa","b""bb","ccc"
395         try (CSVParser parser = CSVFormat.RFC4180.parse(new StringReader(dqString))) {
396             final Iterator<CSVRecord> records = parser.iterator();
397             final CSVRecord record = records.next();
398             assertFalse(records.hasNext());
399             assertEquals(3, record.size());
400             assertEquals("aaa", record.get(0));
401             assertEquals("b\"bb", record.get(1));
402             assertEquals("ccc", record.get(2));
403         }
404     }
405 
406     @Test
407     public void testCSV57() throws Exception {
408         try (CSVParser parser = CSVParser.parse("", CSVFormat.DEFAULT)) {
409             final List<CSVRecord> list = parser.getRecords();
410             assertNotNull(list);
411             assertEquals(0, list.size());
412         }
413     }
414 
415     @Test
416     public void testDefaultFormat() throws IOException {
417         // @formatter:off
418         final String code = "" + "a,b#\n" + // 1)
419             "\"\n\",\" \",#\n" +            // 2)
420             "#,\"\"\n" +                    // 3)
421             "# Final comment\n"             // 4)
422         ;
423         // @formatter:on
424         final String[][] res = { { "a", "b#" }, { "\n", " ", "#" }, { "#", "" }, { "# Final comment" } };
425         CSVFormat format = CSVFormat.DEFAULT;
426         assertFalse(format.isCommentMarkerSet());
427         final String[][] resComments = { { "a", "b#" }, { "\n", " ", "#" } };
428         try (CSVParser parser = CSVParser.parse(code, format)) {
429             final List<CSVRecord> records = parser.getRecords();
430             assertFalse(records.isEmpty());
431             Utils.compare("Failed to parse without comments", res, records);
432             format = CSVFormat.DEFAULT.withCommentMarker('#');
433         }
434         try (CSVParser parser = CSVParser.parse(code, format)) {
435             final List<CSVRecord> records = parser.getRecords();
436             Utils.compare("Failed to parse with comments", resComments, records);
437         }
438     }
439 
440     @Test
441     public void testDuplicateHeadersAllowedByDefault() throws Exception {
442         try (CSVParser parser = CSVParser.parse("a,b,a\n1,2,3\nx,y,z", CSVFormat.DEFAULT.withHeader())) {
443             // noop
444         }
445     }
446 
447     @Test
448     public void testDuplicateHeadersNotAllowed() {
449         assertThrows(IllegalArgumentException.class,
450                 () -> CSVParser.parse("a,b,a\n1,2,3\nx,y,z", CSVFormat.DEFAULT.withHeader().withAllowDuplicateHeaderNames(false)));
451     }
452 
453     @Test
454     public void testEmptyFile() throws Exception {
455         try (CSVParser parser = CSVParser.parse(Paths.get("src/test/resources/org/apache/commons/csv/empty.txt"), StandardCharsets.UTF_8,
456                 CSVFormat.DEFAULT)) {
457             assertNull(parser.nextRecord());
458         }
459     }
460 
461     @Test
462     public void testEmptyFileHeaderParsing() throws Exception {
463         try (CSVParser parser = CSVParser.parse("", CSVFormat.DEFAULT.withFirstRecordAsHeader())) {
464             assertNull(parser.nextRecord());
465             assertTrue(parser.getHeaderNames().isEmpty());
466         }
467     }
468 
469     @Test
470     public void testEmptyLineBehaviorCSV() throws Exception {
471         final String[] codes = { "hello,\r\n\r\n\r\n", "hello,\n\n\n", "hello,\"\"\r\n\r\n\r\n", "hello,\"\"\n\n\n" };
472         final String[][] res = { { "hello", "" } // CSV format ignores empty lines
473         };
474         for (final String code : codes) {
475             try (CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
476                 final List<CSVRecord> records = parser.getRecords();
477                 assertEquals(res.length, records.size());
478                 assertFalse(records.isEmpty());
479                 for (int i = 0; i < res.length; i++) {
480                     assertArrayEquals(res[i], records.get(i).values());
481                 }
482             }
483         }
484     }
485 
486     @Test
487     public void testEmptyLineBehaviorExcel() throws Exception {
488         final String[] codes = { "hello,\r\n\r\n\r\n", "hello,\n\n\n", "hello,\"\"\r\n\r\n\r\n", "hello,\"\"\n\n\n" };
489         final String[][] res = { { "hello", "" }, { "" }, // Excel format does not ignore empty lines
490                 { "" } };
491         for (final String code : codes) {
492             try (CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
493                 final List<CSVRecord> records = parser.getRecords();
494                 assertEquals(res.length, records.size());
495                 assertFalse(records.isEmpty());
496                 for (int i = 0; i < res.length; i++) {
497                     assertArrayEquals(res[i], records.get(i).values());
498                 }
499             }
500         }
501     }
502 
503     @Test
504     public void testEmptyString() throws Exception {
505         try (CSVParser parser = CSVParser.parse("", CSVFormat.DEFAULT)) {
506             assertNull(parser.nextRecord());
507         }
508     }
509 
510     @Test
511     public void testEndOfFileBehaviorCSV() throws Exception {
512         final String[] codes = { "hello,\r\n\r\nworld,\r\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\r\n", "hello,\r\n\r\nworld,\"\"",
513                 "hello,\r\n\r\nworld,\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\n", "hello,\r\n\r\nworld,\"\"" };
514         final String[][] res = { { "hello", "" }, // CSV format ignores empty lines
515                 { "world", "" } };
516         for (final String code : codes) {
517             try (CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
518                 final List<CSVRecord> records = parser.getRecords();
519                 assertEquals(res.length, records.size());
520                 assertFalse(records.isEmpty());
521                 for (int i = 0; i < res.length; i++) {
522                     assertArrayEquals(res[i], records.get(i).values());
523                 }
524             }
525         }
526     }
527 
528     @Test
529     public void testEndOfFileBehaviorExcel() throws Exception {
530         final String[] codes = { "hello,\r\n\r\nworld,\r\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\r\n", "hello,\r\n\r\nworld,\"\"",
531                 "hello,\r\n\r\nworld,\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\n", "hello,\r\n\r\nworld,\"\"" };
532         final String[][] res = { { "hello", "" }, { "" }, // Excel format does not ignore empty lines
533                 { "world", "" } };
534 
535         for (final String code : codes) {
536             try (CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
537                 final List<CSVRecord> records = parser.getRecords();
538                 assertEquals(res.length, records.size());
539                 assertFalse(records.isEmpty());
540                 for (int i = 0; i < res.length; i++) {
541                     assertArrayEquals(res[i], records.get(i).values());
542                 }
543             }
544         }
545     }
546 
547     @Test
548     public void testExcelFormat1() throws IOException {
549         final String code = "value1,value2,value3,value4\r\na,b,c,d\r\n  x,,," + "\r\n\r\n\"\"\"hello\"\"\",\"  \"\"world\"\"\",\"abc\ndef\",\r\n";
550         final String[][] res = { { "value1", "value2", "value3", "value4" }, { "a", "b", "c", "d" }, { "  x", "", "", "" }, { "" },
551                 { "\"hello\"", "  \"world\"", "abc\ndef", "" } };
552         try (CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
553             final List<CSVRecord> records = parser.getRecords();
554             assertEquals(res.length, records.size());
555             assertFalse(records.isEmpty());
556             for (int i = 0; i < res.length; i++) {
557                 assertArrayEquals(res[i], records.get(i).values());
558             }
559         }
560     }
561 
562     @Test
563     public void testExcelFormat2() throws Exception {
564         final String code = "foo,baar\r\n\r\nhello,\r\n\r\nworld,\r\n";
565         final String[][] res = { { "foo", "baar" }, { "" }, { "hello", "" }, { "" }, { "world", "" } };
566         try (CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
567             final List<CSVRecord> records = parser.getRecords();
568             assertEquals(res.length, records.size());
569             assertFalse(records.isEmpty());
570             for (int i = 0; i < res.length; i++) {
571                 assertArrayEquals(res[i], records.get(i).values());
572             }
573         }
574     }
575 
576     /**
577      * Tests an exported Excel worksheet with a header row and rows that have more columns than the headers
578      */
579     @Test
580     public void testExcelHeaderCountLessThanData() throws Exception {
581         final String code = "A,B,C,,\r\na,b,c,d,e\r\n";
582         try (CSVParser parser = CSVParser.parse(code, EXCEL_WITH_HEADER)) {
583             parser.getRecords().forEach(record -> {
584                 assertEquals("a", record.get("A"));
585                 assertEquals("b", record.get("B"));
586                 assertEquals("c", record.get("C"));
587             });
588         }
589     }
590 
591     @Test
592     public void testFirstEndOfLineCr() throws IOException {
593         final String data = "foo\rbaar,\rhello,world\r,kanu";
594         try (CSVParser parser = CSVParser.parse(data, CSVFormat.DEFAULT)) {
595             final List<CSVRecord> records = parser.getRecords();
596             assertEquals(4, records.size());
597             assertEquals("\r", parser.getFirstEndOfLine());
598         }
599     }
600 
601     @Test
602     public void testFirstEndOfLineCrLf() throws IOException {
603         final String data = "foo\r\nbaar,\r\nhello,world\r\n,kanu";
604         try (CSVParser parser = CSVParser.parse(data, CSVFormat.DEFAULT)) {
605             final List<CSVRecord> records = parser.getRecords();
606             assertEquals(4, records.size());
607             assertEquals("\r\n", parser.getFirstEndOfLine());
608         }
609     }
610 
611     @Test
612     public void testFirstEndOfLineLf() throws IOException {
613         final String data = "foo\nbaar,\nhello,world\n,kanu";
614         try (CSVParser parser = CSVParser.parse(data, CSVFormat.DEFAULT)) {
615             final List<CSVRecord> records = parser.getRecords();
616             assertEquals(4, records.size());
617             assertEquals("\n", parser.getFirstEndOfLine());
618         }
619     }
620 
621     @Test
622     public void testForEach() throws Exception {
623         try (Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
624                 CSVParser parser = CSVFormat.DEFAULT.parse(in)) {
625             final List<CSVRecord> records = new ArrayList<>();
626             for (final CSVRecord record : parser) {
627                 records.add(record);
628             }
629             assertEquals(3, records.size());
630             assertArrayEquals(new String[] { "a", "b", "c" }, records.get(0).values());
631             assertArrayEquals(new String[] { "1", "2", "3" }, records.get(1).values());
632             assertArrayEquals(new String[] { "x", "y", "z" }, records.get(2).values());
633         }
634     }
635 
636     @Test
637     public void testGetHeaderComment_HeaderComment1() throws IOException {
638         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_COMMENT, FORMAT_AUTO_HEADER)) {
639             parser.getRecords();
640             // Expect a header comment
641             assertTrue(parser.hasHeaderComment());
642             assertEquals("header comment", parser.getHeaderComment());
643         }
644     }
645 
646     @Test
647     public void testGetHeaderComment_HeaderComment2() throws IOException {
648         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_COMMENT, FORMAT_EXPLICIT_HEADER)) {
649             parser.getRecords();
650             // Expect a header comment
651             assertTrue(parser.hasHeaderComment());
652             assertEquals("header comment", parser.getHeaderComment());
653         }
654     }
655 
656     @Test
657     public void testGetHeaderComment_HeaderComment3() throws IOException {
658         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_COMMENT, FORMAT_EXPLICIT_HEADER_NOSKIP)) {
659             parser.getRecords();
660             // Expect no header comment - the text "comment" is attached to the first record
661             assertFalse(parser.hasHeaderComment());
662             assertNull(parser.getHeaderComment());
663         }
664     }
665 
666     @Test
667     public void testGetHeaderComment_HeaderTrailerComment() throws IOException {
668         try (CSVParser parser = CSVParser.parse(CSV_INPUT_MULTILINE_HEADER_TRAILER_COMMENT, FORMAT_AUTO_HEADER)) {
669             parser.getRecords();
670             // Expect a header comment
671             assertTrue(parser.hasHeaderComment());
672             assertEquals("multi-line" + LF + "header comment", parser.getHeaderComment());
673         }
674     }
675 
676     @Test
677     public void testGetHeaderComment_NoComment1() throws IOException {
678         try (CSVParser parser = CSVParser.parse(CSV_INPUT_NO_COMMENT, FORMAT_AUTO_HEADER)) {
679             parser.getRecords();
680             // Expect no header comment
681             assertFalse(parser.hasHeaderComment());
682             assertNull(parser.getHeaderComment());
683         }
684     }
685 
686     @Test
687     public void testGetHeaderComment_NoComment2() throws IOException {
688         try (CSVParser parser = CSVParser.parse(CSV_INPUT_NO_COMMENT, FORMAT_EXPLICIT_HEADER)) {
689             parser.getRecords();
690             // Expect no header comment
691             assertFalse(parser.hasHeaderComment());
692             assertNull(parser.getHeaderComment());
693         }
694     }
695 
696     @Test
697     public void testGetHeaderComment_NoComment3() throws IOException {
698         try (CSVParser parser = CSVParser.parse(CSV_INPUT_NO_COMMENT, FORMAT_EXPLICIT_HEADER_NOSKIP)) {
699             parser.getRecords();
700             // Expect no header comment
701             assertFalse(parser.hasHeaderComment());
702             assertNull(parser.getHeaderComment());
703         }
704     }
705 
706     @Test
707     public void testGetHeaderMap() throws Exception {
708         try (CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z", CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
709             final Map<String, Integer> headerMap = parser.getHeaderMap();
710             final Iterator<String> columnNames = headerMap.keySet().iterator();
711             // Headers are iterated in column order.
712             assertEquals("A", columnNames.next());
713             assertEquals("B", columnNames.next());
714             assertEquals("C", columnNames.next());
715             final Iterator<CSVRecord> records = parser.iterator();
716 
717             // Parse to make sure getHeaderMap did not have a side-effect.
718             for (int i = 0; i < 3; i++) {
719                 assertTrue(records.hasNext());
720                 final CSVRecord record = records.next();
721                 assertEquals(record.get(0), record.get("A"));
722                 assertEquals(record.get(1), record.get("B"));
723                 assertEquals(record.get(2), record.get("C"));
724             }
725 
726             assertFalse(records.hasNext());
727         }
728     }
729 
730     @Test
731     public void testGetHeaderNames() throws IOException {
732         try (CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z", CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
733             final Map<String, Integer> nameIndexMap = parser.getHeaderMap();
734             final List<String> headerNames = parser.getHeaderNames();
735             assertNotNull(headerNames);
736             assertEquals(nameIndexMap.size(), headerNames.size());
737             for (int i = 0; i < headerNames.size(); i++) {
738                 final String name = headerNames.get(i);
739                 assertEquals(i, nameIndexMap.get(name).intValue());
740             }
741         }
742     }
743 
744     @Test
745     public void testGetHeaderNamesReadOnly() throws IOException {
746         try (CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z", CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
747             final List<String> headerNames = parser.getHeaderNames();
748             assertNotNull(headerNames);
749             assertThrows(UnsupportedOperationException.class, () -> headerNames.add("This is a read-only list."));
750         }
751     }
752 
753     @Test
754     public void testGetLine() throws IOException {
755         try (CSVParser parser = CSVParser.parse(CSV_INPUT, CSVFormat.DEFAULT.withIgnoreSurroundingSpaces())) {
756             for (final String[] re : RESULT) {
757                 assertArrayEquals(re, parser.nextRecord().values());
758             }
759 
760             assertNull(parser.nextRecord());
761         }
762     }
763 
764     @Test
765     public void testGetLineNumberWithCR() throws Exception {
766         validateLineNumbers(String.valueOf(CR));
767     }
768 
769     @Test
770     public void testGetLineNumberWithCRLF() throws Exception {
771         validateLineNumbers(CRLF);
772     }
773 
774     @Test
775     public void testGetLineNumberWithLF() throws Exception {
776         validateLineNumbers(String.valueOf(LF));
777     }
778 
779     @Test
780     public void testGetOneLine() throws IOException {
781         try (CSVParser parser = CSVParser.parse(CSV_INPUT_1, CSVFormat.DEFAULT)) {
782             final CSVRecord record = parser.getRecords().get(0);
783             assertArrayEquals(RESULT[0], record.values());
784         }
785     }
786 
787     /**
788      * Tests reusing a parser to process new string records one at a time as they are being discovered. See [CSV-110].
789      *
790      * @throws IOException when an I/O error occurs.
791      */
792     @Test
793     public void testGetOneLineOneParser() throws IOException {
794         final CSVFormat format = CSVFormat.DEFAULT;
795         try (PipedWriter writer = new PipedWriter();
796                 CSVParser parser = CSVParser.builder()
797                         .setReader(new PipedReader(writer))
798                         .setFormat(format)
799                         .get()) {
800             writer.append(CSV_INPUT_1);
801             writer.append(format.getRecordSeparator());
802             final CSVRecord record1 = parser.nextRecord();
803             assertArrayEquals(RESULT[0], record1.values());
804             writer.append(CSV_INPUT_2);
805             writer.append(format.getRecordSeparator());
806             final CSVRecord record2 = parser.nextRecord();
807             assertArrayEquals(RESULT[1], record2.values());
808         }
809     }
810 
811     @Test
812     public void testGetRecordFourBytesRead() throws Exception {
813         final String code = "id,a,b,c\n" +
814             "1,😊,🤔,😂\n" +
815             "2,😊,🤔,😂\n" +
816             "3,😊,🤔,😂\n";
817         final CSVFormat format = CSVFormat.Builder.create()
818             .setDelimiter(',')
819             .setQuote('\'')
820             .get();
821         try (CSVParser parser = CSVParser.builder().setReader(new StringReader(code)).setFormat(format).setCharset(UTF_8).setTrackBytes(true).get()) {
822             CSVRecord record = new CSVRecord(parser, null, null, 1L, 0L, 0L);
823 
824             assertEquals(0, parser.getRecordNumber());
825             assertNotNull(record = parser.nextRecord());
826             assertEquals(1, record.getRecordNumber());
827             assertEquals(code.indexOf('i'), record.getCharacterPosition());
828             assertEquals(record.getBytePosition(), record.getCharacterPosition());
829 
830             assertNotNull(record = parser.nextRecord());
831             assertEquals(2, record.getRecordNumber());
832             assertEquals(code.indexOf('1'), record.getCharacterPosition());
833             assertEquals(record.getBytePosition(), record.getCharacterPosition());
834             assertNotNull(record = parser.nextRecord());
835             assertEquals(3, record.getRecordNumber());
836             assertEquals(code.indexOf('2'), record.getCharacterPosition());
837             assertEquals(record.getBytePosition(), 26);
838             assertNotNull(record = parser.nextRecord());
839             assertEquals(4, record.getRecordNumber());
840             assertEquals(code.indexOf('3'), record.getCharacterPosition());
841             assertEquals(record.getBytePosition(), 43);
842         }
843     }
844 
845     @Test
846     public void testGetRecordNumberWithCR() throws Exception {
847         validateRecordNumbers(String.valueOf(CR));
848     }
849 
850     @Test
851     public void testGetRecordNumberWithCRLF() throws Exception {
852         validateRecordNumbers(CRLF);
853     }
854 
855     @Test
856     public void testGetRecordNumberWithLF() throws Exception {
857         validateRecordNumbers(String.valueOf(LF));
858     }
859 
860     @Test
861     public void testGetRecordPositionWithCRLF() throws Exception {
862         validateRecordPosition(CRLF);
863     }
864 
865     @Test
866     public void testGetRecordPositionWithLF() throws Exception {
867         validateRecordPosition(String.valueOf(LF));
868     }
869 
870     @Test
871     public void testGetRecords() throws IOException {
872         try (CSVParser parser = CSVParser.parse(CSV_INPUT, CSVFormat.DEFAULT.withIgnoreSurroundingSpaces())) {
873             final List<CSVRecord> records = parser.getRecords();
874             assertEquals(RESULT.length, records.size());
875             assertFalse(records.isEmpty());
876             for (int i = 0; i < RESULT.length; i++) {
877                 assertArrayEquals(RESULT[i], records.get(i).values());
878             }
879         }
880     }
881 
882     @Test
883     public void testGetRecordsFromBrokenInputStream() throws IOException {
884         @SuppressWarnings("resource") // We also get an exception on close, which is OK but can't assert in a try.
885         final CSVParser parser = CSVParser.parse(new BrokenInputStream(), UTF_8, CSVFormat.DEFAULT);
886         assertThrows(UncheckedIOException.class, parser::getRecords);
887 
888     }
889 
890     @Test
891     public void testGetRecordThreeBytesRead() throws Exception {
892         final String code = "id,date,val5,val4\n" +
893             "11111111111111,'4017-09-01',きちんと節分近くには咲いてる~,v4\n" +
894             "22222222222222,'4017-01-01',おはよう私の友人~,v4\n" +
895             "33333333333333,'4017-01-01',きる自然の力ってすごいな~,v4\n";
896         final CSVFormat format = CSVFormat.Builder.create()
897             .setDelimiter(',')
898             .setQuote('\'')
899             .get();
900         try (CSVParser parser = CSVParser.builder().setReader(new StringReader(code)).setFormat(format).setCharset(UTF_8).setTrackBytes(true).get()) {
901             CSVRecord record = new CSVRecord(parser, null, null, 1L, 0L, 0L);
902 
903             assertEquals(0, parser.getRecordNumber());
904             assertNotNull(record = parser.nextRecord());
905             assertEquals(1, record.getRecordNumber());
906             assertEquals(code.indexOf('i'), record.getCharacterPosition());
907             assertEquals(record.getBytePosition(), record.getCharacterPosition());
908 
909             assertNotNull(record = parser.nextRecord());
910             assertEquals(2, record.getRecordNumber());
911             assertEquals(code.indexOf('1'), record.getCharacterPosition());
912             assertEquals(record.getBytePosition(), record.getCharacterPosition());
913 
914             assertNotNull(record = parser.nextRecord());
915             assertEquals(3, record.getRecordNumber());
916             assertEquals(code.indexOf('2'), record.getCharacterPosition());
917             assertEquals(record.getBytePosition(), 95);
918 
919             assertNotNull(record = parser.nextRecord());
920             assertEquals(4, record.getRecordNumber());
921             assertEquals(code.indexOf('3'), record.getCharacterPosition());
922             assertEquals(record.getBytePosition(), 154);
923         }
924     }
925 
926     @Test
927     public void testGetRecordWithMultiLineValues() throws Exception {
928         try (CSVParser parser = CSVParser.parse("\"a\r\n1\",\"a\r\n2\"" + CRLF + "\"b\r\n1\",\"b\r\n2\"" + CRLF + "\"c\r\n1\",\"c\r\n2\"",
929                 CSVFormat.DEFAULT.withRecordSeparator(CRLF))) {
930             CSVRecord record;
931             assertEquals(0, parser.getRecordNumber());
932             assertEquals(0, parser.getCurrentLineNumber());
933             assertNotNull(record = parser.nextRecord());
934             assertEquals(3, parser.getCurrentLineNumber());
935             assertEquals(1, record.getRecordNumber());
936             assertEquals(1, parser.getRecordNumber());
937             assertNotNull(record = parser.nextRecord());
938             assertEquals(6, parser.getCurrentLineNumber());
939             assertEquals(2, record.getRecordNumber());
940             assertEquals(2, parser.getRecordNumber());
941             assertNotNull(record = parser.nextRecord());
942             assertEquals(9, parser.getCurrentLineNumber());
943             assertEquals(3, record.getRecordNumber());
944             assertEquals(3, parser.getRecordNumber());
945             assertNull(record = parser.nextRecord());
946             assertEquals(9, parser.getCurrentLineNumber());
947             assertEquals(3, parser.getRecordNumber());
948         }
949     }
950 
951     @Test
952     public void testGetTrailerComment_HeaderComment1() throws IOException {
953         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_COMMENT, FORMAT_AUTO_HEADER)) {
954             parser.getRecords();
955             assertFalse(parser.hasTrailerComment());
956             assertNull(parser.getTrailerComment());
957         }
958     }
959 
960     @Test
961     public void testGetTrailerComment_HeaderComment2() throws IOException {
962         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_COMMENT, FORMAT_EXPLICIT_HEADER)) {
963             parser.getRecords();
964             assertFalse(parser.hasTrailerComment());
965             assertNull(parser.getTrailerComment());
966         }
967     }
968 
969     @Test
970     public void testGetTrailerComment_HeaderComment3() throws IOException {
971         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_COMMENT, FORMAT_EXPLICIT_HEADER_NOSKIP)) {
972             parser.getRecords();
973             assertFalse(parser.hasTrailerComment());
974             assertNull(parser.getTrailerComment());
975         }
976     }
977 
978     @Test
979     public void testGetTrailerComment_HeaderTrailerComment1() throws IOException {
980         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_TRAILER_COMMENT, FORMAT_AUTO_HEADER)) {
981             parser.getRecords();
982             assertTrue(parser.hasTrailerComment());
983             assertEquals("comment", parser.getTrailerComment());
984         }
985     }
986 
987     @Test
988     public void testGetTrailerComment_HeaderTrailerComment2() throws IOException {
989         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_TRAILER_COMMENT, FORMAT_EXPLICIT_HEADER)) {
990             parser.getRecords();
991             assertTrue(parser.hasTrailerComment());
992             assertEquals("comment", parser.getTrailerComment());
993         }
994     }
995 
996     @Test
997     public void testGetTrailerComment_HeaderTrailerComment3() throws IOException {
998         try (CSVParser parser = CSVParser.parse(CSV_INPUT_HEADER_TRAILER_COMMENT, FORMAT_EXPLICIT_HEADER_NOSKIP)) {
999             parser.getRecords();
1000             assertTrue(parser.hasTrailerComment());
1001             assertEquals("comment", parser.getTrailerComment());
1002         }
1003     }
1004 
1005     @Test
1006     public void testGetTrailerComment_MultilineComment() throws IOException {
1007         try (CSVParser parser = CSVParser.parse(CSV_INPUT_MULTILINE_HEADER_TRAILER_COMMENT, FORMAT_AUTO_HEADER)) {
1008             parser.getRecords();
1009             assertTrue(parser.hasTrailerComment());
1010             assertEquals("multi-line" + LF + "comment", parser.getTrailerComment());
1011         }
1012     }
1013 
1014     @Test
1015     public void testHeader() throws Exception {
1016         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1017 
1018         try (CSVParser parser = CSVFormat.DEFAULT.withHeader().parse(in)) {
1019             final Iterator<CSVRecord> records = parser.iterator();
1020 
1021             for (int i = 0; i < 2; i++) {
1022                 assertTrue(records.hasNext());
1023                 final CSVRecord record = records.next();
1024                 assertEquals(record.get(0), record.get("a"));
1025                 assertEquals(record.get(1), record.get("b"));
1026                 assertEquals(record.get(2), record.get("c"));
1027             }
1028 
1029             assertFalse(records.hasNext());
1030         }
1031     }
1032 
1033     @Test
1034     public void testHeaderComment() throws Exception {
1035         final Reader in = new StringReader("# comment\na,b,c\n1,2,3\nx,y,z");
1036         try (CSVParser parser = CSVFormat.DEFAULT.withCommentMarker('#').withHeader().parse(in)) {
1037             final Iterator<CSVRecord> records = parser.iterator();
1038             for (int i = 0; i < 2; i++) {
1039                 assertTrue(records.hasNext());
1040                 final CSVRecord record = records.next();
1041                 assertEquals(record.get(0), record.get("a"));
1042                 assertEquals(record.get(1), record.get("b"));
1043                 assertEquals(record.get(2), record.get("c"));
1044             }
1045             assertFalse(records.hasNext());
1046         }
1047     }
1048 
1049     @Test
1050     public void testHeaderMissing() throws Exception {
1051         final Reader in = new StringReader("a,,c\n1,2,3\nx,y,z");
1052         try (CSVParser parser = CSVFormat.DEFAULT.withHeader().withAllowMissingColumnNames().parse(in)) {
1053             final Iterator<CSVRecord> records = parser.iterator();
1054             for (int i = 0; i < 2; i++) {
1055                 assertTrue(records.hasNext());
1056                 final CSVRecord record = records.next();
1057                 assertEquals(record.get(0), record.get("a"));
1058                 assertEquals(record.get(2), record.get("c"));
1059             }
1060             assertFalse(records.hasNext());
1061         }
1062     }
1063 
1064     @Test
1065     public void testHeaderMissingWithNull() throws Exception {
1066         final Reader in = new StringReader("a,,c,,e\n1,2,3,4,5\nv,w,x,y,z");
1067         try (CSVParser parser = CSVFormat.DEFAULT.withHeader().withNullString("").withAllowMissingColumnNames().parse(in)) {
1068             parser.iterator();
1069         }
1070     }
1071 
1072     @Test
1073     public void testHeadersMissing() throws Exception {
1074         try (Reader in = new StringReader("a,,c,,e\n1,2,3,4,5\nv,w,x,y,z");
1075                 CSVParser parser = CSVFormat.DEFAULT.withHeader().withAllowMissingColumnNames().parse(in)) {
1076             parser.iterator();
1077         }
1078     }
1079 
1080     @Test
1081     public void testHeadersMissingException() {
1082         final Reader in = new StringReader("a,,c,,e\n1,2,3,4,5\nv,w,x,y,z");
1083         assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withHeader().parse(in).iterator());
1084     }
1085 
1086     @Test
1087     public void testHeadersMissingOneColumnException() {
1088         final Reader in = new StringReader("a,,c,d,e\n1,2,3,4,5\nv,w,x,y,z");
1089         assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withHeader().parse(in).iterator());
1090     }
1091 
1092     @Test
1093     public void testHeadersWithNullColumnName() throws IOException {
1094         final Reader in = new StringReader("header1,null,header3\n1,2,3\n4,5,6");
1095         try (CSVParser parser = CSVFormat.DEFAULT.withHeader().withNullString("null").withAllowMissingColumnNames().parse(in)) {
1096             final Iterator<CSVRecord> records = parser.iterator();
1097             final CSVRecord record = records.next();
1098             // Expect the null header to be missing
1099             @SuppressWarnings("resource")
1100             final CSVParser recordParser = record.getParser();
1101             assertEquals(Arrays.asList("header1", "header3"), recordParser.getHeaderNames());
1102             assertEquals(2, recordParser.getHeaderMap().size());
1103         }
1104     }
1105 
1106     @Test
1107     public void testIgnoreCaseHeaderMapping() throws Exception {
1108         final Reader reader = new StringReader("1,2,3");
1109         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("One", "TWO", "three").withIgnoreHeaderCase().parse(reader)) {
1110             final Iterator<CSVRecord> records = parser.iterator();
1111             final CSVRecord record = records.next();
1112             assertEquals("1", record.get("one"));
1113             assertEquals("2", record.get("two"));
1114             assertEquals("3", record.get("THREE"));
1115         }
1116     }
1117 
1118     @Test
1119     public void testIgnoreEmptyLines() throws IOException {
1120         final String code = "\nfoo,baar\n\r\n,\n\n,world\r\n\n";
1121         // String code = "world\r\n\n";
1122         // String code = "foo;baar\r\n\r\nhello;\r\n\r\nworld;\r\n";
1123         try (CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
1124             final List<CSVRecord> records = parser.getRecords();
1125             assertEquals(3, records.size());
1126         }
1127     }
1128 
1129     @Test
1130     public void testInvalidFormat() {
1131         assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withDelimiter(CR));
1132     }
1133 
1134     @Test
1135     public void testIterator() throws Exception {
1136         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1137         try (CSVParser parser = CSVFormat.DEFAULT.parse(in)) {
1138             final Iterator<CSVRecord> iterator = parser.iterator();
1139             assertTrue(iterator.hasNext());
1140             assertThrows(UnsupportedOperationException.class, iterator::remove);
1141             assertArrayEquals(new String[] { "a", "b", "c" }, iterator.next().values());
1142             assertArrayEquals(new String[] { "1", "2", "3" }, iterator.next().values());
1143             assertTrue(iterator.hasNext());
1144             assertTrue(iterator.hasNext());
1145             assertTrue(iterator.hasNext());
1146             assertArrayEquals(new String[] { "x", "y", "z" }, iterator.next().values());
1147             assertFalse(iterator.hasNext());
1148             assertThrows(NoSuchElementException.class, iterator::next);
1149         }
1150     }
1151 
1152     @Test
1153     public void testIteratorSequenceBreaking() throws IOException {
1154         final String fiveRows = "1\n2\n3\n4\n5\n";
1155         // Iterator hasNext() shouldn't break sequence
1156         try (CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows))) {
1157             final Iterator<CSVRecord> iter = parser.iterator();
1158             int recordNumber = 0;
1159             while (iter.hasNext()) {
1160                 final CSVRecord record = iter.next();
1161                 recordNumber++;
1162                 assertEquals(String.valueOf(recordNumber), record.get(0));
1163                 if (recordNumber >= 2) {
1164                     break;
1165                 }
1166             }
1167             iter.hasNext();
1168             while (iter.hasNext()) {
1169                 final CSVRecord record = iter.next();
1170                 recordNumber++;
1171                 assertEquals(String.valueOf(recordNumber), record.get(0));
1172             }
1173         }
1174         // Consecutive enhanced for loops shouldn't break sequence
1175         try (CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows))) {
1176             int recordNumber = 0;
1177             for (final CSVRecord record : parser) {
1178                 recordNumber++;
1179                 assertEquals(String.valueOf(recordNumber), record.get(0));
1180                 if (recordNumber >= 2) {
1181                     break;
1182                 }
1183             }
1184             for (final CSVRecord record : parser) {
1185                 recordNumber++;
1186                 assertEquals(String.valueOf(recordNumber), record.get(0));
1187             }
1188         }
1189         // Consecutive enhanced for loops with hasNext() peeking shouldn't break sequence
1190         try (CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows))) {
1191             int recordNumber = 0;
1192             for (final CSVRecord record : parser) {
1193                 recordNumber++;
1194                 assertEquals(String.valueOf(recordNumber), record.get(0));
1195                 if (recordNumber >= 2) {
1196                     break;
1197                 }
1198             }
1199             parser.iterator().hasNext();
1200             for (final CSVRecord record : parser) {
1201                 recordNumber++;
1202                 assertEquals(String.valueOf(recordNumber), record.get(0));
1203             }
1204         }
1205     }
1206 
1207     @Test
1208     public void testLineFeedEndings() throws IOException {
1209         final String code = "foo\nbaar,\nhello,world\n,kanu";
1210         try (CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
1211             final List<CSVRecord> records = parser.getRecords();
1212             assertEquals(4, records.size());
1213         }
1214     }
1215 
1216     @Test
1217     public void testMappedButNotSetAsOutlook2007ContactExport() throws Exception {
1218         final Reader in = new StringReader("a,b,c\n1,2\nx,y,z");
1219         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("A", "B", "C").withSkipHeaderRecord().parse(in)) {
1220             final Iterator<CSVRecord> records = parser.iterator();
1221             CSVRecord record;
1222             // 1st record
1223             record = records.next();
1224             assertTrue(record.isMapped("A"));
1225             assertTrue(record.isMapped("B"));
1226             assertTrue(record.isMapped("C"));
1227             assertTrue(record.isSet("A"));
1228             assertTrue(record.isSet("B"));
1229             assertFalse(record.isSet("C"));
1230             assertEquals("1", record.get("A"));
1231             assertEquals("2", record.get("B"));
1232             assertFalse(record.isConsistent());
1233             // 2nd record
1234             record = records.next();
1235             assertTrue(record.isMapped("A"));
1236             assertTrue(record.isMapped("B"));
1237             assertTrue(record.isMapped("C"));
1238             assertTrue(record.isSet("A"));
1239             assertTrue(record.isSet("B"));
1240             assertTrue(record.isSet("C"));
1241             assertEquals("x", record.get("A"));
1242             assertEquals("y", record.get("B"));
1243             assertEquals("z", record.get("C"));
1244             assertTrue(record.isConsistent());
1245             // end
1246             assertFalse(records.hasNext());
1247         }
1248     }
1249 
1250     @Test
1251     @Disabled
1252     public void testMongoDbCsv() throws Exception {
1253         try (CSVParser parser = CSVParser.parse("\"a a\",b,c" + LF + "d,e,f", CSVFormat.MONGODB_CSV)) {
1254             final Iterator<CSVRecord> itr1 = parser.iterator();
1255             final Iterator<CSVRecord> itr2 = parser.iterator();
1256 
1257             final CSVRecord first = itr1.next();
1258             assertEquals("a a", first.get(0));
1259             assertEquals("b", first.get(1));
1260             assertEquals("c", first.get(2));
1261 
1262             final CSVRecord second = itr2.next();
1263             assertEquals("d", second.get(0));
1264             assertEquals("e", second.get(1));
1265             assertEquals("f", second.get(2));
1266         }
1267     }
1268 
1269     @Test
1270     // TODO this may lead to strange behavior, throw an exception if iterator() has already been called?
1271     public void testMultipleIterators() throws Exception {
1272         try (CSVParser parser = CSVParser.parse("a,b,c" + CRLF + "d,e,f", CSVFormat.DEFAULT)) {
1273             final Iterator<CSVRecord> itr1 = parser.iterator();
1274 
1275             final CSVRecord first = itr1.next();
1276             assertEquals("a", first.get(0));
1277             assertEquals("b", first.get(1));
1278             assertEquals("c", first.get(2));
1279 
1280             final CSVRecord second = itr1.next();
1281             assertEquals("d", second.get(0));
1282             assertEquals("e", second.get(1));
1283             assertEquals("f", second.get(2));
1284         }
1285     }
1286 
1287     @Test
1288     public void testNewCSVParserNullReaderFormat() {
1289         assertThrows(NullPointerException.class, () -> new CSVParser(null, CSVFormat.DEFAULT));
1290     }
1291 
1292     @Test
1293     public void testNewCSVParserReaderNullFormat() {
1294         assertThrows(NullPointerException.class, () -> new CSVParser(new StringReader(""), null));
1295     }
1296 
1297     @Test
1298     public void testNoHeaderMap() throws Exception {
1299         try (CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z", CSVFormat.DEFAULT)) {
1300             assertNull(parser.getHeaderMap());
1301         }
1302     }
1303 
1304     @Test
1305     public void testNotValueCSV() throws IOException {
1306         final String source = "#";
1307         final CSVFormat csvFormat = CSVFormat.DEFAULT.withCommentMarker('#');
1308         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1309             final CSVRecord csvRecord = csvParser.nextRecord();
1310             assertNull(csvRecord);
1311         }
1312     }
1313 
1314     @Test
1315     public void testParse() throws Exception {
1316         final ClassLoader loader = ClassLoader.getSystemClassLoader();
1317         final URL url = loader.getResource("org/apache/commons/csv/CSVFileParser/test.csv");
1318         final CSVFormat format = CSVFormat.DEFAULT.builder().setHeader("A", "B", "C", "D").get();
1319         final Charset charset = StandardCharsets.UTF_8;
1320         // Reader
1321         try (CSVParser parser = CSVParser.parse(new InputStreamReader(url.openStream(), charset), format)) {
1322             parseFully(parser);
1323         }
1324         try (CSVParser parser = CSVParser.builder().setReader(new InputStreamReader(url.openStream(), charset)).setFormat(format).get()) {
1325             parseFully(parser);
1326         }
1327         // String
1328         final Path path = Paths.get(url.toURI());
1329         final String string = new String(Files.readAllBytes(path), charset);
1330         try (CSVParser parser = CSVParser.parse(string, format)) {
1331             parseFully(parser);
1332         }
1333         try (CSVParser parser = CSVParser.builder().setCharSequence(string).setFormat(format).get()) {
1334             parseFully(parser);
1335         }
1336         // File
1337         final File file = new File(url.toURI());
1338         try (CSVParser parser = CSVParser.parse(file, charset, format)) {
1339             parseFully(parser);
1340         }
1341         try (CSVParser parser = CSVParser.builder().setFile(file).setCharset(charset).setFormat(format).get()) {
1342             parseFully(parser);
1343         }
1344         // InputStream
1345         try (CSVParser parser = CSVParser.parse(url.openStream(), charset, format)) {
1346             parseFully(parser);
1347         }
1348         try (CSVParser parser = CSVParser.builder().setInputStream(url.openStream()).setCharset(charset).setFormat(format).get()) {
1349             parseFully(parser);
1350         }
1351         // Path
1352         try (CSVParser parser = CSVParser.parse(path, charset, format)) {
1353             parseFully(parser);
1354         }
1355         try (CSVParser parser = CSVParser.builder().setPath(path).setCharset(charset).setFormat(format).get()) {
1356             parseFully(parser);
1357         }
1358         // URL
1359         try (CSVParser parser = CSVParser.parse(url, charset, format)) {
1360             parseFully(parser);
1361         }
1362         try (CSVParser parser = CSVParser.builder().setURI(url.toURI()).setCharset(charset).setFormat(format).get()) {
1363             parseFully(parser);
1364         }
1365         // InputStreamReader
1366         try (CSVParser parser = new CSVParser(new InputStreamReader(url.openStream(), charset), format)) {
1367             parseFully(parser);
1368         }
1369         try (CSVParser parser = CSVParser.builder().setReader(new InputStreamReader(url.openStream(), charset)).setFormat(format).get()) {
1370             parseFully(parser);
1371         }
1372         // InputStreamReader with longs
1373         try (CSVParser parser = new CSVParser(new InputStreamReader(url.openStream(), charset), format, /* characterOffset= */0, /* recordNumber= */1)) {
1374             parseFully(parser);
1375         }
1376         try (CSVParser parser = CSVParser.builder().setReader(new InputStreamReader(url.openStream(), charset)).setFormat(format).setCharacterOffset(0)
1377                 .setRecordNumber(0).get()) {
1378             parseFully(parser);
1379         }
1380     }
1381 
1382     @Test
1383     public void testParseFileNullFormat() {
1384         assertThrows(NullPointerException.class, () -> CSVParser.parse(new File("CSVFileParser/test.csv"), Charset.defaultCharset(), null));
1385     }
1386 
1387     @Test
1388     public void testParseNullFileFormat() {
1389         assertThrows(NullPointerException.class, () -> CSVParser.parse((File) null, Charset.defaultCharset(), CSVFormat.DEFAULT));
1390     }
1391 
1392     @Test
1393     public void testParseNullPathFormat() {
1394         assertThrows(NullPointerException.class, () -> CSVParser.parse((Path) null, Charset.defaultCharset(), CSVFormat.DEFAULT));
1395     }
1396 
1397     @Test
1398     public void testParseNullStringFormat() {
1399         assertThrows(NullPointerException.class, () -> CSVParser.parse((String) null, CSVFormat.DEFAULT));
1400     }
1401 
1402     @Test
1403     public void testParseNullUrlCharsetFormat() {
1404         assertThrows(NullPointerException.class, () -> CSVParser.parse((URL) null, Charset.defaultCharset(), CSVFormat.DEFAULT));
1405     }
1406 
1407     @Test
1408     public void testParserUrlNullCharsetFormat() {
1409         assertThrows(NullPointerException.class, () -> CSVParser.parse(new URL("https://commons.apache.org"), null, CSVFormat.DEFAULT));
1410     }
1411 
1412     @Test
1413     public void testParseStringNullFormat() {
1414         assertThrows(NullPointerException.class, () -> CSVParser.parse("csv data", (CSVFormat) null));
1415     }
1416 
1417     @Test
1418     public void testParseUrlCharsetNullFormat() {
1419         assertThrows(NullPointerException.class, () -> CSVParser.parse(new URL("https://commons.apache.org"), Charset.defaultCharset(), null));
1420     }
1421 
1422     @Test
1423     public void testParseWithDelimiterStringWithEscape() throws IOException {
1424         final String source = "a![!|!]b![|]c[|]xyz\r\nabc[abc][|]xyz";
1425         final CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setEscape('!').get();
1426         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1427             CSVRecord csvRecord = csvParser.nextRecord();
1428             assertEquals("a[|]b![|]c", csvRecord.get(0));
1429             assertEquals("xyz", csvRecord.get(1));
1430             csvRecord = csvParser.nextRecord();
1431             assertEquals("abc[abc]", csvRecord.get(0));
1432             assertEquals("xyz", csvRecord.get(1));
1433         }
1434     }
1435 
1436     @Test
1437     public void testParseWithDelimiterStringWithQuote() throws IOException {
1438         final String source = "'a[|]b[|]c'[|]xyz\r\nabc[abc][|]xyz";
1439         final CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setQuote('\'').get();
1440         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1441             CSVRecord csvRecord = csvParser.nextRecord();
1442             assertEquals("a[|]b[|]c", csvRecord.get(0));
1443             assertEquals("xyz", csvRecord.get(1));
1444             csvRecord = csvParser.nextRecord();
1445             assertEquals("abc[abc]", csvRecord.get(0));
1446             assertEquals("xyz", csvRecord.get(1));
1447         }
1448     }
1449 
1450     @Test
1451     public void testParseWithDelimiterWithEscape() throws IOException {
1452         final String source = "a!,b!,c,xyz";
1453         final CSVFormat csvFormat = CSVFormat.DEFAULT.withEscape('!');
1454         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1455             final CSVRecord csvRecord = csvParser.nextRecord();
1456             assertEquals("a,b,c", csvRecord.get(0));
1457             assertEquals("xyz", csvRecord.get(1));
1458         }
1459     }
1460 
1461     @Test
1462     public void testParseWithDelimiterWithQuote() throws IOException {
1463         final String source = "'a,b,c',xyz";
1464         final CSVFormat csvFormat = CSVFormat.DEFAULT.withQuote('\'');
1465         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1466             final CSVRecord csvRecord = csvParser.nextRecord();
1467             assertEquals("a,b,c", csvRecord.get(0));
1468             assertEquals("xyz", csvRecord.get(1));
1469         }
1470     }
1471 
1472     @Test
1473     public void testParseWithQuoteThrowsException() {
1474         final CSVFormat csvFormat = CSVFormat.DEFAULT.withQuote('\'');
1475         assertThrows(IOException.class, () -> csvFormat.parse(new StringReader("'a,b,c','")).nextRecord());
1476         assertThrows(IOException.class, () -> csvFormat.parse(new StringReader("'a,b,c'abc,xyz")).nextRecord());
1477         assertThrows(IOException.class, () -> csvFormat.parse(new StringReader("'abc'a,b,c',xyz")).nextRecord());
1478     }
1479 
1480     @Test
1481     public void testParseWithQuoteWithEscape() throws IOException {
1482         final String source = "'a?,b?,c?d',xyz";
1483         final CSVFormat csvFormat = CSVFormat.DEFAULT.withQuote('\'').withEscape('?');
1484         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1485             final CSVRecord csvRecord = csvParser.nextRecord();
1486             assertEquals("a,b,c?d", csvRecord.get(0));
1487             assertEquals("xyz", csvRecord.get(1));
1488         }
1489     }
1490 
1491     @ParameterizedTest
1492     @EnumSource(CSVFormat.Predefined.class)
1493     public void testParsingPrintedEmptyFirstColumn(final CSVFormat.Predefined format) throws Exception {
1494         final String[][] lines = { { "a", "b" }, { "", "x" } };
1495         final StringWriter buf = new StringWriter();
1496         try (CSVPrinter printer = new CSVPrinter(buf, format.getFormat())) {
1497             printer.printRecords(Stream.of(lines));
1498         }
1499         try (CSVParser csvRecords = CSVParser.builder()
1500                 .setReader(new StringReader(buf.toString()))
1501                 .setFormat(format.getFormat())
1502                 .get()) {
1503             for (final String[] line : lines) {
1504                 assertArrayEquals(line, csvRecords.nextRecord().values());
1505             }
1506             assertNull(csvRecords.nextRecord());
1507         }
1508     }
1509 
1510     @Test
1511     public void testProvidedHeader() throws Exception {
1512         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1513 
1514         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("A", "B", "C").parse(in)) {
1515             final Iterator<CSVRecord> records = parser.iterator();
1516 
1517             for (int i = 0; i < 3; i++) {
1518                 assertTrue(records.hasNext());
1519                 final CSVRecord record = records.next();
1520                 assertTrue(record.isMapped("A"));
1521                 assertTrue(record.isMapped("B"));
1522                 assertTrue(record.isMapped("C"));
1523                 assertFalse(record.isMapped("NOT MAPPED"));
1524                 assertEquals(record.get(0), record.get("A"));
1525                 assertEquals(record.get(1), record.get("B"));
1526                 assertEquals(record.get(2), record.get("C"));
1527             }
1528 
1529             assertFalse(records.hasNext());
1530         }
1531     }
1532 
1533     @Test
1534     public void testProvidedHeaderAuto() throws Exception {
1535         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1536 
1537         try (CSVParser parser = CSVFormat.DEFAULT.withHeader().parse(in)) {
1538             final Iterator<CSVRecord> records = parser.iterator();
1539 
1540             for (int i = 0; i < 2; i++) {
1541                 assertTrue(records.hasNext());
1542                 final CSVRecord record = records.next();
1543                 assertTrue(record.isMapped("a"));
1544                 assertTrue(record.isMapped("b"));
1545                 assertTrue(record.isMapped("c"));
1546                 assertFalse(record.isMapped("NOT MAPPED"));
1547                 assertEquals(record.get(0), record.get("a"));
1548                 assertEquals(record.get(1), record.get("b"));
1549                 assertEquals(record.get(2), record.get("c"));
1550             }
1551 
1552             assertFalse(records.hasNext());
1553         }
1554     }
1555 
1556     @Test
1557     public void testRepeatedHeadersAreReturnedInCSVRecordHeaderNames() throws IOException {
1558         final Reader in = new StringReader("header1,header2,header1\n1,2,3\n4,5,6");
1559         try (CSVParser parser = CSVFormat.DEFAULT.withFirstRecordAsHeader().withTrim().parse(in)) {
1560             final Iterator<CSVRecord> records = parser.iterator();
1561             final CSVRecord record = records.next();
1562             @SuppressWarnings("resource")
1563             final CSVParser recordParser = record.getParser();
1564             assertEquals(Arrays.asList("header1", "header2", "header1"), recordParser.getHeaderNames());
1565         }
1566     }
1567 
1568     @Test
1569     public void testRoundtrip() throws Exception {
1570         final StringWriter out = new StringWriter();
1571         final String data = "a,b,c\r\n1,2,3\r\nx,y,z\r\n";
1572         try (CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT);
1573                 CSVParser parse = CSVParser.parse(data, CSVFormat.DEFAULT)) {
1574             for (final CSVRecord record : parse) {
1575                 printer.printRecord(record);
1576             }
1577             assertEquals(data, out.toString());
1578         }
1579     }
1580 
1581     @Test
1582     public void testSkipAutoHeader() throws Exception {
1583         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1584         try (CSVParser parser = CSVFormat.DEFAULT.withHeader().parse(in)) {
1585             final Iterator<CSVRecord> records = parser.iterator();
1586             final CSVRecord record = records.next();
1587             assertEquals("1", record.get("a"));
1588             assertEquals("2", record.get("b"));
1589             assertEquals("3", record.get("c"));
1590         }
1591     }
1592 
1593     @Test
1594     public void testSkipHeaderOverrideDuplicateHeaders() throws Exception {
1595         final Reader in = new StringReader("a,a,a\n1,2,3\nx,y,z");
1596         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().parse(in)) {
1597             final Iterator<CSVRecord> records = parser.iterator();
1598             final CSVRecord record = records.next();
1599             assertEquals("1", record.get("X"));
1600             assertEquals("2", record.get("Y"));
1601             assertEquals("3", record.get("Z"));
1602         }
1603     }
1604 
1605     @Test
1606     public void testSkipSetAltHeaders() throws Exception {
1607         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1608         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().parse(in)) {
1609             final Iterator<CSVRecord> records = parser.iterator();
1610             final CSVRecord record = records.next();
1611             assertEquals("1", record.get("X"));
1612             assertEquals("2", record.get("Y"));
1613             assertEquals("3", record.get("Z"));
1614         }
1615     }
1616 
1617     @Test
1618     public void testSkipSetHeader() throws Exception {
1619         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1620         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("a", "b", "c").withSkipHeaderRecord().parse(in)) {
1621             final Iterator<CSVRecord> records = parser.iterator();
1622             final CSVRecord record = records.next();
1623             assertEquals("1", record.get("a"));
1624             assertEquals("2", record.get("b"));
1625             assertEquals("3", record.get("c"));
1626         }
1627     }
1628 
1629     @Test
1630     @Disabled
1631     public void testStartWithEmptyLinesThenHeaders() throws Exception {
1632         final String[] codes = { "\r\n\r\n\r\nhello,\r\n\r\n\r\n", "hello,\n\n\n", "hello,\"\"\r\n\r\n\r\n", "hello,\"\"\n\n\n" };
1633         final String[][] res = { { "hello", "" }, { "" }, // Excel format does not ignore empty lines
1634                 { "" } };
1635         for (final String code : codes) {
1636             try (CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
1637                 final List<CSVRecord> records = parser.getRecords();
1638                 assertEquals(res.length, records.size());
1639                 assertFalse(records.isEmpty());
1640                 for (int i = 0; i < res.length; i++) {
1641                     assertArrayEquals(res[i], records.get(i).values());
1642                 }
1643             }
1644         }
1645     }
1646 
1647     @Test
1648     public void testStream() throws Exception {
1649         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1650         try (CSVParser parser = CSVFormat.DEFAULT.parse(in)) {
1651             final List<CSVRecord> list = parser.stream().collect(Collectors.toList());
1652             assertFalse(list.isEmpty());
1653             assertArrayEquals(new String[] { "a", "b", "c" }, list.get(0).values());
1654             assertArrayEquals(new String[] { "1", "2", "3" }, list.get(1).values());
1655             assertArrayEquals(new String[] { "x", "y", "z" }, list.get(2).values());
1656         }
1657     }
1658 
1659     @Test
1660     public void testThrowExceptionWithLineAndPosition() throws IOException {
1661         final String csvContent = "col1,col2,col3,col4,col5,col6,col7,col8,col9,col10\nrec1,rec2,rec3,rec4,rec5,rec6,rec7,rec8,\"\"rec9\"\",rec10";
1662         final StringReader stringReader = new StringReader(csvContent);
1663         // @formatter:off
1664         final CSVFormat csvFormat = CSVFormat.DEFAULT.builder()
1665                 .setHeader()
1666                 .setSkipHeaderRecord(true)
1667                 .get();
1668         // @formatter:on
1669         try (CSVParser csvParser = csvFormat.parse(stringReader)) {
1670             final UncheckedIOException exception = assertThrows(UncheckedIOException.class, csvParser::getRecords);
1671             assertInstanceOf(CSVException.class, exception.getCause());
1672             assertTrue(exception.getMessage().contains("Invalid character between encapsulated token and delimiter at line: 2, position: 94"),
1673                     exception::getMessage);
1674         }
1675     }
1676 
1677     @Test
1678     public void testTrailingDelimiter() throws Exception {
1679         final Reader in = new StringReader("a,a,a,\n\"1\",\"2\",\"3\",\nx,y,z,");
1680         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().withTrailingDelimiter().parse(in)) {
1681             final Iterator<CSVRecord> records = parser.iterator();
1682             final CSVRecord record = records.next();
1683             assertEquals("1", record.get("X"));
1684             assertEquals("2", record.get("Y"));
1685             assertEquals("3", record.get("Z"));
1686             assertEquals(3, record.size());
1687         }
1688     }
1689 
1690     @Test
1691     public void testTrim() throws Exception {
1692         final Reader in = new StringReader("a,a,a\n\" 1 \",\" 2 \",\" 3 \"\nx,y,z");
1693         try (CSVParser parser = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().withTrim().parse(in)) {
1694             final Iterator<CSVRecord> records = parser.iterator();
1695             final CSVRecord record = records.next();
1696             assertEquals("1", record.get("X"));
1697             assertEquals("2", record.get("Y"));
1698             assertEquals("3", record.get("Z"));
1699             assertEquals(3, record.size());
1700         }
1701     }
1702 
1703     private void validateLineNumbers(final String lineSeparator) throws IOException {
1704         try (CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c", CSVFormat.DEFAULT.withRecordSeparator(lineSeparator))) {
1705             assertEquals(0, parser.getCurrentLineNumber());
1706             assertNotNull(parser.nextRecord());
1707             assertEquals(1, parser.getCurrentLineNumber());
1708             assertNotNull(parser.nextRecord());
1709             assertEquals(2, parser.getCurrentLineNumber());
1710             assertNotNull(parser.nextRecord());
1711             // Read EOF without EOL should 3
1712             assertEquals(3, parser.getCurrentLineNumber());
1713             assertNull(parser.nextRecord());
1714             // Read EOF without EOL should 3
1715             assertEquals(3, parser.getCurrentLineNumber());
1716         }
1717     }
1718 
1719     private void validateRecordNumbers(final String lineSeparator) throws IOException {
1720         try (CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c", CSVFormat.DEFAULT.withRecordSeparator(lineSeparator))) {
1721             CSVRecord record;
1722             assertEquals(0, parser.getRecordNumber());
1723             assertNotNull(record = parser.nextRecord());
1724             assertEquals(1, record.getRecordNumber());
1725             assertEquals(1, parser.getRecordNumber());
1726             assertNotNull(record = parser.nextRecord());
1727             assertEquals(2, record.getRecordNumber());
1728             assertEquals(2, parser.getRecordNumber());
1729             assertNotNull(record = parser.nextRecord());
1730             assertEquals(3, record.getRecordNumber());
1731             assertEquals(3, parser.getRecordNumber());
1732             assertNull(record = parser.nextRecord());
1733             assertEquals(3, parser.getRecordNumber());
1734         }
1735     }
1736 
1737     private void validateRecordPosition(final String lineSeparator) throws IOException {
1738         final String nl = lineSeparator; // used as linebreak in values for better distinction
1739         final String code = "a,b,c" + lineSeparator + "1,2,3" + lineSeparator +
1740                 // to see if recordPosition correctly points to the enclosing quote
1741                 "'A" + nl + "A','B" + nl + "B',CC" + lineSeparator +
1742                 // unicode test... not very relevant while operating on strings instead of bytes, but for
1743                 // completeness...
1744                 "\u00c4,\u00d6,\u00dc" + lineSeparator + "EOF,EOF,EOF";
1745         final CSVFormat format = CSVFormat.newFormat(',').withQuote('\'').withRecordSeparator(lineSeparator);
1746         final long positionRecord3;
1747         try (CSVParser parser = CSVParser.parse(code, format)) {
1748             CSVRecord record;
1749             assertEquals(0, parser.getRecordNumber());
1750             // nextRecord
1751             assertNotNull(record = parser.nextRecord());
1752             assertEquals(1, record.getRecordNumber());
1753             assertEquals(code.indexOf('a'), record.getCharacterPosition());
1754             // nextRecord
1755             assertNotNull(record = parser.nextRecord());
1756             assertEquals(2, record.getRecordNumber());
1757             assertEquals(code.indexOf('1'), record.getCharacterPosition());
1758             // nextRecord
1759             assertNotNull(record = parser.nextRecord());
1760             positionRecord3 = record.getCharacterPosition();
1761             assertEquals(3, record.getRecordNumber());
1762             assertEquals(code.indexOf("'A"), record.getCharacterPosition());
1763             assertEquals("A" + lineSeparator + "A", record.get(0));
1764             assertEquals("B" + lineSeparator + "B", record.get(1));
1765             assertEquals("CC", record.get(2));
1766             // nextRecord
1767             assertNotNull(record = parser.nextRecord());
1768             assertEquals(4, record.getRecordNumber());
1769             assertEquals(code.indexOf('\u00c4'), record.getCharacterPosition());
1770             // nextRecord
1771             assertNotNull(record = parser.nextRecord());
1772             assertEquals(5, record.getRecordNumber());
1773             assertEquals(code.indexOf("EOF"), record.getCharacterPosition());
1774         }
1775         // now try to read starting at record 3
1776         try (CSVParser parser = CSVParser.builder()
1777                 .setReader(new StringReader(code.substring((int) positionRecord3)))
1778                 .setFormat(format)
1779                 .setCharacterOffset(positionRecord3)
1780                 .setRecordNumber(3)
1781                 .get()) {
1782             CSVRecord record;
1783             // nextRecord
1784             assertNotNull(record = parser.nextRecord());
1785             assertEquals(3, record.getRecordNumber());
1786             assertEquals(code.indexOf("'A"), record.getCharacterPosition());
1787             assertEquals("A" + lineSeparator + "A", record.get(0));
1788             assertEquals("B" + lineSeparator + "B", record.get(1));
1789             assertEquals("CC", record.get(2));
1790             // nextRecord
1791             assertNotNull(record = parser.nextRecord());
1792             assertEquals(4, record.getRecordNumber());
1793             assertEquals(code.indexOf('\u00c4'), record.getCharacterPosition());
1794             assertEquals("\u00c4", record.get(0));
1795         } // again with ctor
1796         try (CSVParser parser = new CSVParser(new StringReader(code.substring((int) positionRecord3)), format, positionRecord3, 3)) {
1797             CSVRecord record;
1798             // nextRecord
1799             assertNotNull(record = parser.nextRecord());
1800             assertEquals(3, record.getRecordNumber());
1801             assertEquals(code.indexOf("'A"), record.getCharacterPosition());
1802             assertEquals("A" + lineSeparator + "A", record.get(0));
1803             assertEquals("B" + lineSeparator + "B", record.get(1));
1804             assertEquals("CC", record.get(2));
1805             // nextRecord
1806             assertNotNull(record = parser.nextRecord());
1807             assertEquals(4, record.getRecordNumber());
1808             assertEquals(code.indexOf('\u00c4'), record.getCharacterPosition());
1809             assertEquals("\u00c4", record.get(0));
1810         }
1811     }
1812 }