CSV Options
This page explains how to use options with rix/csv.
The examples use the public Rix facade:
#include <rix.hpp>and access CSV through:
rix.csvCSV options let you customize parsing and writing behavior without changing the rest of your application code.
Basic idea
For simple CSV input, use:
const auto table = rix.csv.parse(input);When you need custom behavior, create options and pass them to the parser or writer.
Example shape:
rixlib::csv::Options options;
const auto table = rix.csv.parse(input, options);2
3
Options are useful when your application needs to:
trim fields
transform fields
skip rows
filter rows
customize CSV handling
keep parsing logic reusable2
3
4
5
6
Complete example
Create a file:
mkdir -p ~/rix-csv-options
cd ~/rix-csv-options
touch options.cpp2
3
Add:
#include <rix.hpp>
#include <sstream>
#include <string>
static void print_table(const rixlib::csv::Table &table)
{
for (const auto &row : table)
{
std::ostringstream line;
for (std::size_t i = 0; i < row.size(); ++i)
{
if (i > 0)
{
line << " ";
}
line << row[i];
}
rix.debug.print(line.str());
}
}
int main()
{
const std::string input =
"name,language\n"
"Ada,C++\n"
"Gaspard,Vix\n"
"Skip,Ignored\n";
rixlib::csv::Options options;
options.field_transformer = [](std::string value) {
if (value == "Vix")
{
return std::string{"Vix.cpp"};
}
return value;
};
options.row_filter = [](const rixlib::csv::Row &row) {
if (!row.empty() && row[0] == "Skip")
{
return false;
}
return true;
};
const auto table = rix.csv.parse(input, options);
rix.debug.print("rows:", table.size());
print_table(table);
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
Run it:
vix run options.cppIf Rix is not available yet for single-file usage:
vix install -g rix/rix
vix run options.cpp2
Expected output
The output should look like this:
rows: 3
name language
Ada C++
Gaspard Vix.cpp2
3
4
The row starting with Skip is removed.
The field Vix is transformed into Vix.cpp.
Options object
Create an options object:
rixlib::csv::Options options;Then pass it to parsing:
const auto table = rix.csv.parse(input, options);Use options when the CSV parser should do more than return the raw fields.
Field transformer
A field transformer receives a field value and returns the value that should be stored in the parsed table.
Example:
options.field_transformer = [](std::string value) {
if (value == "Vix")
{
return std::string{"Vix.cpp"};
}
return value;
};2
3
4
5
6
7
8
Use this for small transformations such as:
normalizing names
rewriting values
cleaning imported data
mapping old values to new values2
3
4
Trim-like transformer
If your CSV input contains extra spaces, you can use a transformer to normalize fields.
Example shape:
options.field_transformer = [](std::string value) {
while (!value.empty() && value.front() == ' ')
{
value.erase(value.begin());
}
while (!value.empty() && value.back() == ' ')
{
value.pop_back();
}
return value;
};2
3
4
5
6
7
8
9
10
11
12
13
Then parse:
const auto table = rix.csv.parse(input, options);This keeps cleanup logic close to parsing.
Row filter
A row filter receives a parsed row and decides whether the row should be kept.
Example:
options.row_filter = [](const rixlib::csv::Row &row) {
if (!row.empty() && row[0] == "Skip")
{
return false;
}
return true;
};2
3
4
5
6
7
8
Return:
true -> keep the row
false -> remove the row2
Use row filters for:
skipping empty rows
skipping comments
removing invalid rows
filtering imported data
keeping only rows your app needs2
3
4
5
Skip empty rows
Example:
options.row_filter = [](const rixlib::csv::Row &row) {
for (const auto &field : row)
{
if (!field.empty())
{
return true;
}
}
return false;
};2
3
4
5
6
7
8
9
10
11
This removes rows where every field is empty.
Skip comment rows
If your CSV input uses comment-style rows:
# generated data
name,language
Ada,C++
Gaspard,Vix2
3
4
you can filter rows whose first field starts with #:
options.row_filter = [](const rixlib::csv::Row &row) {
if (!row.empty() && !row[0].empty() && row[0][0] == '#')
{
return false;
}
return true;
};2
3
4
5
6
7
8
Then parse normally:
const auto table = rix.csv.parse(input, options);Combine transformer and filter
You can use both hooks together.
rixlib::csv::Options options;
options.field_transformer = [](std::string value) {
if (value == "Vix")
{
return std::string{"Vix.cpp"};
}
return value;
};
options.row_filter = [](const rixlib::csv::Row &row) {
return !row.empty();
};
const auto table = rix.csv.parse(input, options);2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
The field transformer runs on field values.
The row filter decides whether each parsed row should be kept.
Validate after parsing
Options help transform and filter CSV, but you should still validate external input.
Example:
const auto table = rix.csv.parse(input, options);
if (table.empty())
{
rix.debug.eprint("CSV is empty");
return 1;
}
if (table[0].size() < 2)
{
rix.debug.eprint("expected at least two columns");
return 1;
}2
3
4
5
6
7
8
9
10
11
12
13
Check row size before indexing:
for (std::size_t i = 1; i < table.size(); ++i)
{
const auto &row = table[i];
if (row.size() < 2)
{
rix.debug.eprint("invalid row:", i);
continue;
}
rix.debug.print("name:", row[0]);
rix.debug.print("language:", row[1]);
}2
3
4
5
6
7
8
9
10
11
12
13
Options and headers
CSV headers are still normal rows.
If the first row is a header, your row filter should normally keep it.
Example:
options.row_filter = [](const rixlib::csv::Row &row) {
if (row.empty())
{
return false;
}
return true;
};2
3
4
5
6
7
8
Then process the header separately:
const auto &header = table[0];and data rows from index 1:
for (std::size_t i = 1; i < table.size(); ++i)
{
const auto &row = table[i];
}2
3
4
Options in a Vix project
Create an application:
vix new csv-options --app
cd csv-options2
Add Rix:
vix add rix/rix
vix install2
In vix.app, add:
deps = [
"rix/rix",
]2
3
A small manifest can look like this:
name = "csv-options"
type = "executable"
standard = "c++20"
output_dir = "bin"
sources = [
"src/main.cpp",
]
include_dirs = [
"include",
"src",
]
deps = [
"rix/rix",
]
packages = [
"vix",
]
links = [
"vix::vix",
]2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
Then use options in src/main.cpp:
#include <rix.hpp>
int main()
{
const std::string input =
"name,language\n"
"Ada,C++\n"
"Gaspard,Vix\n";
rixlib::csv::Options options;
options.field_transformer = [](std::string value) {
if (value == "Vix")
{
return std::string{"Vix.cpp"};
}
return value;
};
const auto table = rix.csv.parse(input, options);
rix.debug.print("rows:", table.size());
return 0;
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
Build and run:
vix build
vix run2
Single-file usage
For small scripts, examples, and experiments:
vix run options.cppIf Rix is installed globally for single-file usage:
vix install -g rix/rix
vix run options.cpp2
For project usage, prefer:
vix add rix/rix
vix install2
and keep the dependency in vix.app:
deps = [
"rix/rix",
]2
3
Facade usage
The recommended documentation style is:
#include <rix.hpp>Then:
const auto table = rix.csv.parse(input, options);This keeps CSV available through the same public object as the other Rix packages:
rix.csv
rix.debug
rix.auth
rix.pdf2
3
4
Independent package usage
If your project only needs CSV, you can install the independent package:
vix add rix/csv
vix install2
In vix.app:
deps = [
"rix/csv",
]2
3
Then include:
#include <rix/csv.hpp>Use this style when you do not need the unified rix.* facade.
Lightweight facade usage
If you want the facade style but only want CSV mounted:
#define RIX_ENABLE_CSV
#include <rix.hpp>
int main()
{
rixlib::csv::Options options;
const auto table = rix.csv.parse("name,lang\nAda,C++\n", options);
return 0;
}2
3
4
5
6
7
8
9
10
11
When at least one RIX_ENABLE_* macro is defined, only selected modules are mounted.
Common mistakes
Filtering out the header by accident
If the first row is a header, make sure the filter keeps it.
Wrong:
options.row_filter = [](const rixlib::csv::Row &row) {
return row[0] != "name";
};2
3
This removes the header row.
Accessing empty rows inside a filter
Wrong:
options.row_filter = [](const rixlib::csv::Row &row) {
return row[0] != "Skip";
};2
3
Better:
options.row_filter = [](const rixlib::csv::Row &row) {
return row.empty() || row[0] != "Skip";
};2
3
Check that the row has fields before reading row[0].
Doing large business logic inside parsing options
Options are best for small parsing-time transformations.
Keep larger application decisions in normal code after parsing.
Forgetting deps
For a Vix project, do not put Rix packages in packages.
Use:
deps = [
"rix/rix",
]2
3
packages is for CMake package discovery.
deps is for Vix Registry packages.
What you should remember
Create options:
rixlib::csv::Options options;Transform fields:
options.field_transformer = [](std::string value) {
return value;
};2
3
Filter rows:
options.row_filter = [](const rixlib::csv::Row &row) {
return true;
};2
3
Parse with options:
const auto table = rix.csv.parse(input, options);Validate the parsed table before indexing external data.
Use options for small parse-time cleanup.
Use normal application code for larger business rules.
Next step
Learn how to access CSV rows safely.
Next: Safe Access