Rix is the official package layer for Vix.cpp Browse packages
Skip to content

Parse CSV

This page explains how to parse CSV text with rix/csv.

The examples use the public Rix facade:

cpp
#include <rix.hpp>

and access CSV through:

cpp
rix.csv

Parsing turns CSV text into a table that can be inspected row by row.

Basic example

Create a file:

bash
mkdir -p ~/rix-csv-parse
cd ~/rix-csv-parse
touch parse.cpp

Add:

cpp
#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:

bash
vix run parse.cpp

If Rix is not available yet for single-file usage:

bash
vix install -g rix/rix
vix run parse.cpp

Expected output

The output should look like this:

txt
rows: 3
name language
Ada C++
Gaspard Vix

The first row is the header.

The following rows are data rows.

Parse a string

Use:

cpp
const auto table = rix.csv.parse(input);

Example:

cpp
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:

cpp
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:

txt
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:

txt
Table
  -> Row
     -> Field

A table contains rows.

Each row contains fields.

Example:

cpp
const auto table = rix.csv.parse(input);

for (const auto &row : table)
{
  for (const auto &field : row)
  {
    rix.debug.print(field);
  }
}

Count rows

Use:

cpp
table.size()

Example:

cpp
const auto table = rix.csv.parse(input);

rix.debug.print("row count:", table.size());

For this input:

csv
name,language
Ada,C++
Gaspard,Vix

the row count is:

txt
3

because the header is also a row.

Check for empty CSV

Use:

cpp
table.empty()

Example:

cpp
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:

cpp
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:

csv
name,language
Ada,C++

the header is:

txt
name
language

Read data rows

If the first row is a header, read data rows starting at index 1:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

cpp
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:

txt
She said "hello"

Use CSV parsing instead of splitting strings manually.

Avoid manual splitting

Avoid this:

cpp
std::getline(line, field, ',');

Manual splitting breaks when fields contain commas, quotes, or newlines.

Use:

cpp
const auto table = rix.csv.parse(input);

The parser handles CSV structure for you.

Complete validation example

cpp
#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:

bash
vix run parse.cpp

Use in a Vix project

Create an application:

bash
vix new csv-parse --app
cd csv-parse

Add Rix:

bash
vix add rix/rix
vix install

In vix.app, add:

txt
deps = [
  "rix/rix",
]

A small manifest can look like this:

txt
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:

cpp
#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:

bash
vix build
vix run

Single-file usage

For small scripts, examples, and experiments, you can use:

bash
vix run parse.cpp

If Rix is installed globally for single-file usage:

bash
vix install -g rix/rix
vix run parse.cpp

For project usage, prefer:

bash
vix add rix/rix
vix install

and keep the dependency in vix.app:

txt
deps = [
  "rix/rix",
]

Facade usage

The recommended documentation style is:

cpp
#include <rix.hpp>

Then:

cpp
const auto table = rix.csv.parse(input);

This keeps CSV available through the same public object as the other Rix packages:

cpp
rix.csv
rix.debug
rix.auth
rix.pdf

Independent package usage

If your project only needs CSV, you can install the independent package:

bash
vix add rix/csv
vix install

In vix.app:

txt
deps = [
  "rix/csv",
]

Then include:

cpp
#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:

cpp
#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:

cpp
for (std::size_t i = 1; i < table.size(); ++i)
{
  const auto &row = table[i];
}

Accessing missing fields

Wrong:

cpp
rix.debug.print(row[0], row[1]);

Better:

cpp
if (row.size() >= 2)
{
  rix.debug.print(row[0], row[1]);
}

Splitting CSV manually

Wrong:

cpp
std::getline(stream, field, ',');

Better:

cpp
const auto table = rix.csv.parse(input);

Forgetting deps

For a Vix project, do not put Rix packages in packages.

Use:

txt
deps = [
  "rix/rix",
]

packages is for CMake package discovery.

deps is for Vix Registry packages.

What you should remember

Parse CSV:

cpp
const auto table = rix.csv.parse(input);

Check for empty input:

cpp
if (table.empty())
{
  return 1;
}

Read rows:

cpp
for (const auto &row : table)
{
  for (const auto &field : row)
  {
    rix.debug.print(field);
  }
}

Read data rows after a header:

cpp
for (std::size_t i = 1; i < table.size(); ++i)
{
  const auto &row = table[i];
}

Validate row size before indexing:

cpp
if (row.size() >= 2)
{
  rix.debug.print(row[0], row[1]);
}

Next step

Learn how to write CSV output.

Next: Write CSV

Released under the MIT License.