View Javadoc
1   /*
2    * Licensed to the Apache Software Foundation (ASF) under one or more
3    * contributor license agreements.  See the NOTICE file distributed with
4    * this work for additional information regarding copyright ownership.
5    * The ASF licenses this file to You under the Apache License, Version 2.0
6    * (the "License"); you may not use this file except in compliance with
7    * the License.  You may obtain a copy of the License at
8    *
9    *      http://www.apache.org/licenses/LICENSE-2.0
10   *
11   * Unless required by applicable law or agreed to in writing, software
12   * distributed under the License is distributed on an "AS IS" BASIS,
13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14   * See the License for the specific language governing permissions and
15   * limitations under the License.
16   */
17  
18  package org.apache.commons.csv;
19  
20  import static org.apache.commons.csv.Constants.CR;
21  import static org.apache.commons.csv.Constants.CRLF;
22  import static org.apache.commons.csv.Constants.LF;
23  import static org.junit.jupiter.api.Assertions.assertArrayEquals;
24  import static org.junit.jupiter.api.Assertions.assertEquals;
25  import static org.junit.jupiter.api.Assertions.assertFalse;
26  import static org.junit.jupiter.api.Assertions.assertNotNull;
27  import static org.junit.jupiter.api.Assertions.assertNull;
28  import static org.junit.jupiter.api.Assertions.assertThrows;
29  import static org.junit.jupiter.api.Assertions.assertTrue;
30  
31  import java.io.File;
32  import java.io.IOException;
33  import java.io.InputStreamReader;
34  import java.io.PipedReader;
35  import java.io.PipedWriter;
36  import java.io.Reader;
37  import java.io.StringReader;
38  import java.io.StringWriter;
39  import java.net.URL;
40  import java.nio.charset.Charset;
41  import java.nio.charset.StandardCharsets;
42  import java.nio.file.Files;
43  import java.nio.file.Path;
44  import java.nio.file.Paths;
45  import java.util.ArrayList;
46  import java.util.Arrays;
47  import java.util.Iterator;
48  import java.util.List;
49  import java.util.Map;
50  import java.util.NoSuchElementException;
51  import java.util.stream.Collectors;
52  
53  import org.apache.commons.io.input.BOMInputStream;
54  import org.junit.jupiter.api.Disabled;
55  import org.junit.jupiter.api.Test;
56  
57  /**
58   * CSVParserTest
59   *
60   * The test are organized in three different sections: The 'setter/getter' section, the lexer section and finally the
61   * parser section. In case a test fails, you should follow a top-down approach for fixing a potential bug (its likely
62   * that the parser itself fails if the lexer has problems...).
63   */
64  public class CSVParserTest {
65  
66      private static final Charset UTF_8 = StandardCharsets.UTF_8;
67  
68      private static final String UTF_8_NAME = UTF_8.name();
69  
70      private static final String CSV_INPUT = "a,b,c,d\n" + " a , b , 1 2 \n" + "\"foo baar\", b,\n"
71      // + " \"foo\n,,\n\"\",,\n\\\"\",d,e\n";
72          + "   \"foo\n,,\n\"\",,\n\"\"\",d,e\n"; // changed to use standard CSV escaping
73  
74      private static final String CSV_INPUT_1 = "a,b,c,d";
75  
76      private static final String CSV_INPUT_2 = "a,b,1 2";
77  
78      private static final String[][] RESULT = {{"a", "b", "c", "d"}, {"a", "b", "1 2"}, {"foo baar", "b", ""},
79          {"foo\n,,\n\",,\n\"", "d", "e"}};
80  
81      private BOMInputStream createBOMInputStream(final String resource) throws IOException {
82          final URL url = ClassLoader.getSystemClassLoader().getResource(resource);
83          return new BOMInputStream(url.openStream());
84      }
85  
86      private void parseFully(final CSVParser parser) {
87          for (final CSVRecord csvRecord : parser) {
88              assertNotNull(csvRecord);
89          }
90      }
91  
92      @Test
93      public void testBackslashEscaping() throws IOException {
94  
95          // To avoid confusion over the need for escaping chars in java code,
96          // We will test with a forward slash as the escape char, and a single
97          // quote as the encapsulator.
98  
99          final String code = "one,two,three\n" // 0
100             + "'',''\n" // 1) empty encapsulators
101             + "/',/'\n" // 2) single encapsulators
102             + "'/'','/''\n" // 3) single encapsulators encapsulated via escape
103             + "'''',''''\n" // 4) single encapsulators encapsulated via doubling
104             + "/,,/,\n" // 5) separator escaped
105             + "//,//\n" // 6) escape escaped
106             + "'//','//'\n" // 7) escape escaped in encapsulation
107             + "   8   ,   \"quoted \"\" /\" // string\"   \n" // don't eat spaces
108             + "9,   /\n   \n" // escaped newline
109             + "";
110         final String[][] res = {{"one", "two", "three"}, // 0
111             {"", ""}, // 1
112             {"'", "'"}, // 2
113             {"'", "'"}, // 3
114             {"'", "'"}, // 4
115             {",", ","}, // 5
116             {"/", "/"}, // 6
117             {"/", "/"}, // 7
118             {"   8   ", "   \"quoted \"\" /\" / string\"   "}, {"9", "   \n   "},};
119 
120         final CSVFormat format = CSVFormat.newFormat(',').withQuote('\'').withRecordSeparator(CRLF).withEscape('/')
121             .withIgnoreEmptyLines();
122 
123         try (final CSVParser parser = CSVParser.parse(code, format)) {
124             final List<CSVRecord> records = parser.getRecords();
125             assertFalse(records.isEmpty());
126 
127             Utils.compare("Records do not match expected result", res, records);
128         }
129     }
130 
131     @Test
132     public void testBackslashEscaping2() throws IOException {
133 
134         // To avoid confusion over the need for escaping chars in java code,
135         // We will test with a forward slash as the escape char, and a single
136         // quote as the encapsulator.
137 
138         final String code = "" + " , , \n" // 1)
139             + " \t ,  , \n" // 2)
140             + " // , /, , /,\n" // 3)
141             + "";
142         final String[][] res = {{" ", " ", " "}, // 1
143             {" \t ", "  ", " "}, // 2
144             {" / ", " , ", " ,"}, // 3
145         };
146 
147         final CSVFormat format = CSVFormat.newFormat(',').withRecordSeparator(CRLF).withEscape('/')
148             .withIgnoreEmptyLines();
149 
150         try (final CSVParser parser = CSVParser.parse(code, format)) {
151             final List<CSVRecord> records = parser.getRecords();
152             assertFalse(records.isEmpty());
153 
154             Utils.compare("", res, records);
155         }
156     }
157 
158     @Test
159     @Disabled
160     public void testBackslashEscapingOld() throws IOException {
161         final String code = "one,two,three\n" + "on\\\"e,two\n" + "on\"e,two\n" + "one,\"tw\\\"o\"\n"
162             + "one,\"t\\,wo\"\n" + "one,two,\"th,ree\"\n" + "\"a\\\\\"\n" + "a\\,b\n" + "\"a\\\\,b\"";
163         final String[][] res = {{"one", "two", "three"}, {"on\\\"e", "two"}, {"on\"e", "two"}, {"one", "tw\"o"},
164             {"one", "t\\,wo"}, // backslash in quotes only escapes a delimiter (",")
165             {"one", "two", "th,ree"}, {"a\\\\"}, // backslash in quotes only escapes a delimiter (",")
166             {"a\\", "b"}, // a backslash must be returned
167             {"a\\\\,b"} // backslash in quotes only escapes a delimiter (",")
168         };
169         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
170             final List<CSVRecord> records = parser.getRecords();
171             assertEquals(res.length, records.size());
172             assertFalse(records.isEmpty());
173             for (int i = 0; i < res.length; i++) {
174                 assertArrayEquals(res[i], records.get(i).values());
175             }
176         }
177     }
178 
179     @Test
180     @Disabled("CSV-107")
181     public void testBOM() throws IOException {
182         final URL url = ClassLoader.getSystemClassLoader().getResource("org/apache/commons/csv/CSVFileParser/bom.csv");
183         try (final CSVParser parser = CSVParser.parse(url, Charset.forName(UTF_8_NAME), CSVFormat.EXCEL.withHeader())) {
184             for (final CSVRecord record : parser) {
185                 final String string = record.get("Date");
186                 assertNotNull(string);
187                 // System.out.println("date: " + record.get("Date"));
188             }
189         }
190     }
191 
192     @Test
193     public void testBOMInputStream_ParserWithInputStream() throws IOException {
194         try (final BOMInputStream inputStream = createBOMInputStream("org/apache/commons/csv/CSVFileParser/bom.csv");
195             final CSVParser parser = CSVParser.parse(inputStream, UTF_8, CSVFormat.EXCEL.withHeader())) {
196             for (final CSVRecord record : parser) {
197                 final String string = record.get("Date");
198                 assertNotNull(string);
199                 // System.out.println("date: " + record.get("Date"));
200             }
201         }
202     }
203 
204     @Test
205     public void testBOMInputStream_ParserWithReader() throws IOException {
206         try (
207             final Reader reader = new InputStreamReader(
208                 createBOMInputStream("org/apache/commons/csv/CSVFileParser/bom.csv"), UTF_8_NAME);
209             final CSVParser parser = new CSVParser(reader, CSVFormat.EXCEL.withHeader())) {
210             for (final CSVRecord record : parser) {
211                 final String string = record.get("Date");
212                 assertNotNull(string);
213                 // System.out.println("date: " + record.get("Date"));
214             }
215         }
216     }
217 
218     @Test
219     public void testBOMInputStream_parseWithReader() throws IOException {
220         try (
221             final Reader reader = new InputStreamReader(
222                 createBOMInputStream("org/apache/commons/csv/CSVFileParser/bom.csv"), UTF_8_NAME);
223             final CSVParser parser = CSVParser.parse(reader, CSVFormat.EXCEL.withHeader())) {
224             for (final CSVRecord record : parser) {
225                 final String string = record.get("Date");
226                 assertNotNull(string);
227                 // System.out.println("date: " + record.get("Date"));
228             }
229         }
230     }
231 
232     @Test
233     public void testCarriageReturnEndings() throws IOException {
234         final String code = "foo\rbaar,\rhello,world\r,kanu";
235         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
236             final List<CSVRecord> records = parser.getRecords();
237             assertEquals(4, records.size());
238         }
239     }
240 
241     @Test
242     public void testCarriageReturnLineFeedEndings() throws IOException {
243         final String code = "foo\r\nbaar,\r\nhello,world\r\n,kanu";
244         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
245             final List<CSVRecord> records = parser.getRecords();
246             assertEquals(4, records.size());
247         }
248     }
249 
250     @Test
251     public void testClose() throws Exception {
252         final Reader in = new StringReader("# comment\na,b,c\n1,2,3\nx,y,z");
253         final Iterator<CSVRecord> records;
254         try (final CSVParser parser = CSVFormat.DEFAULT.withCommentMarker('#').withHeader().parse(in)) {
255             records = parser.iterator();
256             assertTrue(records.hasNext());
257         }
258         assertFalse(records.hasNext());
259         assertThrows(NoSuchElementException.class, records::next);
260     }
261 
262     @Test
263     public void testCSV235() throws IOException {
264         final String dqString = "\"aaa\",\"b\"\"bb\",\"ccc\""; // "aaa","b""bb","ccc"
265         final Iterator<CSVRecord> records = CSVFormat.RFC4180.parse(new StringReader(dqString)).iterator();
266         final CSVRecord record = records.next();
267         assertFalse(records.hasNext());
268         assertEquals(3, record.size());
269         assertEquals("aaa", record.get(0));
270         assertEquals("b\"bb", record.get(1));
271         assertEquals("ccc", record.get(2));
272     }
273 
274     @Test
275     public void testCSV57() throws Exception {
276         try (final CSVParser parser = CSVParser.parse("", CSVFormat.DEFAULT)) {
277             final List<CSVRecord> list = parser.getRecords();
278             assertNotNull(list);
279             assertEquals(0, list.size());
280         }
281     }
282 
283     @Test
284     public void testDefaultFormat() throws IOException {
285         final String code = "" + "a,b#\n" // 1)
286             + "\"\n\",\" \",#\n" // 2)
287             + "#,\"\"\n" // 3)
288             + "# Final comment\n"// 4)
289         ;
290         final String[][] res = {{"a", "b#"}, {"\n", " ", "#"}, {"#", ""}, {"# Final comment"}};
291 
292         CSVFormat format = CSVFormat.DEFAULT;
293         assertFalse(format.isCommentMarkerSet());
294         final String[][] res_comments = {{"a", "b#"}, {"\n", " ", "#"},};
295 
296         try (final CSVParser parser = CSVParser.parse(code, format)) {
297             final List<CSVRecord> records = parser.getRecords();
298             assertFalse(records.isEmpty());
299 
300             Utils.compare("Failed to parse without comments", res, records);
301 
302             format = CSVFormat.DEFAULT.withCommentMarker('#');
303         }
304         try (final CSVParser parser = CSVParser.parse(code, format)) {
305             final List<CSVRecord> records = parser.getRecords();
306 
307             Utils.compare("Failed to parse with comments", res_comments, records);
308         }
309     }
310 
311     @Test
312     public void testDuplicateHeadersAllowedByDefault() throws Exception {
313         CSVParser.parse("a,b,a\n1,2,3\nx,y,z", CSVFormat.DEFAULT.withHeader());
314     }
315 
316     @Test
317     public void testDuplicateHeadersNotAllowed() {
318         assertThrows(IllegalArgumentException.class, () -> CSVParser.parse("a,b,a\n1,2,3\nx,y,z",
319             CSVFormat.DEFAULT.withHeader().withAllowDuplicateHeaderNames(false)));
320     }
321 
322     @Test
323     public void testEmptyFile() throws Exception {
324         try (final CSVParser parser = CSVParser.parse(Paths.get("src/test/resources/org/apache/commons/csv/empty.txt"),
325             StandardCharsets.UTF_8, CSVFormat.DEFAULT)) {
326             assertNull(parser.nextRecord());
327         }
328     }
329 
330     @Test
331     public void testEmptyFileHeaderParsing() throws Exception {
332         try (final CSVParser parser = CSVParser.parse("", CSVFormat.DEFAULT.withFirstRecordAsHeader())) {
333             assertNull(parser.nextRecord());
334             assertTrue(parser.getHeaderNames().isEmpty());
335         }
336     }
337 
338     @Test
339     public void testEmptyLineBehaviorCSV() throws Exception {
340         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"};
341         final String[][] res = {{"hello", ""} // CSV format ignores empty lines
342         };
343         for (final String code : codes) {
344             try (final CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
345                 final List<CSVRecord> records = parser.getRecords();
346                 assertEquals(res.length, records.size());
347                 assertFalse(records.isEmpty());
348                 for (int i = 0; i < res.length; i++) {
349                     assertArrayEquals(res[i], records.get(i).values());
350                 }
351             }
352         }
353     }
354 
355     @Test
356     public void testEmptyLineBehaviorExcel() throws Exception {
357         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"};
358         final String[][] res = {{"hello", ""}, {""}, // Excel format does not ignore empty lines
359             {""}};
360         for (final String code : codes) {
361             try (final CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
362                 final List<CSVRecord> records = parser.getRecords();
363                 assertEquals(res.length, records.size());
364                 assertFalse(records.isEmpty());
365                 for (int i = 0; i < res.length; i++) {
366                     assertArrayEquals(res[i], records.get(i).values());
367                 }
368             }
369         }
370     }
371 
372     @Test
373     public void testEmptyString() throws Exception {
374         try (final CSVParser parser = CSVParser.parse("", CSVFormat.DEFAULT)) {
375             assertNull(parser.nextRecord());
376         }
377     }
378 
379     @Test
380     public void testEndOfFileBehaviorCSV() throws Exception {
381         final String[] codes = {"hello,\r\n\r\nworld,\r\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\r\n",
382             "hello,\r\n\r\nworld,\"\"", "hello,\r\n\r\nworld,\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\n",
383             "hello,\r\n\r\nworld,\"\""};
384         final String[][] res = {{"hello", ""}, // CSV format ignores empty lines
385             {"world", ""}};
386         for (final String code : codes) {
387             try (final CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
388                 final List<CSVRecord> records = parser.getRecords();
389                 assertEquals(res.length, records.size());
390                 assertFalse(records.isEmpty());
391                 for (int i = 0; i < res.length; i++) {
392                     assertArrayEquals(res[i], records.get(i).values());
393                 }
394             }
395         }
396     }
397 
398     @Test
399     public void testEndOfFileBehaviorExcel() throws Exception {
400         final String[] codes = {"hello,\r\n\r\nworld,\r\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\r\n",
401             "hello,\r\n\r\nworld,\"\"", "hello,\r\n\r\nworld,\n", "hello,\r\n\r\nworld,", "hello,\r\n\r\nworld,\"\"\n",
402             "hello,\r\n\r\nworld,\"\""};
403         final String[][] res = {{"hello", ""}, {""}, // Excel format does not ignore empty lines
404             {"world", ""}};
405 
406         for (final String code : codes) {
407             try (final CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
408                 final List<CSVRecord> records = parser.getRecords();
409                 assertEquals(res.length, records.size());
410                 assertFalse(records.isEmpty());
411                 for (int i = 0; i < res.length; i++) {
412                     assertArrayEquals(res[i], records.get(i).values());
413                 }
414             }
415         }
416     }
417 
418     @Test
419     public void testExcelFormat1() throws IOException {
420         final String code = "value1,value2,value3,value4\r\na,b,c,d\r\n  x,,,"
421             + "\r\n\r\n\"\"\"hello\"\"\",\"  \"\"world\"\"\",\"abc\ndef\",\r\n";
422         final String[][] res = {{"value1", "value2", "value3", "value4"}, {"a", "b", "c", "d"}, {"  x", "", "", ""},
423             {""}, {"\"hello\"", "  \"world\"", "abc\ndef", ""}};
424         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
425             final List<CSVRecord> records = parser.getRecords();
426             assertEquals(res.length, records.size());
427             assertFalse(records.isEmpty());
428             for (int i = 0; i < res.length; i++) {
429                 assertArrayEquals(res[i], records.get(i).values());
430             }
431         }
432     }
433 
434     @Test
435     public void testExcelFormat2() throws Exception {
436         final String code = "foo,baar\r\n\r\nhello,\r\n\r\nworld,\r\n";
437         final String[][] res = {{"foo", "baar"}, {""}, {"hello", ""}, {""}, {"world", ""}};
438         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
439             final List<CSVRecord> records = parser.getRecords();
440             assertEquals(res.length, records.size());
441             assertFalse(records.isEmpty());
442             for (int i = 0; i < res.length; i++) {
443                 assertArrayEquals(res[i], records.get(i).values());
444             }
445         }
446     }
447 
448     /**
449      * Tests an exported Excel worksheet with a header row and rows that have more columns than the headers
450      *
451      * @throws Exception
452      */
453     @Test
454     public void testExcelHeaderCountLessThanData() throws Exception {
455         final String code = "A,B,C,,\r\na,b,c,d,e\r\n";
456         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL.withHeader())) {
457             for (final CSVRecord record : parser.getRecords()) {
458                 assertEquals("a", record.get("A"));
459                 assertEquals("b", record.get("B"));
460                 assertEquals("c", record.get("C"));
461             }
462         }
463     }
464 
465     @Test
466     public void testFirstEndOfLineCr() throws IOException {
467         final String data = "foo\rbaar,\rhello,world\r,kanu";
468         try (final CSVParser parser = CSVParser.parse(data, CSVFormat.DEFAULT)) {
469             final List<CSVRecord> records = parser.getRecords();
470             assertEquals(4, records.size());
471             assertEquals("\r", parser.getFirstEndOfLine());
472         }
473     }
474 
475     @Test
476     public void testFirstEndOfLineCrLf() throws IOException {
477         final String data = "foo\r\nbaar,\r\nhello,world\r\n,kanu";
478         try (final CSVParser parser = CSVParser.parse(data, CSVFormat.DEFAULT)) {
479             final List<CSVRecord> records = parser.getRecords();
480             assertEquals(4, records.size());
481             assertEquals("\r\n", parser.getFirstEndOfLine());
482         }
483     }
484 
485     @Test
486     public void testFirstEndOfLineLf() throws IOException {
487         final String data = "foo\nbaar,\nhello,world\n,kanu";
488         try (final CSVParser parser = CSVParser.parse(data, CSVFormat.DEFAULT)) {
489             final List<CSVRecord> records = parser.getRecords();
490             assertEquals(4, records.size());
491             assertEquals("\n", parser.getFirstEndOfLine());
492         }
493     }
494 
495     @Test
496     public void testForEach() throws Exception {
497         final List<CSVRecord> records = new ArrayList<>();
498         try (final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z")) {
499             for (final CSVRecord record : CSVFormat.DEFAULT.parse(in)) {
500                 records.add(record);
501             }
502             assertEquals(3, records.size());
503             assertArrayEquals(new String[] {"a", "b", "c"}, records.get(0).values());
504             assertArrayEquals(new String[] {"1", "2", "3"}, records.get(1).values());
505             assertArrayEquals(new String[] {"x", "y", "z"}, records.get(2).values());
506         }
507     }
508 
509     @Test
510     public void testGetHeaderMap() throws Exception {
511         try (final CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z",
512             CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
513             final Map<String, Integer> headerMap = parser.getHeaderMap();
514             final Iterator<String> columnNames = headerMap.keySet().iterator();
515             // Headers are iterated in column order.
516             assertEquals("A", columnNames.next());
517             assertEquals("B", columnNames.next());
518             assertEquals("C", columnNames.next());
519             final Iterator<CSVRecord> records = parser.iterator();
520 
521             // Parse to make sure getHeaderMap did not have a side-effect.
522             for (int i = 0; i < 3; i++) {
523                 assertTrue(records.hasNext());
524                 final CSVRecord record = records.next();
525                 assertEquals(record.get(0), record.get("A"));
526                 assertEquals(record.get(1), record.get("B"));
527                 assertEquals(record.get(2), record.get("C"));
528             }
529 
530             assertFalse(records.hasNext());
531         }
532     }
533 
534     @Test
535     public void testGetHeaderNames() throws IOException {
536         try (final CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z",
537             CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
538             final Map<String, Integer> nameIndexMap = parser.getHeaderMap();
539             final List<String> headerNames = parser.getHeaderNames();
540             assertNotNull(headerNames);
541             assertEquals(nameIndexMap.size(), headerNames.size());
542             for (int i = 0; i < headerNames.size(); i++) {
543                 final String name = headerNames.get(i);
544                 assertEquals(i, nameIndexMap.get(name).intValue());
545             }
546         }
547     }
548 
549     @Test
550     public void testGetHeaderNamesReadOnly() throws IOException {
551         try (final CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z",
552             CSVFormat.DEFAULT.withHeader("A", "B", "C"))) {
553             final List<String> headerNames = parser.getHeaderNames();
554             assertNotNull(headerNames);
555             assertThrows(UnsupportedOperationException.class, () -> headerNames.add("This is a read-only list."));
556         }
557     }
558 
559     @Test
560     public void testGetLine() throws IOException {
561         try (final CSVParser parser = CSVParser.parse(CSV_INPUT, CSVFormat.DEFAULT.withIgnoreSurroundingSpaces())) {
562             for (final String[] re : RESULT) {
563                 assertArrayEquals(re, parser.nextRecord().values());
564             }
565 
566             assertNull(parser.nextRecord());
567         }
568     }
569 
570     @Test
571     public void testGetLineNumberWithCR() throws Exception {
572         this.validateLineNumbers(String.valueOf(CR));
573     }
574 
575     @Test
576     public void testGetLineNumberWithCRLF() throws Exception {
577         this.validateLineNumbers(CRLF);
578     }
579 
580     @Test
581     public void testGetLineNumberWithLF() throws Exception {
582         this.validateLineNumbers(String.valueOf(LF));
583     }
584 
585     @Test
586     public void testGetOneLine() throws IOException {
587         try (final CSVParser parser = CSVParser.parse(CSV_INPUT_1, CSVFormat.DEFAULT)) {
588             final CSVRecord record = parser.getRecords().get(0);
589             assertArrayEquals(RESULT[0], record.values());
590         }
591     }
592 
593     /**
594      * Tests reusing a parser to process new string records one at a time as they are being discovered. See [CSV-110].
595      *
596      * @throws IOException when an I/O error occurs.
597      */
598     @Test
599     public void testGetOneLineOneParser() throws IOException {
600         final CSVFormat format = CSVFormat.DEFAULT;
601         try (final PipedWriter writer = new PipedWriter();
602             final CSVParser parser = new CSVParser(new PipedReader(writer), format)) {
603             writer.append(CSV_INPUT_1);
604             writer.append(format.getRecordSeparator());
605             final CSVRecord record1 = parser.nextRecord();
606             assertArrayEquals(RESULT[0], record1.values());
607             writer.append(CSV_INPUT_2);
608             writer.append(format.getRecordSeparator());
609             final CSVRecord record2 = parser.nextRecord();
610             assertArrayEquals(RESULT[1], record2.values());
611         }
612     }
613 
614     @Test
615     public void testGetRecordNumberWithCR() throws Exception {
616         this.validateRecordNumbers(String.valueOf(CR));
617     }
618 
619     @Test
620     public void testGetRecordNumberWithCRLF() throws Exception {
621         this.validateRecordNumbers(CRLF);
622     }
623 
624     @Test
625     public void testGetRecordNumberWithLF() throws Exception {
626         this.validateRecordNumbers(String.valueOf(LF));
627     }
628 
629     @Test
630     public void testGetRecordPositionWithCRLF() throws Exception {
631         this.validateRecordPosition(CRLF);
632     }
633 
634     @Test
635     public void testGetRecordPositionWithLF() throws Exception {
636         this.validateRecordPosition(String.valueOf(LF));
637     }
638 
639     @Test
640     public void testGetRecords() throws IOException {
641         try (final CSVParser parser = CSVParser.parse(CSV_INPUT, CSVFormat.DEFAULT.withIgnoreSurroundingSpaces())) {
642             final List<CSVRecord> records = parser.getRecords();
643             assertEquals(RESULT.length, records.size());
644             assertFalse(records.isEmpty());
645             for (int i = 0; i < RESULT.length; i++) {
646                 assertArrayEquals(RESULT[i], records.get(i).values());
647             }
648         }
649     }
650 
651     @Test
652     public void testGetRecordWithMultiLineValues() throws Exception {
653         try (final CSVParser parser = CSVParser.parse(
654             "\"a\r\n1\",\"a\r\n2\"" + CRLF + "\"b\r\n1\",\"b\r\n2\"" + CRLF + "\"c\r\n1\",\"c\r\n2\"",
655             CSVFormat.DEFAULT.withRecordSeparator(CRLF))) {
656             CSVRecord record;
657             assertEquals(0, parser.getRecordNumber());
658             assertEquals(0, parser.getCurrentLineNumber());
659             assertNotNull(record = parser.nextRecord());
660             assertEquals(3, parser.getCurrentLineNumber());
661             assertEquals(1, record.getRecordNumber());
662             assertEquals(1, parser.getRecordNumber());
663             assertNotNull(record = parser.nextRecord());
664             assertEquals(6, parser.getCurrentLineNumber());
665             assertEquals(2, record.getRecordNumber());
666             assertEquals(2, parser.getRecordNumber());
667             assertNotNull(record = parser.nextRecord());
668             assertEquals(9, parser.getCurrentLineNumber());
669             assertEquals(3, record.getRecordNumber());
670             assertEquals(3, parser.getRecordNumber());
671             assertNull(record = parser.nextRecord());
672             assertEquals(9, parser.getCurrentLineNumber());
673             assertEquals(3, parser.getRecordNumber());
674         }
675     }
676 
677     @Test
678     public void testHeader() throws Exception {
679         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
680 
681         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader().parse(in).iterator();
682 
683         for (int i = 0; i < 2; i++) {
684             assertTrue(records.hasNext());
685             final CSVRecord record = records.next();
686             assertEquals(record.get(0), record.get("a"));
687             assertEquals(record.get(1), record.get("b"));
688             assertEquals(record.get(2), record.get("c"));
689         }
690 
691         assertFalse(records.hasNext());
692     }
693 
694     @Test
695     public void testHeaderComment() throws Exception {
696         final Reader in = new StringReader("# comment\na,b,c\n1,2,3\nx,y,z");
697 
698         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withCommentMarker('#').withHeader().parse(in).iterator();
699 
700         for (int i = 0; i < 2; i++) {
701             assertTrue(records.hasNext());
702             final CSVRecord record = records.next();
703             assertEquals(record.get(0), record.get("a"));
704             assertEquals(record.get(1), record.get("b"));
705             assertEquals(record.get(2), record.get("c"));
706         }
707 
708         assertFalse(records.hasNext());
709     }
710 
711     @Test
712     public void testHeaderMissing() throws Exception {
713         final Reader in = new StringReader("a,,c\n1,2,3\nx,y,z");
714 
715         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader().withAllowMissingColumnNames().parse(in)
716             .iterator();
717 
718         for (int i = 0; i < 2; i++) {
719             assertTrue(records.hasNext());
720             final CSVRecord record = records.next();
721             assertEquals(record.get(0), record.get("a"));
722             assertEquals(record.get(2), record.get("c"));
723         }
724 
725         assertFalse(records.hasNext());
726     }
727 
728     @Test
729     public void testHeaderMissingWithNull() throws Exception {
730         final Reader in = new StringReader("a,,c,,e\n1,2,3,4,5\nv,w,x,y,z");
731         CSVFormat.DEFAULT.withHeader().withNullString("").withAllowMissingColumnNames().parse(in).iterator();
732     }
733 
734     @Test
735     public void testHeadersMissing() throws Exception {
736         final Reader in = new StringReader("a,,c,,e\n1,2,3,4,5\nv,w,x,y,z");
737         CSVFormat.DEFAULT.withHeader().withAllowMissingColumnNames().parse(in).iterator();
738     }
739 
740     @Test
741     public void testHeadersMissingException() {
742         final Reader in = new StringReader("a,,c,,e\n1,2,3,4,5\nv,w,x,y,z");
743         assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withHeader().parse(in).iterator());
744     }
745 
746     @Test
747     public void testHeadersMissingOneColumnException() throws Exception {
748         final Reader in = new StringReader("a,,c,d,e\n1,2,3,4,5\nv,w,x,y,z");
749         assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withHeader().parse(in).iterator());
750     }
751 
752     @Test
753     public void testHeadersWithNullColumnName() throws IOException {
754         final Reader in = new StringReader("header1,null,header3\n1,2,3\n4,5,6");
755         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader().withNullString("null")
756             .withAllowMissingColumnNames().parse(in).iterator();
757         final CSVRecord record = records.next();
758         // Expect the null header to be missing
759         assertEquals(Arrays.asList("header1", "header3"), record.getParser().getHeaderNames());
760         assertEquals(2, record.getParser().getHeaderMap().size());
761     }
762 
763     @Test
764     public void testIgnoreCaseHeaderMapping() throws Exception {
765         final Reader reader = new StringReader("1,2,3");
766         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("One", "TWO", "three").withIgnoreHeaderCase()
767             .parse(reader).iterator();
768         final CSVRecord record = records.next();
769         assertEquals("1", record.get("one"));
770         assertEquals("2", record.get("two"));
771         assertEquals("3", record.get("THREE"));
772     }
773 
774     @Test
775     public void testIgnoreEmptyLines() throws IOException {
776         final String code = "\nfoo,baar\n\r\n,\n\n,world\r\n\n";
777         // String code = "world\r\n\n";
778         // String code = "foo;baar\r\n\r\nhello;\r\n\r\nworld;\r\n";
779         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
780             final List<CSVRecord> records = parser.getRecords();
781             assertEquals(3, records.size());
782         }
783     }
784 
785     @Test
786     public void testInvalidFormat() {
787         assertThrows(IllegalArgumentException.class, () -> CSVFormat.DEFAULT.withDelimiter(CR));
788     }
789 
790     @Test
791     public void testIterator() throws Exception {
792         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
793 
794         final Iterator<CSVRecord> iterator = CSVFormat.DEFAULT.parse(in).iterator();
795 
796         assertTrue(iterator.hasNext());
797         assertThrows(UnsupportedOperationException.class, iterator::remove);
798         assertArrayEquals(new String[] {"a", "b", "c"}, iterator.next().values());
799         assertArrayEquals(new String[] {"1", "2", "3"}, iterator.next().values());
800         assertTrue(iterator.hasNext());
801         assertTrue(iterator.hasNext());
802         assertTrue(iterator.hasNext());
803         assertArrayEquals(new String[] {"x", "y", "z"}, iterator.next().values());
804         assertFalse(iterator.hasNext());
805 
806         assertThrows(NoSuchElementException.class, iterator::next);
807     }
808 
809     @Test
810     public void testIteratorSequenceBreaking() throws IOException {
811         final String fiveRows = "1\n2\n3\n4\n5\n";
812 
813         // Iterator hasNext() shouldn't break sequence
814         try (CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows))) {
815 
816             final Iterator<CSVRecord> iter = parser.iterator();
817             int recordNumber = 0;
818             while (iter.hasNext()) {
819                 final CSVRecord record = iter.next();
820                 recordNumber++;
821                 assertEquals(String.valueOf(recordNumber), record.get(0));
822                 if (recordNumber >= 2) {
823                     break;
824                 }
825             }
826             iter.hasNext();
827             while (iter.hasNext()) {
828                 final CSVRecord record = iter.next();
829                 recordNumber++;
830                 assertEquals(String.valueOf(recordNumber), record.get(0));
831             }
832         }
833 
834         // Consecutive enhanced for loops shouldn't break sequence
835         try (CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows))) {
836             int recordNumber = 0;
837             for (final CSVRecord record : parser) {
838                 recordNumber++;
839                 assertEquals(String.valueOf(recordNumber), record.get(0));
840                 if (recordNumber >= 2) {
841                     break;
842                 }
843             }
844             for (final CSVRecord record : parser) {
845                 recordNumber++;
846                 assertEquals(String.valueOf(recordNumber), record.get(0));
847             }
848         }
849 
850         // Consecutive enhanced for loops with hasNext() peeking shouldn't break sequence
851         try (CSVParser parser = CSVFormat.DEFAULT.parse(new StringReader(fiveRows))) {
852             int recordNumber = 0;
853             for (final CSVRecord record : parser) {
854                 recordNumber++;
855                 assertEquals(String.valueOf(recordNumber), record.get(0));
856                 if (recordNumber >= 2) {
857                     break;
858                 }
859             }
860             parser.iterator().hasNext();
861             for (final CSVRecord record : parser) {
862                 recordNumber++;
863                 assertEquals(String.valueOf(recordNumber), record.get(0));
864             }
865         }
866     }
867 
868     @Test
869     public void testLineFeedEndings() throws IOException {
870         final String code = "foo\nbaar,\nhello,world\n,kanu";
871         try (final CSVParser parser = CSVParser.parse(code, CSVFormat.DEFAULT)) {
872             final List<CSVRecord> records = parser.getRecords();
873             assertEquals(4, records.size());
874         }
875     }
876 
877     @Test
878     public void testMappedButNotSetAsOutlook2007ContactExport() throws Exception {
879         final Reader in = new StringReader("a,b,c\n1,2\nx,y,z");
880         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("A", "B", "C").withSkipHeaderRecord().parse(in)
881             .iterator();
882         CSVRecord record;
883 
884         // 1st record
885         record = records.next();
886         assertTrue(record.isMapped("A"));
887         assertTrue(record.isMapped("B"));
888         assertTrue(record.isMapped("C"));
889         assertTrue(record.isSet("A"));
890         assertTrue(record.isSet("B"));
891         assertFalse(record.isSet("C"));
892         assertEquals("1", record.get("A"));
893         assertEquals("2", record.get("B"));
894         assertFalse(record.isConsistent());
895 
896         // 2nd record
897         record = records.next();
898         assertTrue(record.isMapped("A"));
899         assertTrue(record.isMapped("B"));
900         assertTrue(record.isMapped("C"));
901         assertTrue(record.isSet("A"));
902         assertTrue(record.isSet("B"));
903         assertTrue(record.isSet("C"));
904         assertEquals("x", record.get("A"));
905         assertEquals("y", record.get("B"));
906         assertEquals("z", record.get("C"));
907         assertTrue(record.isConsistent());
908 
909         assertFalse(records.hasNext());
910     }
911 
912     @Test
913     @Disabled
914     public void testMongoDbCsv() throws Exception {
915         try (final CSVParser parser = CSVParser.parse("\"a a\",b,c" + LF + "d,e,f", CSVFormat.MONGODB_CSV)) {
916             final Iterator<CSVRecord> itr1 = parser.iterator();
917             final Iterator<CSVRecord> itr2 = parser.iterator();
918 
919             final CSVRecord first = itr1.next();
920             assertEquals("a a", first.get(0));
921             assertEquals("b", first.get(1));
922             assertEquals("c", first.get(2));
923 
924             final CSVRecord second = itr2.next();
925             assertEquals("d", second.get(0));
926             assertEquals("e", second.get(1));
927             assertEquals("f", second.get(2));
928         }
929     }
930 
931     @Test
932     // TODO this may lead to strange behavior, throw an exception if iterator() has already been called?
933     public void testMultipleIterators() throws Exception {
934         try (final CSVParser parser = CSVParser.parse("a,b,c" + CRLF + "d,e,f", CSVFormat.DEFAULT)) {
935             final Iterator<CSVRecord> itr1 = parser.iterator();
936 
937             final CSVRecord first = itr1.next();
938             assertEquals("a", first.get(0));
939             assertEquals("b", first.get(1));
940             assertEquals("c", first.get(2));
941 
942             final CSVRecord second = itr1.next();
943             assertEquals("d", second.get(0));
944             assertEquals("e", second.get(1));
945             assertEquals("f", second.get(2));
946         }
947     }
948 
949     @Test
950     public void testNewCSVParserNullReaderFormat() {
951         assertThrows(NullPointerException.class, () -> new CSVParser(null, CSVFormat.DEFAULT));
952     }
953 
954     @Test
955     public void testNewCSVParserReaderNullFormat() {
956         assertThrows(NullPointerException.class, () -> new CSVParser(new StringReader(""), null));
957     }
958 
959     @Test
960     public void testNoHeaderMap() throws Exception {
961         try (final CSVParser parser = CSVParser.parse("a,b,c\n1,2,3\nx,y,z", CSVFormat.DEFAULT)) {
962             assertNull(parser.getHeaderMap());
963         }
964     }
965 
966     @Test
967     public void testNotValueCSV() throws IOException {
968         final String source = "#";
969         final CSVFormat csvFormat = CSVFormat.DEFAULT.withCommentMarker('#');
970         final CSVParser csvParser = csvFormat.parse(new StringReader(source));
971         final CSVRecord csvRecord = csvParser.nextRecord();
972         assertNull(csvRecord);
973     }
974 
975     @Test
976     public void testParse() throws Exception {
977         final ClassLoader loader = ClassLoader.getSystemClassLoader();
978         final URL url = loader.getResource("org/apache/commons/csv/CSVFileParser/test.csv");
979         final CSVFormat format = CSVFormat.DEFAULT.withHeader("A", "B", "C", "D");
980         final Charset charset = StandardCharsets.UTF_8;
981 
982         try (final CSVParser parser = CSVParser.parse(new InputStreamReader(url.openStream(), charset), format)) {
983             parseFully(parser);
984         }
985         try (final CSVParser parser = CSVParser.parse(new String(Files.readAllBytes(Paths.get(url.toURI())), charset),
986             format)) {
987             parseFully(parser);
988         }
989         try (final CSVParser parser = CSVParser.parse(new File(url.toURI()), charset, format)) {
990             parseFully(parser);
991         }
992         try (final CSVParser parser = CSVParser.parse(url.openStream(), charset, format)) {
993             parseFully(parser);
994         }
995         try (final CSVParser parser = CSVParser.parse(Paths.get(url.toURI()), charset, format)) {
996             parseFully(parser);
997         }
998         try (final CSVParser parser = CSVParser.parse(url, charset, format)) {
999             parseFully(parser);
1000         }
1001         try (final CSVParser parser = new CSVParser(new InputStreamReader(url.openStream(), charset), format)) {
1002             parseFully(parser);
1003         }
1004         try (final CSVParser parser = new CSVParser(new InputStreamReader(url.openStream(), charset), format,
1005             /* characterOffset= */0, /* recordNumber= */1)) {
1006             parseFully(parser);
1007         }
1008     }
1009 
1010     @Test
1011     public void testParseFileNullFormat() {
1012         assertThrows(NullPointerException.class,
1013             () -> CSVParser.parse(new File("CSVFileParser/test.csv"), Charset.defaultCharset(), null));
1014     }
1015 
1016     @Test
1017     public void testParseNullFileFormat() {
1018         assertThrows(NullPointerException.class,
1019             () -> CSVParser.parse((File) null, Charset.defaultCharset(), CSVFormat.DEFAULT));
1020     }
1021 
1022     @Test
1023     public void testParseNullPathFormat() {
1024         assertThrows(NullPointerException.class,
1025             () -> CSVParser.parse((Path) null, Charset.defaultCharset(), CSVFormat.DEFAULT));
1026     }
1027 
1028     @Test
1029     public void testParseNullStringFormat() {
1030         assertThrows(NullPointerException.class, () -> CSVParser.parse((String) null, CSVFormat.DEFAULT));
1031     }
1032 
1033     @Test
1034     public void testParseNullUrlCharsetFormat() {
1035         assertThrows(NullPointerException.class,
1036             () -> CSVParser.parse((URL) null, Charset.defaultCharset(), CSVFormat.DEFAULT));
1037     }
1038 
1039     @Test
1040     public void testParserUrlNullCharsetFormat() {
1041         assertThrows(NullPointerException.class,
1042             () -> CSVParser.parse(new URL("https://commons.apache.org"), null, CSVFormat.DEFAULT));
1043     }
1044 
1045     @Test
1046     public void testParseStringNullFormat() {
1047         assertThrows(NullPointerException.class, () -> CSVParser.parse("csv data", (CSVFormat) null));
1048     }
1049 
1050     @Test
1051     public void testParseUrlCharsetNullFormat() {
1052         assertThrows(NullPointerException.class,
1053             () -> CSVParser.parse(new URL("https://commons.apache.org"), Charset.defaultCharset(), null));
1054     }
1055 
1056     @Test
1057     public void testParseWithDelimiterStringWithEscape() throws IOException {
1058         final String source = "a![!|!]b![|]c[|]xyz\r\nabc[abc][|]xyz";
1059         final CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setEscape('!').build();
1060         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1061             CSVRecord csvRecord = csvParser.nextRecord();
1062             assertEquals("a[|]b![|]c", csvRecord.get(0));
1063             assertEquals("xyz", csvRecord.get(1));
1064             csvRecord = csvParser.nextRecord();
1065             assertEquals("abc[abc]", csvRecord.get(0));
1066             assertEquals("xyz", csvRecord.get(1));
1067         }
1068     }
1069 
1070     @Test
1071     public void testParseWithDelimiterStringWithQuote() throws IOException {
1072         final String source = "'a[|]b[|]c'[|]xyz\r\nabc[abc][|]xyz";
1073         final CSVFormat csvFormat = CSVFormat.DEFAULT.builder().setDelimiter("[|]").setQuote('\'').build();
1074         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1075             CSVRecord csvRecord = csvParser.nextRecord();
1076             assertEquals("a[|]b[|]c", csvRecord.get(0));
1077             assertEquals("xyz", csvRecord.get(1));
1078             csvRecord = csvParser.nextRecord();
1079             assertEquals("abc[abc]", csvRecord.get(0));
1080             assertEquals("xyz", csvRecord.get(1));
1081         }
1082     }
1083 
1084     @Test
1085     public void testParseWithDelimiterWithEscape() throws IOException {
1086         final String source = "a!,b!,c,xyz";
1087         final CSVFormat csvFormat = CSVFormat.DEFAULT.withEscape('!');
1088         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1089             final CSVRecord csvRecord = csvParser.nextRecord();
1090             assertEquals("a,b,c", csvRecord.get(0));
1091             assertEquals("xyz", csvRecord.get(1));
1092         }
1093     }
1094 
1095     @Test
1096     public void testParseWithDelimiterWithQuote() throws IOException {
1097         final String source = "'a,b,c',xyz";
1098         final CSVFormat csvFormat = CSVFormat.DEFAULT.withQuote('\'');
1099         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1100             final CSVRecord csvRecord = csvParser.nextRecord();
1101             assertEquals("a,b,c", csvRecord.get(0));
1102             assertEquals("xyz", csvRecord.get(1));
1103         }
1104     }
1105 
1106     @Test
1107     public void testParseWithQuoteThrowsException() {
1108         final CSVFormat csvFormat = CSVFormat.DEFAULT.withQuote('\'');
1109         assertThrows(IOException.class, () -> csvFormat.parse(new StringReader("'a,b,c','")).nextRecord());
1110         assertThrows(IOException.class, () -> csvFormat.parse(new StringReader("'a,b,c'abc,xyz")).nextRecord());
1111         assertThrows(IOException.class, () -> csvFormat.parse(new StringReader("'abc'a,b,c',xyz")).nextRecord());
1112     }
1113 
1114     @Test
1115     public void testParseWithQuoteWithEscape() throws IOException {
1116         final String source = "'a?,b?,c?d',xyz";
1117         final CSVFormat csvFormat = CSVFormat.DEFAULT.withQuote('\'').withEscape('?');
1118         try (CSVParser csvParser = csvFormat.parse(new StringReader(source))) {
1119             final CSVRecord csvRecord = csvParser.nextRecord();
1120             assertEquals("a,b,c?d", csvRecord.get(0));
1121             assertEquals("xyz", csvRecord.get(1));
1122         }
1123     }
1124 
1125     @Test
1126     public void testProvidedHeader() throws Exception {
1127         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1128 
1129         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("A", "B", "C").parse(in).iterator();
1130 
1131         for (int i = 0; i < 3; i++) {
1132             assertTrue(records.hasNext());
1133             final CSVRecord record = records.next();
1134             assertTrue(record.isMapped("A"));
1135             assertTrue(record.isMapped("B"));
1136             assertTrue(record.isMapped("C"));
1137             assertFalse(record.isMapped("NOT MAPPED"));
1138             assertEquals(record.get(0), record.get("A"));
1139             assertEquals(record.get(1), record.get("B"));
1140             assertEquals(record.get(2), record.get("C"));
1141         }
1142 
1143         assertFalse(records.hasNext());
1144     }
1145 
1146     @Test
1147     public void testProvidedHeaderAuto() throws Exception {
1148         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1149 
1150         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader().parse(in).iterator();
1151 
1152         for (int i = 0; i < 2; i++) {
1153             assertTrue(records.hasNext());
1154             final CSVRecord record = records.next();
1155             assertTrue(record.isMapped("a"));
1156             assertTrue(record.isMapped("b"));
1157             assertTrue(record.isMapped("c"));
1158             assertFalse(record.isMapped("NOT MAPPED"));
1159             assertEquals(record.get(0), record.get("a"));
1160             assertEquals(record.get(1), record.get("b"));
1161             assertEquals(record.get(2), record.get("c"));
1162         }
1163 
1164         assertFalse(records.hasNext());
1165     }
1166 
1167     @Test
1168     public void testRepeatedHeadersAreReturnedInCSVRecordHeaderNames() throws IOException {
1169         final Reader in = new StringReader("header1,header2,header1\n1,2,3\n4,5,6");
1170         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withFirstRecordAsHeader().withTrim().parse(in).iterator();
1171         final CSVRecord record = records.next();
1172         assertEquals(Arrays.asList("header1", "header2", "header1"), record.getParser().getHeaderNames());
1173     }
1174 
1175     @Test
1176     public void testRoundtrip() throws Exception {
1177         final StringWriter out = new StringWriter();
1178         try (final CSVPrinter printer = new CSVPrinter(out, CSVFormat.DEFAULT)) {
1179             final String input = "a,b,c\r\n1,2,3\r\nx,y,z\r\n";
1180             for (final CSVRecord record : CSVParser.parse(input, CSVFormat.DEFAULT)) {
1181                 printer.printRecord(record);
1182             }
1183             assertEquals(input, out.toString());
1184         }
1185     }
1186 
1187     @Test
1188     public void testSkipAutoHeader() throws Exception {
1189         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1190         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader().parse(in).iterator();
1191         final CSVRecord record = records.next();
1192         assertEquals("1", record.get("a"));
1193         assertEquals("2", record.get("b"));
1194         assertEquals("3", record.get("c"));
1195     }
1196 
1197     @Test
1198     public void testSkipHeaderOverrideDuplicateHeaders() throws Exception {
1199         final Reader in = new StringReader("a,a,a\n1,2,3\nx,y,z");
1200         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().parse(in)
1201             .iterator();
1202         final CSVRecord record = records.next();
1203         assertEquals("1", record.get("X"));
1204         assertEquals("2", record.get("Y"));
1205         assertEquals("3", record.get("Z"));
1206     }
1207 
1208     @Test
1209     public void testSkipSetAltHeaders() throws Exception {
1210         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1211         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord().parse(in)
1212             .iterator();
1213         final CSVRecord record = records.next();
1214         assertEquals("1", record.get("X"));
1215         assertEquals("2", record.get("Y"));
1216         assertEquals("3", record.get("Z"));
1217     }
1218 
1219     @Test
1220     public void testSkipSetHeader() throws Exception {
1221         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1222         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("a", "b", "c").withSkipHeaderRecord().parse(in)
1223             .iterator();
1224         final CSVRecord record = records.next();
1225         assertEquals("1", record.get("a"));
1226         assertEquals("2", record.get("b"));
1227         assertEquals("3", record.get("c"));
1228     }
1229 
1230     @Test
1231     @Disabled
1232     public void testStartWithEmptyLinesThenHeaders() throws Exception {
1233         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",
1234             "hello,\"\"\n\n\n"};
1235         final String[][] res = {{"hello", ""}, {""}, // Excel format does not ignore empty lines
1236             {""}};
1237         for (final String code : codes) {
1238             try (final CSVParser parser = CSVParser.parse(code, CSVFormat.EXCEL)) {
1239                 final List<CSVRecord> records = parser.getRecords();
1240                 assertEquals(res.length, records.size());
1241                 assertFalse(records.isEmpty());
1242                 for (int i = 0; i < res.length; i++) {
1243                     assertArrayEquals(res[i], records.get(i).values());
1244                 }
1245             }
1246         }
1247     }
1248 
1249     @Test
1250     public void testStream() throws Exception {
1251         final Reader in = new StringReader("a,b,c\n1,2,3\nx,y,z");
1252         final List<CSVRecord> list = CSVFormat.DEFAULT.parse(in).stream().collect(Collectors.toList());
1253         assertFalse(list.isEmpty());
1254         assertArrayEquals(new String[] {"a", "b", "c"}, list.get(0).values());
1255         assertArrayEquals(new String[] {"1", "2", "3"}, list.get(1).values());
1256         assertArrayEquals(new String[] {"x", "y", "z"}, list.get(2).values());
1257     }
1258 
1259     @Test
1260     public void testTrailingDelimiter() throws Exception {
1261         final Reader in = new StringReader("a,a,a,\n\"1\",\"2\",\"3\",\nx,y,z,");
1262         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord()
1263             .withTrailingDelimiter().parse(in).iterator();
1264         final CSVRecord record = records.next();
1265         assertEquals("1", record.get("X"));
1266         assertEquals("2", record.get("Y"));
1267         assertEquals("3", record.get("Z"));
1268         assertEquals(3, record.size());
1269     }
1270 
1271     @Test
1272     public void testTrim() throws Exception {
1273         final Reader in = new StringReader("a,a,a\n\" 1 \",\" 2 \",\" 3 \"\nx,y,z");
1274         final Iterator<CSVRecord> records = CSVFormat.DEFAULT.withHeader("X", "Y", "Z").withSkipHeaderRecord()
1275             .withTrim().parse(in).iterator();
1276         final CSVRecord record = records.next();
1277         assertEquals("1", record.get("X"));
1278         assertEquals("2", record.get("Y"));
1279         assertEquals("3", record.get("Z"));
1280         assertEquals(3, record.size());
1281     }
1282 
1283     private void validateLineNumbers(final String lineSeparator) throws IOException {
1284         try (final CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c",
1285             CSVFormat.DEFAULT.withRecordSeparator(lineSeparator))) {
1286             assertEquals(0, parser.getCurrentLineNumber());
1287             assertNotNull(parser.nextRecord());
1288             assertEquals(1, parser.getCurrentLineNumber());
1289             assertNotNull(parser.nextRecord());
1290             assertEquals(2, parser.getCurrentLineNumber());
1291             assertNotNull(parser.nextRecord());
1292             // Read EOF without EOL should 3
1293             assertEquals(3, parser.getCurrentLineNumber());
1294             assertNull(parser.nextRecord());
1295             // Read EOF without EOL should 3
1296             assertEquals(3, parser.getCurrentLineNumber());
1297         }
1298     }
1299 
1300     private void validateRecordNumbers(final String lineSeparator) throws IOException {
1301         try (final CSVParser parser = CSVParser.parse("a" + lineSeparator + "b" + lineSeparator + "c",
1302             CSVFormat.DEFAULT.withRecordSeparator(lineSeparator))) {
1303             CSVRecord record;
1304             assertEquals(0, parser.getRecordNumber());
1305             assertNotNull(record = parser.nextRecord());
1306             assertEquals(1, record.getRecordNumber());
1307             assertEquals(1, parser.getRecordNumber());
1308             assertNotNull(record = parser.nextRecord());
1309             assertEquals(2, record.getRecordNumber());
1310             assertEquals(2, parser.getRecordNumber());
1311             assertNotNull(record = parser.nextRecord());
1312             assertEquals(3, record.getRecordNumber());
1313             assertEquals(3, parser.getRecordNumber());
1314             assertNull(record = parser.nextRecord());
1315             assertEquals(3, parser.getRecordNumber());
1316         }
1317     }
1318 
1319     private void validateRecordPosition(final String lineSeparator) throws IOException {
1320         final String nl = lineSeparator; // used as linebreak in values for better distinction
1321 
1322         final String code = "a,b,c" + lineSeparator + "1,2,3" + lineSeparator +
1323         // to see if recordPosition correctly points to the enclosing quote
1324             "'A" + nl + "A','B" + nl + "B',CC" + lineSeparator +
1325             // unicode test... not very relevant while operating on strings instead of bytes, but for
1326             // completeness...
1327             "\u00c4,\u00d6,\u00dc" + lineSeparator + "EOF,EOF,EOF";
1328 
1329         final CSVFormat format = CSVFormat.newFormat(',').withQuote('\'').withRecordSeparator(lineSeparator);
1330         CSVParser parser = CSVParser.parse(code, format);
1331 
1332         CSVRecord record;
1333         assertEquals(0, parser.getRecordNumber());
1334 
1335         assertNotNull(record = parser.nextRecord());
1336         assertEquals(1, record.getRecordNumber());
1337         assertEquals(code.indexOf('a'), record.getCharacterPosition());
1338 
1339         assertNotNull(record = parser.nextRecord());
1340         assertEquals(2, record.getRecordNumber());
1341         assertEquals(code.indexOf('1'), record.getCharacterPosition());
1342 
1343         assertNotNull(record = parser.nextRecord());
1344         final long positionRecord3 = record.getCharacterPosition();
1345         assertEquals(3, record.getRecordNumber());
1346         assertEquals(code.indexOf("'A"), record.getCharacterPosition());
1347         assertEquals("A" + lineSeparator + "A", record.get(0));
1348         assertEquals("B" + lineSeparator + "B", record.get(1));
1349         assertEquals("CC", record.get(2));
1350 
1351         assertNotNull(record = parser.nextRecord());
1352         assertEquals(4, record.getRecordNumber());
1353         assertEquals(code.indexOf('\u00c4'), record.getCharacterPosition());
1354 
1355         assertNotNull(record = parser.nextRecord());
1356         assertEquals(5, record.getRecordNumber());
1357         assertEquals(code.indexOf("EOF"), record.getCharacterPosition());
1358 
1359         parser.close();
1360 
1361         // now try to read starting at record 3
1362         parser = new CSVParser(new StringReader(code.substring((int) positionRecord3)), format, positionRecord3, 3);
1363 
1364         assertNotNull(record = parser.nextRecord());
1365         assertEquals(3, record.getRecordNumber());
1366         assertEquals(code.indexOf("'A"), record.getCharacterPosition());
1367         assertEquals("A" + lineSeparator + "A", record.get(0));
1368         assertEquals("B" + lineSeparator + "B", record.get(1));
1369         assertEquals("CC", record.get(2));
1370 
1371         assertNotNull(record = parser.nextRecord());
1372         assertEquals(4, record.getRecordNumber());
1373         assertEquals(code.indexOf('\u00c4'), record.getCharacterPosition());
1374         assertEquals("\u00c4", record.get(0));
1375 
1376         parser.close();
1377     }
1378 }