Skip to content
This repository was archived by the owner on Jun 3, 2026. It is now read-only.

Commit 31a546a

Browse files
committed
[CSV-304] Accessors for header/trailer comments
Add accessors for header comments (before the header row) and trailer comments (after the last record) Also add javadoc and tests
1 parent c51b595 commit 31a546a

2 files changed

Lines changed: 177 additions & 2 deletions

File tree

src/main/java/org/apache/commons/csv/CSVParser.java

Lines changed: 53 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -352,6 +352,9 @@ public static CSVParser parse(final URL url, final Charset charset, final CSVFor
352352

353353
return new CSVParser(new InputStreamReader(url.openStream(), charset), format);
354354
}
355+
private String headerComment;
356+
357+
private String trailerComment;
355358

356359
private final CSVFormat format;
357360

@@ -480,10 +483,12 @@ private Headers createHeaders() throws IOException {
480483
final CSVRecord nextRecord = this.nextRecord();
481484
if (nextRecord != null) {
482485
headerRecord = nextRecord.values();
486+
headerComment = nextRecord.getComment();
483487
}
484488
} else {
485489
if (this.format.getSkipHeaderRecord()) {
486-
this.nextRecord();
490+
final CSVRecord csvRecord = this.nextRecord();
491+
headerComment = csvRecord.getComment();
487492
}
488493
headerRecord = formatHeader;
489494
}
@@ -596,7 +601,49 @@ Map<String, Integer> getHeaderMapRaw() {
596601
public List<String> getHeaderNames() {
597602
return Collections.unmodifiableList(headers.headerNames);
598603
}
599-
604+
/**
605+
* Checks whether this parser has a header comment, false otherwise.
606+
* The header comment appears before the header record.
607+
* Note that if the parser's format has been given an explicit header
608+
* (with {@link CSVFormat.Builder#setHeader(String... )} or another overload)
609+
* and the header record is not being skipped
610+
* ({@link CSVFormat.Builder#setSkipHeaderRecord} is false) then any initial comments
611+
* will be associated with the first record, not the header.
612+
*
613+
* @return true if this parser has seen a header comment, false otherwise
614+
*/
615+
public boolean hasHeaderComment() {
616+
return headerComment != null;
617+
}
618+
/**
619+
* Returns the header comment for this parser, if any.
620+
* The header comment appears before the header record.
621+
*
622+
* @return the header comment for this stream, or null if no comment is available.
623+
*/
624+
public String getHeaderComment() {
625+
return headerComment;
626+
}
627+
/**
628+
* Checks whether this parser has seen a trailer comment, false otherwise.
629+
* Trailer comments are located between the last record and EOF.
630+
* The trailer comments will only be available after the parser has
631+
* finished processing this stream.
632+
*
633+
* @return true if this parser has seen a trailer comment, false otherwise
634+
*/
635+
public boolean hasTrailerComment() {
636+
return trailerComment != null;
637+
}
638+
/**
639+
* Returns the trailer comment for this record, if any.
640+
* Trailer comments are located between the last record and EOF
641+
*
642+
* @return the trailer comment for this stream, or null if no comment is available.
643+
*/
644+
public String getTrailerComment() {
645+
return trailerComment;
646+
}
600647
/**
601648
* Returns the current record number in the input stream.
602649
*
@@ -713,6 +760,10 @@ CSVRecord nextRecord() throws IOException {
713760
case EOF:
714761
if (this.reusableToken.isReady) {
715762
this.addRecordValue(true);
763+
} else {
764+
if (sb != null) {
765+
trailerComment = sb.toString();
766+
}
716767
}
717768
break;
718769
case INVALID:

src/test/java/org/apache/commons/csv/CSVParserTest.java

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1375,4 +1375,128 @@ private void validateRecordPosition(final String lineSeparator) throws IOExcepti
13751375

13761376
parser.close();
13771377
}
1378+
@Test
1379+
public void getHeaderComment() throws IOException {
1380+
// File with no header comments
1381+
String text_1 = "A,B"+CRLF+"1,2"+CRLF;
1382+
// File with a single line header comment
1383+
String text_2 = "# comment"+CRLF+"A,B"+CRLF+"1,2"+CRLF;
1384+
// File with a multi-line header comment
1385+
String text_3 = "# multi-line" + CRLF + "# comment"+CRLF+"A,B"+CRLF+"1,2"+CRLF;
1386+
// Format with auto-detected header
1387+
CSVFormat format_a = CSVFormat.Builder.create(CSVFormat.DEFAULT).setCommentMarker('#').setHeader().build();
1388+
// Format with explicit header
1389+
CSVFormat format_b = CSVFormat.Builder.create(CSVFormat.DEFAULT)
1390+
.setSkipHeaderRecord(true)
1391+
.setCommentMarker('#')
1392+
.setHeader("A","B")
1393+
.build();
1394+
// Format with explicit header that does not skip the header line
1395+
CSVFormat format_c = CSVFormat.Builder.create(CSVFormat.DEFAULT)
1396+
.setCommentMarker('#')
1397+
.setHeader("A","B")
1398+
.build();
1399+
1400+
try (CSVParser parser = CSVParser.parse(text_1, format_a)) {
1401+
parser.getRecords();
1402+
// Expect no header comment
1403+
assertFalse(parser.hasHeaderComment());
1404+
assertNull(parser.getHeaderComment());
1405+
}
1406+
try (CSVParser parser = CSVParser.parse(text_2, format_a)) {
1407+
parser.getRecords();
1408+
// Expect a header comment
1409+
assertTrue(parser.hasHeaderComment());
1410+
assertEquals("comment", parser.getHeaderComment());
1411+
}
1412+
try (CSVParser parser = CSVParser.parse(text_3, format_a)) {
1413+
parser.getRecords();
1414+
// Expect a header comment
1415+
assertTrue(parser.hasHeaderComment());
1416+
assertEquals("multi-line"+LF+"comment", parser.getHeaderComment());
1417+
}
1418+
try (CSVParser parser = CSVParser.parse(text_1, format_b)) {
1419+
parser.getRecords();
1420+
// Expect no header comment
1421+
assertFalse(parser.hasHeaderComment());
1422+
assertNull(parser.getHeaderComment());
1423+
}
1424+
try (CSVParser parser = CSVParser.parse(text_2, format_b)) {
1425+
parser.getRecords();
1426+
// Expect a header comment
1427+
assertTrue(parser.hasHeaderComment());
1428+
assertEquals("comment", parser.getHeaderComment());
1429+
}
1430+
try (CSVParser parser = CSVParser.parse(text_1, format_c)) {
1431+
parser.getRecords();
1432+
// Expect no header comment
1433+
assertFalse(parser.hasHeaderComment());
1434+
assertNull(parser.getHeaderComment());
1435+
}
1436+
try (CSVParser parser = CSVParser.parse(text_2, format_c)) {
1437+
parser.getRecords();
1438+
// Expect no header comment - the text "comment" is attached to the first record
1439+
assertFalse(parser.hasHeaderComment());
1440+
assertNull(parser.getHeaderComment());
1441+
}
1442+
}
1443+
@Test
1444+
public void getTrailerComment() throws IOException {
1445+
// File with a header comment
1446+
String text_1 = "# header comment"+CRLF+"A,B"+CRLF+"1,2"+CRLF;
1447+
// File with a single line header and trailer comment
1448+
String text_2 = "# header comment"+CRLF+"A,B"+CRLF+"1,2"+CRLF + "# comment";
1449+
// File with a multi-line header and trailer comment
1450+
String text_3 = "# multi-line" + CRLF + "# header comment"+CRLF+"A,B"+CRLF+"1,2"+CRLF+"# multi-line"+CRLF+"# comment";
1451+
// Format with auto-detected header
1452+
CSVFormat format_a = CSVFormat.Builder.create(CSVFormat.DEFAULT).setCommentMarker('#').setHeader().build();
1453+
// Format with explicit header
1454+
CSVFormat format_b = CSVFormat.Builder.create(CSVFormat.DEFAULT)
1455+
.setSkipHeaderRecord(true)
1456+
.setCommentMarker('#')
1457+
.setHeader("A","B")
1458+
.build();
1459+
// Format with explicit header that does not skip the header line
1460+
CSVFormat format_c = CSVFormat.Builder.create(CSVFormat.DEFAULT)
1461+
.setCommentMarker('#')
1462+
.setHeader("A","B")
1463+
.build();
1464+
1465+
try (CSVParser parser = CSVParser.parse(text_1, format_a)) {
1466+
parser.getRecords();
1467+
assertFalse(parser.hasTrailerComment());
1468+
assertNull(parser.getTrailerComment());
1469+
}
1470+
try (CSVParser parser = CSVParser.parse(text_2, format_a)) {
1471+
parser.getRecords();
1472+
assertTrue(parser.hasTrailerComment());
1473+
assertEquals("comment", parser.getTrailerComment());
1474+
}
1475+
try (CSVParser parser = CSVParser.parse(text_3, format_a)) {
1476+
parser.getRecords();
1477+
assertTrue(parser.hasTrailerComment());
1478+
assertEquals("multi-line"+LF+"comment", parser.getTrailerComment());
1479+
}
1480+
try (CSVParser parser = CSVParser.parse(text_1, format_b)) {
1481+
parser.getRecords();
1482+
assertFalse(parser.hasTrailerComment());
1483+
assertNull(parser.getTrailerComment());
1484+
}
1485+
try (CSVParser parser = CSVParser.parse(text_2, format_b)) {
1486+
parser.getRecords();
1487+
assertTrue(parser.hasTrailerComment());
1488+
assertEquals("comment", parser.getTrailerComment());
1489+
}
1490+
try (CSVParser parser = CSVParser.parse(text_1, format_c)) {
1491+
parser.getRecords();
1492+
assertFalse(parser.hasTrailerComment());
1493+
assertNull(parser.getTrailerComment());
1494+
}
1495+
try (CSVParser parser = CSVParser.parse(text_2, format_c)) {
1496+
parser.getRecords();
1497+
assertTrue(parser.hasTrailerComment());
1498+
assertEquals("comment", parser.getTrailerComment());
1499+
}
1500+
}
1501+
13781502
}

0 commit comments

Comments
 (0)