Parse CSV
This page explains how to parse CSV text with rix/csv.
The examples use the public Rix facade:
#include <rix.hpp>and access CSV through:
rix.csvParsing turns CSV text into a table that can be inspected row by row.
Basic example
Create a file:
mkdir -p ~/rix-csv-parse
cd ~/rix-csv-parse
touch parse.cppAdd:
#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";
const auto table = rix.csv.parse(input);
rix.debug.print("rows:", table.size());
print_table(table);
return 0;
}Run it:
vix run parse.cppIf Rix is not available yet for single-file usage:
vix install -g rix/rix
vix run parse.cppExpected output
The output should look like this:
rows: 3
name language
Ada C++
Gaspard VixThe first row is the header.
The following rows are data rows.
Parse a string
Use:
const auto table = rix.csv.parse(input);Example:
const std::string input =
"name,language\n"
"Ada,C++\n"
"Gaspard,Vix\n";
const auto table = rix.csv.parse(input);The parsed table can be read with normal row and field access:
rix.debug.print(table[0][0]);
rix.debug.print(table[0][1]);
rix.debug.print(table[1][0]);
rix.debug.print(table[1][1]);For the example above:
table[0][0] -> name
table[0][1] -> language
table[1][0] -> Ada
table[1][1] -> C++Table model
A parsed CSV value is a table.
Conceptually:
Table
-> Row
-> FieldA table contains rows.
Each row contains fields.
Example:
const auto table = rix.csv.parse(input);
for (const auto &row : table)
{
for (const auto &field : row)
{
rix.debug.print(field);
}
}Count rows
Use:
table.size()Example:
const auto table = rix.csv.parse(input);
rix.debug.print("row count:", table.size());For this input:
name,language
Ada,C++
Gaspard,Vixthe row count is:
3because the header is also a row.
Check for empty CSV
Use:
table.empty()Example:
const auto table = rix.csv.parse(input);
if (table.empty())
{
rix.debug.eprint("CSV is empty");
return 1;
}Check for empty tables before accessing table[0].
Read the header
CSV does not force a special header type.
The first row can be treated as the header by convention:
const auto table = rix.csv.parse(input);
if (!table.empty())
{
const auto &header = table[0];
for (const auto &column : header)
{
rix.debug.print("column:", column);
}
}For this input:
name,language
Ada,C++the header is:
name
languageRead data rows
If the first row is a header, read data rows starting at index 1:
for (std::size_t i = 1; i < table.size(); ++i)
{
const auto &row = table[i];
if (row.size() < 2)
{
continue;
}
rix.debug.print("name:", row[0]);
rix.debug.print("language:", row[1]);
}This keeps the header separate from the data.
Validate row size
External CSV input can be inconsistent.
Do not assume every row has the expected number of fields.
Check each row before reading indexes:
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(row[0], row[1]);
}This avoids accessing a missing field.
Parse without a header
Some CSV input has no header.
Example:
const std::string input =
"Ada,C++\n"
"Gaspard,Vix\n";
const auto table = rix.csv.parse(input);
for (const auto &row : table)
{
if (row.size() < 2)
{
continue;
}
rix.debug.print("name:", row[0]);
rix.debug.print("language:", row[1]);
}In this case, every row is treated as data.
Parse quoted fields
CSV fields can contain commas when they are quoted.
Example input:
const std::string input =
"name,note\n"
"Ada,\"created with C++\"\n"
"Grace,\"likes systems, tools, and documents\"\n";
const auto table = rix.csv.parse(input);The comma inside the quoted field belongs to the field value.
It should not create a new column.
Parse quotes inside fields
CSV quotes inside a quoted field are usually escaped by doubling them.
Example input:
const std::string input =
"name,quote\n"
"Ada,\"She said \"\"hello\"\"\"\n";
const auto table = rix.csv.parse(input);The parsed quote field should represent:
She said "hello"Use CSV parsing instead of splitting strings manually.
Avoid manual splitting
Avoid this:
std::getline(line, field, ',');Manual splitting breaks when fields contain commas, quotes, or newlines.
Use:
const auto table = rix.csv.parse(input);The parser handles CSV structure for you.
Complete validation example
#include <rix.hpp>
#include <string>
int main()
{
const std::string input =
"name,language\n"
"Ada,C++\n"
"Gaspard,Vix\n";
const auto table = rix.csv.parse(input);
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;
}
rix.debug.print("header:", table[0][0], table[0][1]);
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]);
}
return 0;
}Run it:
vix run parse.cppUse in a Vix project
Create an application:
vix new csv-parse --app
cd csv-parseAdd Rix:
vix add rix/rix
vix installIn vix.app, add:
deps = [
"rix/rix",
]A small manifest can look like this:
name = "csv-parse"
type = "executable"
standard = "c++20"
output_dir = "bin"
sources = [
"src/main.cpp",
]
include_dirs = [
"include",
"src",
]
deps = [
"rix/rix",
]
packages = [
"vix",
]
links = [
"vix::vix",
]Then use:
#include <rix.hpp>
int main()
{
const auto table = rix.csv.parse(
"name,language\n"
"Ada,C++\n");
rix.debug.print("rows:", table.size());
return 0;
}Build and run:
vix build
vix runSingle-file usage
For small scripts, examples, and experiments, you can use:
vix run parse.cppIf Rix is installed globally for single-file usage:
vix install -g rix/rix
vix run parse.cppFor project usage, prefer:
vix add rix/rix
vix installand keep the dependency in vix.app:
deps = [
"rix/rix",
]Facade usage
The recommended documentation style is:
#include <rix.hpp>Then:
const auto table = rix.csv.parse(input);This keeps CSV available through the same public object as the other Rix packages:
rix.csv
rix.debug
rix.auth
rix.pdfIndependent package usage
If your project only needs CSV, you can install the independent package:
vix add rix/csv
vix installIn vix.app:
deps = [
"rix/csv",
]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()
{
const auto table = rix.csv.parse("name,lang\nAda,C++\n");
return 0;
}When at least one RIX_ENABLE_* macro is defined, only selected modules are mounted.
Common mistakes
Treating the header as data
If the first row is a header, start from index 1:
for (std::size_t i = 1; i < table.size(); ++i)
{
const auto &row = table[i];
}Accessing missing fields
Wrong:
rix.debug.print(row[0], row[1]);Better:
if (row.size() >= 2)
{
rix.debug.print(row[0], row[1]);
}Splitting CSV manually
Wrong:
std::getline(stream, field, ',');Better:
const auto table = rix.csv.parse(input);Forgetting deps
For a Vix project, do not put Rix packages in packages.
Use:
deps = [
"rix/rix",
]packages is for CMake package discovery.
deps is for Vix Registry packages.
What you should remember
Parse CSV:
const auto table = rix.csv.parse(input);Check for empty input:
if (table.empty())
{
return 1;
}Read rows:
for (const auto &row : table)
{
for (const auto &field : row)
{
rix.debug.print(field);
}
}Read data rows after a header:
for (std::size_t i = 1; i < table.size(); ++i)
{
const auto &row = table[i];
}Validate row size before indexing:
if (row.size() >= 2)
{
rix.debug.print(row[0], row[1]);
}Next step
Learn how to write CSV output.
Next: Write CSV