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