13#include <unordered_set>
23#if defined(__has_include)
24#if __has_include(<expected>)
26#ifdef __cpp_lib_expected
27#define CSV_HAS_STD_EXPECTED
37#include "../external/classify_scalar.hpp"
43 template<
typename RowSink,
typename ParsePolicy,
typename FieldPolicy,
typename RowPolicy>
45 struct CSVRowRowPolicy;
47 class CSVParserDriverBase;
49 namespace speculative {
50 struct CSVRowFragment;
53 namespace data_frame {
struct RowViewAccessor; }
55 static const std::string ERROR_NAN =
"Not a number.";
56 static const std::string ERROR_OVERFLOW =
"Overflow error.";
57 static const std::string ERROR_FLOAT_TO_INT =
58 "Attempted to convert a floating point value to an integral type.";
59 static const std::string ERROR_NEG_TO_UNSIGNED =
"Negative numbers cannot be converted to unsigned types.";
83 switch (scalar.type) {
88 this->value_.integer = scalar.integer;
92 this->value_.floating = scalar.floating;
95 this->value_.timestamp = scalar.timestamp;
98 this->value_.boolean = scalar.boolean;
109 operator std::string()
const {
110 return std::string(this->sv);
139#if CSV_EXCEPTIONS_ENABLED
140 template<
typename T = std::
string> T
get()
const {
149#ifdef CSV_HAS_STD_EXPECTED
161 template<
typename T = std::
string>
162 std::expected<T, CSVConversionError>
as()
const {
167 ? std::expected<T, CSVConversionError>(std::unexpected(err))
168 : std::expected<T, CSVConversionError>(out);
187 template<
typename T = std::
string>
198 error = check_convert(out);
212 operator std::optional<T>()
const {
214 return try_get(out) ? std::optional<T>(out) : std::nullopt;
221 template<
typename T =
long long>
223 static_assert(std::is_integral<T>::value,
224 "try_parse_hex only works with integral types (int, long, long long, etc.)");
226 return classify_scalar::parse_hex(this->sv.data(), this->sv.data() + this->sv.size(), parsedValue);
245#ifdef DOXYGEN_SHOULD_SKIP_THIS
250 internals::enable_if_t<
251 std::is_integral<T>::value && std::is_unsigned<T>::value && !std::is_same<T, bool>::value
252 && (
sizeof(T) >=
sizeof(std::uint64_t)),
256 std::uint64_t milliseconds = 0;
260 out =
static_cast<T
>(milliseconds);
266 template<
typename Rep,
typename Period>
268 std::uint64_t milliseconds = 0;
272 out = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(
273 std::chrono::milliseconds(milliseconds));
278 template<
typename Duration>
279 bool try_parse_timestamp(std::chrono::time_point<std::chrono::system_clock, Duration>& out)
const noexcept {
280 std::uint64_t milliseconds = 0;
284 out = std::chrono::time_point<std::chrono::system_clock, Duration>(
285 std::chrono::duration_cast<Duration>(std::chrono::milliseconds(milliseconds)));
305 static_assert(std::is_arithmetic<T>::value,
306 "T should be a numeric value.");
313 return internals::is_equal(this->numeric_value_as_long_double(),
static_cast<long double>(other), 0.000001L);
354#if defined(__GNUC__) && !defined(__clang__)
356 constexpr FieldValue() noexcept
357 : integer(0), floating(0), timestamp(0),
boolean(false) {}
359 std::int64_t integer;
360 long double floating;
361 std::uint64_t timestamp;
366 constexpr FieldValue() noexcept : floating(0) {}
368 std::int64_t integer;
369 long double floating;
370 std::uint64_t timestamp;
375 struct FieldValueOutput {
378 template<
classify_scalar::ScalarKind Kind>
379 typename std::enable_if<
380 Kind == classify_scalar::scalar_int8
381 || Kind == classify_scalar::scalar_int16
382 || Kind == classify_scalar::scalar_int32
383 || Kind == classify_scalar::scalar_int64,
384 void>
::type set(std::int64_t parsed)
const noexcept {
385 value.integer = parsed;
388 template<
classify_scalar::ScalarKind Kind>
389 typename std::enable_if<Kind == classify_scalar::scalar_float, void>::type set(
long double parsed)
const noexcept {
390 value.floating = parsed;
393 template<
classify_scalar::ScalarKind Kind>
394 typename std::enable_if<Kind == classify_scalar::scalar_bool, void>::type set(
bool parsed)
const noexcept {
395 value.boolean = parsed;
398 template<
classify_scalar::ScalarKind Kind>
399 typename std::enable_if<Kind == classify_scalar::scalar_timestamp, void>::type set(std::uint64_t parsed)
const noexcept {
400 value.timestamp = parsed;
404 mutable FieldValue value_;
406 mutable DataType type_ = DataType::UNKNOWN;
408 bool stores_integral() const noexcept {
412 long double numeric_value_as_long_double() const noexcept {
413 return stores_integral()
414 ?
static_cast<long double>(value_.integer)
418 void cache_parsed_value(
DataType parsed_type,
long double parsed_value)
noexcept {
422 value_.integer =
static_cast<std::int64_t
>(parsed_value);
425 value_.floating = parsed_value;
433 out.assign(this->sv.data(), this->sv.size());
444 out = this->numeric_value_as_long_double();
452 out = this->value_.boolean;
456 template<
typename Rep,
typename Period>
457 CSVConversionError check_convert(std::chrono::duration<Rep, Period>& out)
const noexcept {
461 out = std::chrono::duration_cast<std::chrono::duration<Rep, Period>>(
462 std::chrono::milliseconds(this->value_.timestamp));
466 template<
typename Duration>
467 CSVConversionError check_convert(std::chrono::time_point<std::chrono::system_clock, Duration>& out)
const noexcept {
471 out = std::chrono::time_point<std::chrono::system_clock, Duration>(
472 std::chrono::duration_cast<Duration>(std::chrono::milliseconds(this->value_.timestamp)));
490 if (this->numeric_value_as_long_double() < 0)
496 const long double value = this->numeric_value_as_long_double();
497 if (value <
static_cast<long double>(std::numeric_limits<T>::min())
498 || value >
static_cast<long double>(std::numeric_limits<T>::max())) {
503 out = this->stores_integral()
504 ?
static_cast<T
>(this->value_.integer)
505 : static_cast<T>(this->value_.floating);
510 inline void get_value() const noexcept {
511 if ((
int)type_ < 0) {
512 if (this->sv.empty()) {
517 const char* first = this->sv.data();
518 const char* last = first + this->sv.size();
519 typedef classify_scalar::policy_pack<
520 classify_scalar::builtin_numeric_policy<
'.',
false>,
521 classify_scalar::builtin_timestamp_policy,
522 classify_scalar::builtin_bool_policy
523 > csv_field_policy_pack;
525 type_ = classify_scalar::classify_scalar<
527 true>(first, last, FieldValueOutput{ this->value_ }, csv_field_policy_pack());
535 template<
typename RowSink,
typename ParsePolicy,
typename FieldPolicy,
typename RowPolicy>
537 friend struct internals::CSVRowRowPolicy;
538 friend struct internals::data_frame::RowViewAccessor;
539 friend internals::parser::CSVParserDriverBase;
540 friend struct internals::speculative::CSVRowFragment;
545 CSVRow(internals::RawCSVDataPtr _data) : data(_data) {}
546 CSVRow(internals::RawCSVDataPtr _data,
size_t _data_start,
size_t _field_bounds)
547 : data(_data), data_start(_data_start), fields_start(_field_bounds) {}
548 CSVRow(internals::RawCSVDataPtr _data,
size_t _data_start,
size_t _field_bounds,
size_t _row_length)
549 : data(_data), data_start(_data_start), fields_start(_field_bounds), row_length(_row_length) {}
559 return this->data ? this->data->source_start + this->data_start : 0;
570#if CSV_EXCEPTIONS_ENABLED
576#if !CSV_EXCEPTIONS_ENABLED
578 inline std::string to_json()
const {
579 const auto* converter = this->get_json_converter();
580 return converter ? converter->row_to_json(this->
size(),
581 [
this](
size_t i) {
return this->get_field(i); }) :
"{}";
584 inline std::string to_json_array()
const {
585 const auto* converter = this->get_json_converter();
586 return converter ? converter->row_to_json_array(this->
size(),
587 [
this](
size_t i) {
return this->get_field(i); }) :
"[]";
593 const auto* converter = this->get_json_converter();
594 return converter ? converter->try_row_to_json(this->
size(),
595 [
this](
size_t i) {
return this->get_field(i); }, subset, error) :
"{}";
597 inline std::string try_to_json_array(
const std::vector<std::string>& subset,
CSVError& error)
const {
599 const auto* converter = this->get_json_converter();
600 return converter ? converter->try_row_to_json_array(this->
size(),
601 [
this](
size_t i) {
return this->get_field(i); }, subset, error) :
"[]";
603#if CSV_EXCEPTIONS_ENABLED
604 inline std::string to_json(
const std::vector<std::string>& subset = {})
const {
607 internals::throw_error(error);
610 inline std::string to_json_array(
const std::vector<std::string>& subset = {})
const {
612 auto out = try_to_json_array(subset, error);
613 internals::throw_error(error);
620 return this->data->col_names->get_col_names();
625 return this->data->col_names;
631#if CSV_EXCEPTIONS_ENABLED
636#if CSV_EXCEPTIONS_ENABLED
638 const std::vector<std::string>& subset
648 return std::views::iota(
size_t{0}, this->
size())
649 | std::views::transform([
this](
size_t i) {
return this->get_field(i); });
661 operator std::vector<std::string>()
const;
691 const CSVField* operator->()
const noexcept;
697#ifndef DOXYGEN_SHOULD_SKIP_THIS
699 using difference_type = std::ptrdiff_t;
701 using iterator_category = std::input_iterator_tag;
703 using iterator_concept = std::random_access_iterator_tag;
709 reference operator*()
const;
711 reference
operator[](difference_type n)
const;
717 iterator operator+(difference_type n) const noexcept;
718 iterator operator-(difference_type n) const noexcept;
719 iterator& operator+=(difference_type n) noexcept;
720 iterator& operator-=(difference_type n) noexcept;
721 difference_type operator-(const
iterator& other) const noexcept;
724 bool operator==(const
iterator& other) const noexcept;
725 bool operator!=(const
iterator& other) const noexcept;
726 bool operator<(const
iterator& other) const noexcept;
727 bool operator>(const
iterator& other) const noexcept;
728 bool operator<=(const
iterator& other) const noexcept;
729 bool operator>=(const
iterator& other) const noexcept;
732 internals::RawCSVDataPtr data_;
733 size_t data_start_ = 0;
734 size_t fields_start_ = 0;
735 size_t row_length_ = 0;
736 difference_type index_ = 0;
745#ifndef DOXYGEN_SHOULD_SKIP_THIS
746 using value_type = iterator::value_type;
747 using difference_type = iterator::difference_type;
749 using reference = iterator::reference;
750 using iterator_category = std::input_iterator_tag;
752 using iterator_concept = std::random_access_iterator_tag;
758 reference operator*()
const;
759 pointer operator->()
const;
760 reference
operator[](difference_type n)
const;
790 CSVError& error,
const std::vector<std::string>& subset)
const;
803 std::unordered_map<std::
string, std::
string> to_unordered_map_impl(
804 const std::vector<std::
string>* subset,
CSVError& error) const;
807 inline
csv::
string_view get_field_impl(
size_t index, const internals::RawCSVDataPtr& _data)
const {
808#if CSV_EXCEPTIONS_ENABLED
809 if (index >= this->
size())
810 throw std::runtime_error(internals::CSV_ERROR_INDEX_OUT_OF_BOUNDS);
812 CSV_DEBUG_ASSERT(index < this->
size());
815 const size_t field_index = this->fields_start + index;
816 const auto field = _data->fields[field_index];
818 if (field.has_realized_storage()) {
819 field_str = _data->quote_arena.view(field.start, field.length);
822 field_str =
csv::string_view(_data->data).substr(this->data_start + field.start, field.length);
825 if (_data->has_ws_trimming) {
826 field_str = internals::get_trimmed(field_str, _data->ws_flags);
832 CSVField make_field(
size_t index,
const internals::RawCSVDataPtr& _data)
const;
840 csv::string_view get_field_safe(
size_t index, internals::RawCSVDataPtr _data)
const;
842 const internals::JsonConverter* get_json_converter()
const {
843 if (this->data.get() ==
nullptr) {
847 return &this->data->json_converter.get_or_create([
this]() {
848 const std::vector<std::string> columns = this->data->col_names
849 ? this->data->col_names->get_col_names()
850 : std::vector<std::string>();
851 return std::make_shared<internals::JsonConverter>(columns);
855 internals::RawCSVDataPtr data;
858 size_t data_start = 0;
861 size_t fields_start = 0;
864 size_t row_length = 0;
867 size_t data_end = (std::numeric_limits<size_t>::max)();
871#pragma region CSVField::get Specializations
873#if CSV_EXCEPTIONS_ENABLED
876 inline std::string CSVField::get<std::string>()
const {
877 return std::string(this->sv);
892 inline long double CSVField::get<long double>()
const {
894 throw std::runtime_error(internals::ERROR_NAN);
896 return this->numeric_value_as_long_double();
903 CONSTEXPR_14
bool CSVField::try_get<csv::string_view>(
csv::string_view& out)
const noexcept {
908#pragma endregion CSVField::get Specializations
915 return this->sv == other;
922 return this->sv == other;
Data type representing individual CSV values.
bool try_parse_decimal(long double &dVal, const char decimalSymbol='.')
Attempts to parse a decimal (or integer) value using the given symbol, returning true if the value is...
bool try_get(T &out, CSVConversionError &error) const noexcept
Convert with the same rules as try_get(), retaining the failure reason.
bool try_parse_timestamp(T &out) const noexcept
Parse this field as Unix milliseconds in a 64-bit unsigned integer.
bool is_timestamp() const noexcept
Returns true if field is a timestamp value.
bool try_parse_timestamp(std::uint64_t &out) const noexcept
Parse this field as Unix milliseconds.
DataType type() const noexcept
Return the type of the underlying CSV data.
bool is_num() const noexcept
Returns true if field is an integer or float.
bool is_bool() const noexcept
Returns true if field is a boolean value.
bool operator==(T other) const noexcept
Compares the contents of this field to a numeric value.
bool is_str() const noexcept
Returns true if field is a non-numeric, non-empty string.
bool try_parse_timestamp(std::chrono::duration< Rep, Period > &out) const noexcept
Parse this field as a timestamp duration since the Unix epoch.
bool try_parse_hex(T &parsedValue) const
Parse a hexadecimal value, returning false if the value is not hex.
bool try_get(T &out) const noexcept
Non-throwing equivalent of get().
bool is_null() const noexcept
Returns true if field is an empty string or string of whitespace characters.
constexpr CSVField(csv::string_view _sv) noexcept
Constructs a CSVField from a string_view.
T get() const
Returns the value casted to the requested type, performing type checking before.
bool is_float() const noexcept
Returns true if field is a floating point value.
bool is_int() const noexcept
Returns true if field is an integer.
CONSTEXPR csv::string_view get_sv() const noexcept
Return a string view over the field's contents.
bool try_parse_timestamp(std::chrono::time_point< std::chrono::system_clock, Duration > &out) const noexcept
Parse this field as a std::chrono::system_clock time point.
std::expected< T, CSVConversionError > as() const
Return this field as T, preserving conversion failure as CSVConversionError.
Temporary arrow proxy owning a field value, without a heap allocation.
Iterator producing short-lived CSVField values with independent lazy conversion caches.
Reverse field-value iterator available since C++11.
Data structure for representing CSV rows.
CSVField try_field(size_t index, CSVError &error) const
Checked field retrieval.
CONSTEXPR bool empty() const noexcept
Indicates whether row is empty or not.
csv::string_view raw_str() const noexcept
Return a string_view of the raw bytes of this row as they appear in the underlying parse buffer,...
const std::vector< std::string > & get_col_names() const
Retrieve this row's associated column names.
std::unordered_map< std::string, std::string > try_to_unordered_map(CSVError &error) const
Materialize named values; unknown names or absent header positions report an error.
auto to_sv_range() const
Convert this CSVRow into a std::ranges::input_range of string_views.
std::unordered_map< std::string, std::string > to_unordered_map() const
Convert this CSVRow into an unordered map.
CONSTEXPR size_t size() const noexcept
Return the number of fields in this row.
std::string try_to_json(const std::vector< std::string > &subset, CSVError &error) const
Serialize a subset, reporting unknown column names.
CSVField operator[](size_t n) const
Return the field at n.
internals::ConstColNamesPtr col_names_ptr() const noexcept
Internal accessor for preserving resolved column-name lookup policy across helper types.
size_t byte_offset() const noexcept
Return the absolute byte offset where this row starts in the source.
CSVRow(internals::RawCSVDataPtr _data)
Construct a CSVRow view over parsed row storage.
A standalone header file containing shared code.
#define IF_CONSTEXPR
Expands to if constexpr in C++17 and if otherwise.
#define CONSTEXPR
Expands to constexpr in decent compilers and inline otherwise.
Shared exception message templates and throw helpers.
CSV scalar type classification adapter.
Internal JSON serialization helpers for row-like CSV data.
The all encompassing namespace.
DataType
Enumerates the different CSV field types recognized by this library.
@ CSV_TIMESTAMP
Timestamp value.
@ CSV_INT64
64-bit integer
@ CSV_DOUBLE
Floating point value.
@ CSV_BIGINT
Integer too large to fit in 64 bits.
@ CSV_INT16
16-bit integer
@ CSV_INT32
32-bit integer
@ CSV_STRING
Non-scalar string.
CSVConversionError
Non-throwing CSVField conversion result.
@ FloatToInt
A floating point field was requested as an integral type.
@ Overflow
The parsed value does not fit in the requested target type.
@ NegativeToUnsigned
A negative value was requested as an unsigned type.
@ None
Conversion succeeded.
@ NotANumber
The field is not compatible with the requested target type.
const char * csv_conversion_error_message(CSVConversionError error) noexcept
Return a stable human-readable description for a CSVConversionError.
std::string_view string_view
The string_view class used by this library.
Internal data structures for CSV parsing.
A diagnostic whose text remains valid for the lifetime of the error.
Cached scalar classification and parsed value for one CSV field.