csv-parser 1.1.0
Csv::Parser (csv-parser)
Loading...
Searching...
No Matches
csv_parser.h
Go to the documentation of this file.
1/**************************************************************************
2Copyright: (C) 2021 - 2025 Alexander Shaduri
3License: Zlib
4***************************************************************************/
5
6#ifndef CSV_PARSER_H
7#define CSV_PARSER_H
8
9#include "csv_cell.h"
10#include "csv_error.h"
11#include "csv_matrix.h"
12#include "csv_util.h"
13#include "csv_policies.h"
14
15#include <string>
16#include <string_view>
17#include <array>
18
19
27namespace Csv {
28
29
69template<typename BehaviorPolicy = LocaleAwareBehaviorPolicy>
70class Parser {
71 public:
72
74 constexpr Parser() = default;
75
76
85 template<typename StoreCellFunction>
86 constexpr void parse(std::string_view data, StoreCellFunction storeCellFunc) const;
87
88
98 template<typename Vector2D>
99 constexpr void parseTo2DVector(std::string_view data, Vector2D& values) const;
100
101
113 template<std::size_t rows, std::size_t columns, typename Cell = CellStringReference>
114 constexpr auto parseTo2DArray(std::string_view data) const;
115
116
127 template<typename Vector>
128 constexpr MatrixInformation parseToVectorRowMajor(std::string_view data, Vector& values) const;
129
130
139 template<typename Vector>
140 constexpr MatrixInformation parseToVectorRowMajor(std::string_view data, Vector& values,
141 std::optional<std::size_t> rows_hint, std::optional<std::size_t> columns) const;
142
143
152 template<typename Vector>
153 constexpr MatrixInformation parseToVectorColumnMajor(std::string_view data, Vector& values,
154 std::size_t rows, std::optional<std::size_t> columns_hint) const;
155
156
166 template<std::size_t rows, std::size_t columns, typename Cell = CellStringReference>
167 constexpr auto parseToArray(std::string_view data, MatrixOrder order) const;
168
169
170
171 private:
172
174 enum class MachineState {
175 AtCellStart,
176 InLeadingWhiteSpace,
177 InsideUnquotedValue,
178 InsideQuotedValue,
179 AfterQuotedValue,
180 };
181
182
184 struct ParserState {
185 MachineState machine_state = MachineState::AtCellStart;
186 std::size_t current_row = 0, current_column = 0;
187 std::string_view current_value;
188 bool escaped_quotes_encountered = false;
189
190
192 constexpr void switchToNextColumn()
193 {
194 current_value = std::string_view();
195 escaped_quotes_encountered = false;
196 ++current_column;
197 }
198
199
202 [[nodiscard]] constexpr std::size_t switchToNextLine(const std::string_view& data, std::size_t current_pos)
203 {
204 current_value = std::string_view();
205 escaped_quotes_encountered = false;
206 current_column = 0;
207 ++current_row;
208 // If it's CR, and the next character is LF, skip LF as well.
209 if (auto next_char = peek(data, current_pos); data[current_pos] == '\r' && next_char == '\n') {
210 ++current_pos;
211 }
212 return current_pos;
213 }
214
215
217 constexpr void increaseCurrentValueSize(std::size_t by_chars)
218 {
219 current_value = std::string_view(current_value.data(), current_value.size() + by_chars);
220 }
221
222
224 constexpr void restartCurrentValue(const std::string_view& data, std::size_t current_pos, std::size_t size)
225 {
226 current_value = data.substr(current_pos, size);
227 escaped_quotes_encountered = false;
228 }
229
230 };
231
232
235 [[nodiscard]] static constexpr std::char_traits<char>::int_type peek(const std::string_view& data,
236 std::size_t current_pos, std::size_t advance_chars = 1)
237 {
238 if ((current_pos + advance_chars) < data.size()) {
239 return std::char_traits<char>::to_int_type(data[current_pos + advance_chars]);
240 }
241 return std::char_traits<char>::eof();
242 }
243
244
247 [[nodiscard]] static constexpr std::char_traits<char>::int_type readChar(
248 const std::string_view& data, std::size_t pos)
249 {
250 return peek(data, pos, 0);
251 }
252
253
256 template <typename StoreCellFunction>
257 constexpr void store(StoreCellFunction storeCellFunc,
258 const ParserState& state, CellTypeHint type_hint) const
259 {
260 if (type_hint == CellTypeHint::Empty && !BehaviorPolicy::useEmptyCellType()) {
261 type_hint = CellTypeHint::StringWithoutEscapedQuotes;
262 }
263 storeCellFunc(state.current_row, state.current_column, state.current_value, type_hint);
264 }
265
266};
267
268
269
270
271// ---- Implementation
272
273
274
275template<typename BehaviorPolicy>
276template<typename StoreCellFunction>
277constexpr void Parser<BehaviorPolicy>::parse(std::string_view data, StoreCellFunction storeCellFunc) const
278{
279 ParserState state;
280
281 for (std::size_t pos = 0; pos <= data.size(); ++pos) {
282 auto current_char = readChar(data, pos);
283
284 switch (state.machine_state) {
285 // Starting the cell
286 case MachineState::AtCellStart:
287 {
288 switch(current_char) {
289 case ' ':
290 case '\t':
291 // Store the whitespace
292 state.machine_state = MachineState::InLeadingWhiteSpace;
293 state.restartCurrentValue(data, pos, 1);
294 break;
295 case '\"':
296 // Start a quoted cell.
297 // Discard the starting quote as well, but provide a "Quoted" hint when the quoted cell
298 // is stored.
299 state.machine_state = MachineState::InsideQuotedValue;
300 state.restartCurrentValue(data, pos + 1, 0);
301 break;
302 case ',':
303 // Empty cell. Store the value.
304 store(storeCellFunc, state, CellTypeHint::Empty);
305 state.switchToNextColumn();
306 break;
307 case '\r':
308 case '\n':
309 // Empty cell (trailing comma; last value on the line). Store the value.
310 store(storeCellFunc, state, CellTypeHint::Empty);
311 state.machine_state = MachineState::AtCellStart;
312 // Handle CRLF if needed and set the state to the next line, cell start.
313 pos = state.switchToNextLine(data, pos);
314 break;
315 case std::char_traits<char>::eof():
316 // If it's in the first column, it's a trailing newline, nothing else to do.
317 // Otherwise, it's a last empty cell on the line after comma (aka trailing comma).
318 if (state.current_column != 0) {
319 store(storeCellFunc, state, CellTypeHint::Empty);
320 }
321 return;
322 default:
323 // Start an unquoted cell
324 state.machine_state = MachineState::InsideUnquotedValue;
325 state.restartCurrentValue(data, pos, 1);
326 break;
327 }
328 break;
329 }
330
331 // Only whitespace encountered in the cell so far
332 case MachineState::InLeadingWhiteSpace:
333 {
334 switch(current_char) {
335 case ' ':
336 case '\t':
337 // More whitespace. Append to existing leading whitespace.
338 state.increaseCurrentValueSize(1);
339 break;
340 case '\"':
341 // Quote encountered. Discard the leading whitespace, start a quoted cell.
342 // Discard the starting quote as well, but provide a "Quoted" hint when the quoted cell
343 // is stored.
344 state.machine_state = MachineState::InsideQuotedValue;
345 state.restartCurrentValue(data, pos + 1, 0);
346 break;
347 case ',':
348 // Whitespace-only string cell. Store the value.
349 store(storeCellFunc, state, CellTypeHint::StringWithoutEscapedQuotes);
350 state.machine_state = MachineState::AtCellStart;
351 state.switchToNextColumn();
352 break;
353 case '\r':
354 case '\n':
355 // Whitespace-only string cell (last value on the line). Store the value.
356 store(storeCellFunc, state, CellTypeHint::StringWithoutEscapedQuotes);
357 state.machine_state = MachineState::AtCellStart;
358 // Handle CRLF if needed and set the state to the next line, cell start.
359 pos = state.switchToNextLine(data, pos);
360 break;
361 case std::char_traits<char>::eof():
362 // Store the value, exit.
363 store(storeCellFunc, state, CellTypeHint::StringWithoutEscapedQuotes);
364 return;
365 default:
366 // Continue an unquoted cell
367 state.machine_state = MachineState::InsideUnquotedValue;
368 state.increaseCurrentValueSize(1);
369 break;
370 }
371 break;
372 }
373
374 // We encountered non-whitespace characters in a cell, and it didn't start with a quote.
375 case MachineState::InsideUnquotedValue:
376 {
377 switch(current_char) {
378 case '\"':
379 // Make sure the next character is also a quote, otherwise it's a format error.
380 // We don't accept unescaped double-quotes in unquoted strings because it leads
381 // to ambiguity.
382 if (peek(data, pos) != '\"') {
383 throw ParseError(state.current_row, state.current_column);
384 }
385 // Continue unquoted string, consume the second quote as well.
386 ++pos;
387 state.increaseCurrentValueSize(2);
388 state.escaped_quotes_encountered = true; // used for hints when storing the value
389 break;
390 case ',':
391 // End of cell. Store the value.
392 store(storeCellFunc, state, state.escaped_quotes_encountered ?
393 CellTypeHint::StringWithEscapedQuotes : CellTypeHint::UnquotedData);
394 state.machine_state = MachineState::AtCellStart;
395 state.switchToNextColumn();
396 break;
397 case '\r':
398 case '\n':
399 // End of line. Store the value.
400 store(storeCellFunc, state, state.escaped_quotes_encountered ?
401 CellTypeHint::StringWithEscapedQuotes : CellTypeHint::UnquotedData);
402 state.machine_state = MachineState::AtCellStart;
403 // Handle CRLF if needed and set the state to the next line, cell start.
404 pos = state.switchToNextLine(data, pos);
405 break;
406 case std::char_traits<char>::eof():
407 // Store the value, exit.
408 store(storeCellFunc, state, state.escaped_quotes_encountered ?
409 CellTypeHint::StringWithEscapedQuotes : CellTypeHint::UnquotedData);
410 return;
411 default:
412 // Continue an unquoted cell
413 state.increaseCurrentValueSize(1);
414 break;
415 }
416 break;
417 }
418
419 // The cell started with an optional whitespace and a quote, we're past the first quote.
420 case MachineState::InsideQuotedValue:
421 {
422 switch(current_char) {
423 case '\"':
424 // If the next character is also a quote, it's an escaped quote.
425 if (peek(data, pos) == '\"') {
426 // Continue quoted string, consume the second quote as well.
427 ++pos;
428 state.increaseCurrentValueSize(2);
429 state.escaped_quotes_encountered = true; // used for hints when storing the value
430 } else {
431 // End of quoted value. Store the value, discard the ending quote.
432 store(storeCellFunc, state, state.escaped_quotes_encountered ?
433 CellTypeHint::StringWithEscapedQuotes : CellTypeHint::StringWithoutEscapedQuotes);
434 state.machine_state = MachineState::AfterQuotedValue;
435 }
436 break;
437 case std::char_traits<char>::eof():
438 // EOF while inside a quoted cell, throw an error.
439 throw ParseError(state.current_row, state.current_column);
440 default:
441 // Continue an unquoted cell
442 state.increaseCurrentValueSize(1);
443 break;
444 }
445 break;
446 }
447
448 // The quoted cell just ended
449 case MachineState::AfterQuotedValue:
450 {
451 switch(current_char) {
452 case ' ':
453 case '\t':
454 // Just whitespace, ignore it.
455 break;
456 case ',':
457 // End of cell. The value has been stored already, switch to the next line.
458 state.machine_state = MachineState::AtCellStart;
459 state.switchToNextColumn();
460 break;
461 case '\r':
462 case '\n':
463 // End of line. The value has been stored already, switch to the next line.
464 state.machine_state = MachineState::AtCellStart;
465 pos = state.switchToNextLine(data, pos);
466 break;
467 case std::char_traits<char>::eof():
468 // Nothing more to do, return.
469 return;
470 default:
471 // Anything else is an error
472 throw ParseError(state.current_row, state.current_column);
473 }
474 break;
475 }
476 }
477 }
478}
479
480
481
482template<typename BehaviorPolicy>
483template<typename Vector2D>
484constexpr void Parser<BehaviorPolicy>::parseTo2DVector(std::string_view data, Vector2D& values) const
485{
486 Vector2D parsed_values;
487 parse(data,
488 [&parsed_values](std::size_t row, std::size_t column, std::string_view cell_data, CellTypeHint hint)
489 {
490 if (parsed_values.size() < (column + 1)) {
491 parsed_values.resize(column + 1);
492 }
493 if (parsed_values[column].size() < (row + 1)) {
494 parsed_values[column].resize(row + 1);
495 }
496 parsed_values[column][row] =
497 BehaviorPolicy::template create<typename Vector2D::value_type::value_type>(cell_data, hint);
498 }
499 );
500 std::swap(values, parsed_values);
501}
502
503
504
505template<typename BehaviorPolicy>
506template<std::size_t rows, std::size_t columns, typename Cell>
507constexpr auto Parser<BehaviorPolicy>::parseTo2DArray(std::string_view data) const
508{
509 std::array<std::array<Cell, rows>, columns> matrix;
510
511 parse(data,
512 [&matrix](std::size_t row, std::size_t column,
513 std::string_view cell_data, Csv::CellTypeHint hint)
514 constexpr
515 {
516 matrix[column][row] = BehaviorPolicy::template create<Cell>(cell_data, hint);
517 }
518 );
519
520 return matrix;
521}
522
523
524
525template<typename BehaviorPolicy>
526template<typename Vector>
527constexpr MatrixInformation Parser<BehaviorPolicy>::parseToVectorRowMajor(std::string_view data, Vector& values) const
528{
529 return parseToVectorRowMajor(data, values, std::nullopt, std::nullopt);
530}
531
532
533
534template<typename BehaviorPolicy>
535template<typename Vector>
536constexpr MatrixInformation Parser<BehaviorPolicy>::parseToVectorRowMajor(std::string_view data, Vector& values,
537 std::optional<std::size_t> rows_hint, std::optional<std::size_t> columns) const
538{
539 Vector parsed_values;
540 if (rows_hint.has_value() && columns.has_value()) {
541 parsed_values.reserve(rows_hint.value() * columns.value());
542 }
543
545 info.setOrder(MatrixOrder::RowMajor);
546
547 bool unknown_columns = !columns.has_value();
548 if (!unknown_columns) {
549 info.setColumns(columns.value());
550 }
551 std::size_t read_columns = 0;
552
553 // The lambda is called in row-major order, so we can exploit that to determine the number of columns.
554 parse(data,
555 [&](std::size_t row, std::size_t column, std::string_view cell_data, [[maybe_unused]] CellTypeHint hint)
556 {
557 if (unknown_columns) {
558 if (row == 0) {
559 read_columns = column + 1;
560 parsed_values.resize(read_columns);
561 } else if (row == 1) {
562 unknown_columns = false;
563 info.setColumns(read_columns);
564 if (rows_hint.has_value()) {
565 parsed_values.reserve(rows_hint.value() * info.getColumns());
566 }
567 }
568 }
569
570 if (!unknown_columns) {
571 if (parsed_values.size() < ((row + 1) * info.getColumns())) {
572 parsed_values.resize((row + 1) * info.getColumns());
573 }
574 }
575 parsed_values[info.matrixIndex(row, column)] =
576 BehaviorPolicy::template create<typename Vector::value_type>(cell_data, hint);
577 }
578 );
579 std::swap(values, parsed_values);
580
581 if (values.empty()) {
582 MatrixInformation empty_info;
583 empty_info.setOrder(MatrixOrder::RowMajor);
584 return empty_info;
585 }
586
587 info.setRows(info.getColumns() == 0 ? 0 : (values.size() / info.getColumns()));
588
589 return info;
590}
591
592
593
594template<typename BehaviorPolicy>
595template<typename Vector>
596constexpr MatrixInformation Parser<BehaviorPolicy>::parseToVectorColumnMajor(std::string_view data, Vector& values,
597 std::size_t rows, std::optional<std::size_t> columns_hint) const
598{
599 Vector parsed_values;
600 if (columns_hint.has_value()) {
601 parsed_values.reserve(columns_hint.value() * rows);
602 }
603
605 info.setOrder(MatrixOrder::ColumnMajor);
606 info.setRows(rows);
607
608 parse(data,
609 [&](std::size_t row, std::size_t column, std::string_view cell_data, [[maybe_unused]] CellTypeHint hint)
610 {
611 if (parsed_values.size() < ((column + 1) * rows)) {
612 parsed_values.resize((column + 1) * rows);
613 }
614 parsed_values[info.matrixIndex(row, column)] =
615 BehaviorPolicy::template create<typename Vector::value_type>(cell_data, hint);
616 }
617 );
618 std::swap(values, parsed_values);
619
620 if (values.empty()) {
621 MatrixInformation empty_info;
622 empty_info.setOrder(MatrixOrder::ColumnMajor);
623 return empty_info;
624 }
625
626 info.setColumns(info.getRows() == 0 ? 0 : (values.size() / info.getRows()));
627
628 return info;
629}
630
631
632
633template<typename BehaviorPolicy>
634template<std::size_t rows, std::size_t columns, typename Cell>
635constexpr auto Parser<BehaviorPolicy>::parseToArray(std::string_view data, MatrixOrder order) const
636{
637 std::array<Cell, rows * columns> matrix;
638
639 parse(data,
640 [&matrix, order](std::size_t row, std::size_t column,
641 std::string_view cell_data, Csv::CellTypeHint hint)
642 constexpr
643 {
644 matrix[MatrixInformation::matrixIndex(row, column, rows, columns, order)] =
645 BehaviorPolicy::template create<Cell>(cell_data, hint);
646 }
647 );
648
649 return matrix;
650}
651
652
653
654} // end ns
655
656
657
658#endif
Matrix information (dimensions, order).
Definition csv_matrix.h:32
constexpr void setColumns(const std::size_t columns)
Set number of columns in matrix.
Definition csv_matrix.h:85
constexpr void setOrder(MatrixOrder order)
Set element order (row-major or column-major).
Definition csv_matrix.h:99
constexpr std::size_t getRows() const
Get number of rows in matrix.
Definition csv_matrix.h:64
constexpr std::size_t getColumns() const
Get number of columns in matrix.
Definition csv_matrix.h:78
constexpr void setRows(const std::size_t rows)
Set number of rows in matrix.
Definition csv_matrix.h:71
static constexpr std::size_t matrixIndex(std::size_t row, std::size_t column, std::size_t rows, std::size_t columns, MatrixOrder order)
Get index (offset) in flat-matrix vector.
Definition csv_matrix.h:42
Exception thrown on CSV parse error.
Definition csv_error.h:25
The main CSV parser class.
Definition csv_parser.h:70
constexpr void parseTo2DVector(std::string_view data, Vector2D &values) const
Parse CSV string data into a vector of columns.
Definition csv_parser.h:484
constexpr auto parseToArray(std::string_view data, MatrixOrder order) const
Parse CSV string to 1D std::array, stored in row-major or column-major order.
Definition csv_parser.h:635
constexpr MatrixInformation parseToVectorColumnMajor(std::string_view data, Vector &values, std::size_t rows, std::optional< std::size_t > columns_hint) const
Parse CSV string data into a flat matrix in column-major format (A11, A21, A31, A12,...
Definition csv_parser.h:596
constexpr void parse(std::string_view data, StoreCellFunction storeCellFunc) const
Parse CSV string data and store the results using a callback function.
Definition csv_parser.h:277
constexpr auto parseTo2DArray(std::string_view data) const
Parse CSV string to 2D std::array, an array of columns.
Definition csv_parser.h:507
constexpr MatrixInformation parseToVectorRowMajor(std::string_view data, Vector &values) const
Parse CSV string data into a flat matrix in row-major format (A11, A12, A13, A21, ....
Definition csv_parser.h:527
constexpr Parser()=default
Constructor.
Various Cell-related classes.
CellTypeHint
Type hint associated with the cell to determine the type of the cell value.
Definition csv_cell.h:32
Error handling for CSV parser.
Matrix-related classes used when parsing CSV data into matrices.
MatrixOrder
Order of elements in a matrix.
Definition csv_matrix.h:24
Behavior policies for Csv::Parser, passed as template parameter.
Utility functions used by the library.