MayaFlux 1.0.0
Digital-First Multimedia Processing Framework
Loading...
Searching...
No Matches
DelimitedTextReader.hpp
Go to the documentation of this file.
1#pragma once
2
3#include "CompositeReader.hpp"
4
5#include <fstream>
6
7namespace MayaFlux::IO {
8
9/**
10 * @class DelimitedTextReader
11 * @brief Incremental CSV and TSV reader producing CompositeArray values.
12 *
13 * A header row supplies field names by default. Without a supplied layout,
14 * every field is stored as UTF-8 text, preserving values without speculative
15 * numeric inference. Supply a CompositeLayout before open() to select exact
16 * arithmetic field types; header names must then match its field order.
17 * Empty numeric cells and missing trailing cells remain absent. An
18 * unquoted empty text cell is absent; a quoted empty cell is present.
19 * char, signed char, and unsigned char fields parse as small integers via
20 * from_chars, the same path as every other arithmetic type; a cell must
21 * contain a number such as "65", not a literal character such as "A".
22 *
23 * Usage:
24 * @code
25 * CompositeLayout layout;
26 * layout.add_field<std::string>("name");
27 * layout.add_field<double>("score");
28 * DelimitedTextReader reader;
29 *
30 * reader.set_layout(std::move(layout));
31 * if (reader.open("scores.csv")) {
32 * while (auto batch = reader.read_next(512)) {
33 * if (batch->empty())
34 * break;
35 * }
36 * }
37 * @endcode
38 *
39 * CSV and TSV share the same quoting rules. The delimiter defaults to the
40 * file extension and can be overridden before open().
41 */
42class MAYAFLUX_API DelimitedTextReader : public CompositeReader {
43public:
46
47 /**
48 * @brief Set an exact field layout before opening a file.
49 * @param layout Named arithmetic or text fields in file order.
50 * @return False when a file is already open.
51 */
52 bool set_layout(Kakshya::CompositeLayout layout);
53
54 /**
55 * @brief Select whether the first record supplies field names.
56 * @param enabled True by default; a layout is required when false.
57 * @return False when a file is already open.
58 */
59 bool set_has_header(bool enabled) noexcept;
60
61 /**
62 * @brief Override the delimiter chosen from .csv or .tsv.
63 * @param delimiter A nonzero character other than quote, CR, or LF.
64 * @return False for an invalid delimiter or an open file.
65 */
66 bool set_delimiter(char delimiter) noexcept;
67
68 /**
69 * @brief Check for the .csv or .tsv extension.
70 * @param filepath Candidate file path.
71 */
72 [[nodiscard]] bool can_read(const std::string& filepath) const override;
73
74 /**
75 * @brief Open a CSV or TSV file and establish its Composite layout.
76 * @param filepath Source path.
77 * @param options FileReader options.
78 * @return True when the file and header are valid.
79 */
80 bool open(const std::string& filepath,
81 FileReadOptions options = FileReadOptions::ALL) override;
82
83 /** @brief Close the input and release its active layout. */
84 void close() override;
85
86 /** @brief Whether a file is currently open. */
87 [[nodiscard]] bool is_open() const override { return m_file.is_open(); }
88
89 /** @brief File metadata and ordered field names, when open. */
90 [[nodiscard]] std::optional<FileMetadata> get_metadata() const override;
91
92 /** @brief Delimited text does not declare semantic file regions. */
93 [[nodiscard]] std::vector<FileRegion> get_regions() const override { return {}; }
94
95 /**
96 * @brief Consume all remaining records into one array.
97 * @return Owning array or nullopt on parse or conversion failure.
98 */
99 [[nodiscard]] std::optional<Kakshya::CompositeArray>
100 read_composite() override;
101
102 /**
103 * @brief Consume a bounded number of records.
104 * @param max_elements Maximum records to return; zero returns an empty
105 * array without advancing.
106 * @return Owning array, empty at end of file, or nullopt on failure.
107 */
108 [[nodiscard]] std::optional<Kakshya::CompositeArray>
109 read_next(size_t max_elements) override;
110
111 /** @brief Copy the layout established by open(), if available. */
112 [[nodiscard]] std::optional<Kakshya::CompositeLayout>
113 get_layout() const override
114 {
115 return m_layout;
116 }
117
118 /** @brief Zero-based index of the next data record. */
119 [[nodiscard]] std::vector<uint64_t> get_read_position() const override
120 {
121 return { m_row_position };
122 }
123
124 /**
125 * @brief Seek to a zero-based data record by replaying from the first.
126 * @param position One record index.
127 * @return False for an invalid or out-of-range position.
128 * @note On an out-of-range position, the reader is left at the end of
129 * file (get_read_position() reflects how far the replay reached, and
130 * subsequent read_next() calls return an empty array), not at an
131 * undefined position.
132 */
133 bool seek(const std::vector<uint64_t>& position) override;
134
135 /** @brief Supported file extensions without dots. */
136 [[nodiscard]] std::vector<std::string> get_supported_extensions() const override
137 {
138 return { "csv", "tsv" };
139 }
140
141 /** @brief Bounded sequential reads are supported. */
142 [[nodiscard]] bool supports_streaming() const override { return true; }
143
144 /** @brief Suggested number of records per bounded read. */
145 [[nodiscard]] uint64_t get_preferred_chunk_size() const override { return 1024; }
146
147 /** @brief Delimited data has one element axis. */
148 [[nodiscard]] size_t get_num_dimensions() const override { return 1; }
149
150 /** @brief Total element count is not known without scanning the file. */
151 [[nodiscard]] std::vector<uint64_t> get_dimension_sizes() const override { return {}; }
152
153private:
154 struct ParsedField {
155 std::string value;
156 bool quoted {};
157 };
158
159 std::ifstream m_file;
160 std::string m_filepath;
161 std::optional<Kakshya::CompositeLayout> m_requested_layout;
162 std::optional<Kakshya::CompositeLayout> m_layout;
163
164 std::streampos m_data_start {};
165 uint64_t m_row_position {};
166
167 char m_delimiter {};
168 char m_active_delimiter {};
169 bool m_has_header { true };
170 bool m_at_end {};
171
172 [[nodiscard]] bool read_record(
173 std::vector<ParsedField>& fields, bool& at_end);
174
175 [[nodiscard]] bool append_record(
177 const std::vector<ParsedField>& fields);
178};
179
180}
std::optional< glm::vec3 > position
Definition Decoder.cpp:93
Typed FileReader boundary for schema-bearing Composite data.
std::optional< Kakshya::CompositeLayout > m_requested_layout
std::optional< Kakshya::CompositeLayout > get_layout() const override
Copy the layout established by open(), if available.
bool supports_streaming() const override
Bounded sequential reads are supported.
std::vector< FileRegion > get_regions() const override
Delimited text does not declare semantic file regions.
bool is_open() const override
Whether a file is currently open.
std::vector< uint64_t > get_dimension_sizes() const override
Total element count is not known without scanning the file.
std::vector< uint64_t > get_read_position() const override
Zero-based index of the next data record.
uint64_t get_preferred_chunk_size() const override
Suggested number of records per bounded read.
std::optional< Kakshya::CompositeLayout > m_layout
std::vector< std::string > get_supported_extensions() const override
Supported file extensions without dots.
size_t get_num_dimensions() const override
Delimited data has one element axis.
Incremental CSV and TSV reader producing CompositeArray values.
Owns a sequence of values that share a CompositeLayout.
Shared field description and byte layout for a CompositeArray.
Definition Composite.hpp:52
FileReadOptions
Generic options for file reading behavior.