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