aboutsummaryrefslogtreecommitdiff
path: root/src/csv_parser.c
diff options
context:
space:
mode:
authorTadas Vilkeliskis <vilkeliskis.t@gmail.com>2026-06-11 10:00:42 -0400
committerTadas Vilkeliskis <vilkeliskis.t@gmail.com>2026-06-11 10:00:42 -0400
commit12007e197832263b9113554bdfea1a8258f179bc (patch)
tree2fddfacf735aca531604921eacf9612c17dead2e /src/csv_parser.c
parent01cf97e60f970490a94a56d8c82fca320e17fac5 (diff)
Fix multiple parsing bugs and add RFC 4180 quote support
- Change nread to size_t to prevent overflow. - Fix state machine to handle consecutive newlines appropriately. - Add csv_parser_finish to emit pending trailing fields at EOF. - Fix nread boundary count on callback errors. - Add support for quoted fields, including escaped quotes. - Update and expand test suite to cover new cases and quote parsing.
Diffstat (limited to 'src/csv_parser.c')
-rw-r--r--src/csv_parser.c108
1 files changed, 98 insertions, 10 deletions
diff --git a/src/csv_parser.c b/src/csv_parser.c
index b93713a..32cf8d4 100644
--- a/src/csv_parser.c
+++ b/src/csv_parser.c
@@ -70,8 +70,15 @@ size_t csv_parser_execute(csv_parser_t *parser,
break;
case csvps_field_start:
parser->col += 1;
- parser->state = csvps_field_value;
- field_value = cursor;
+ if (ch == '"') {
+ parser->state = csvps_field_quoted_value;
+ cursor++;
+ parser->nread++;
+ field_value = cursor;
+ } else {
+ parser->state = csvps_field_value;
+ field_value = cursor;
+ }
break;
case csvps_field_value:
if (ch == settings->delimiter) {
@@ -79,10 +86,6 @@ size_t csv_parser_execute(csv_parser_t *parser,
} else if (ch == '\r' || ch == '\n') {
parser->state = csvps_line_end_begin;
} else {
- // If we previously been in csvps_field_value state
- // and field_value is not set, set it right away.
- // This can happen when we execute parser multiple
- // times in the value state.
if (field_value == NULL) {
field_value = cursor;
}
@@ -97,6 +100,7 @@ size_t csv_parser_execute(csv_parser_t *parser,
parser->col);
if (r) {
parser->state = csvps_error;
+ parser->nread++;
return parser->nread;
}
}
@@ -104,6 +108,67 @@ size_t csv_parser_execute(csv_parser_t *parser,
parser->nread++;
}
break;
+ case csvps_field_quoted_value:
+ if (field_value == NULL) {
+ field_value = cursor;
+ }
+ if (ch == '"') {
+ parser->state = csvps_field_quoted_quote;
+ if (settings->field_cb && field_value && (cursor >= field_value)) {
+ r = settings->field_cb(parser,
+ field_value,
+ cursor - field_value,
+ parser->row,
+ parser->col);
+ if (r) {
+ parser->state = csvps_error;
+ return parser->nread;
+ }
+ }
+ field_value = NULL;
+ cursor++;
+ parser->nread++;
+ } else {
+ cursor++;
+ if (cursor == data_end) {
+ if (settings->field_cb && field_value) {
+ r = settings->field_cb(parser,
+ field_value,
+ cursor - field_value,
+ parser->row,
+ parser->col);
+ if (r) {
+ parser->state = csvps_error;
+ parser->nread++;
+ return parser->nread;
+ }
+ }
+ }
+ parser->nread++;
+ }
+ break;
+ case csvps_field_quoted_quote:
+ if (ch == '"') {
+ if (settings->field_cb) {
+ r = settings->field_cb(parser, cursor, 1, parser->row, parser->col);
+ if (r) {
+ parser->state = csvps_error;
+ return parser->nread;
+ }
+ }
+ parser->state = csvps_field_quoted_value;
+ cursor++;
+ parser->nread++;
+ field_value = cursor;
+ } else if (ch == settings->delimiter) {
+ parser->state = csvps_field_end;
+ } else if (ch == '\r' || ch == '\n') {
+ parser->state = csvps_line_end_begin;
+ } else {
+ parser->state = csvps_error;
+ return parser->nread;
+ }
+ break;
case csvps_field_end:
// callback
if (settings->field_cb && field_value) {
@@ -135,15 +200,20 @@ size_t csv_parser_execute(csv_parser_t *parser,
return parser->nread;
}
}
- parser->state = csvps_line_end;
+ if (ch == '\r') {
+ parser->state = csvps_line_end;
+ } else {
+ parser->state = csvps_line_start;
+ }
+ cursor++;
+ parser->nread++;
break;
case csvps_line_end:
- if (ch == '\r' || ch == '\n') {
+ if (ch == '\n') {
cursor++;
parser->nread++;
- } else {
- parser->state = csvps_line_start;
}
+ parser->state = csvps_line_start;
break;
case csvps_error:
return parser->nread;
@@ -156,3 +226,21 @@ size_t csv_parser_execute(csv_parser_t *parser,
return parser->nread;
}
+
+int csv_parser_finish(csv_parser_t *parser, const csv_parser_settings_t *settings)
+{
+ int r = 0;
+ if (parser->state == csvps_field_start) {
+ if (settings->field_cb) {
+ parser->col += 1;
+ r = settings->field_cb(parser, "", 0, parser->row, parser->col);
+ if (r) {
+ parser->state = csvps_error;
+ }
+ }
+ }
+ if (parser->state != csvps_error) {
+ parser->state = csvps_line_start;
+ }
+ return r;
+}