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

CSV Options

This page explains how to use options with rix/csv.

The examples use the public Rix facade:

cpp
#include <rix.hpp>

and access CSV through:

cpp
rix.csv

CSV options let you customize parsing and writing behavior without changing the rest of your application code.

Basic idea

For simple CSV input, use:

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

When you need custom behavior, create options and pass them to the parser or writer.

Example shape:

cpp
rixlib::csv::Options options;

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

Options are useful when your application needs to:

txt
trim fields
transform fields
skip rows
filter rows
customize CSV handling
keep parsing logic reusable

Complete example

Create a file:

bash
mkdir -p ~/rix-csv-options
cd ~/rix-csv-options
touch options.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"
      "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;
}

Run it:

bash
vix run options.cpp

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

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

Expected output

The output should look like this:

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

The row starting with Skip is removed.

The field Vix is transformed into Vix.cpp.

Options object

Create an options object:

cpp
rixlib::csv::Options options;

Then pass it to parsing:

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

cpp
options.field_transformer = [](std::string value) {
  if (value == "Vix")
  {
    return std::string{"Vix.cpp"};
  }

  return value;
};

Use this for small transformations such as:

txt
normalizing names
rewriting values
cleaning imported data
mapping old values to new values

Trim-like transformer

If your CSV input contains extra spaces, you can use a transformer to normalize fields.

Example shape:

cpp
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;
};

Then parse:

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

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  if (!row.empty() && row[0] == "Skip")
  {
    return false;
  }

  return true;
};

Return:

txt
true  -> keep the row
false -> remove the row

Use row filters for:

txt
skipping empty rows
skipping comments
removing invalid rows
filtering imported data
keeping only rows your app needs

Skip empty rows

Example:

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  for (const auto &field : row)
  {
    if (!field.empty())
    {
      return true;
    }
  }

  return false;
};

This removes rows where every field is empty.

Skip comment rows

If your CSV input uses comment-style rows:

csv
# generated data
name,language
Ada,C++
Gaspard,Vix

you can filter rows whose first field starts with #:

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  if (!row.empty() && !row[0].empty() && row[0][0] == '#')
  {
    return false;
  }

  return true;
};

Then parse normally:

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

Combine transformer and filter

You can use both hooks together.

cpp
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);

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:

cpp
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;
}

Check row size before indexing:

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("name:", row[0]);
  rix.debug.print("language:", row[1]);
}

Options and headers

CSV headers are still normal rows.

If the first row is a header, your row filter should normally keep it.

Example:

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  if (row.empty())
  {
    return false;
  }

  return true;
};

Then process the header separately:

cpp
const auto &header = table[0];

and data rows from index 1:

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

Options in a Vix project

Create an application:

bash
vix new csv-options --app
cd csv-options

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-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",
]

Then use options in src/main.cpp:

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;
}

Build and run:

bash
vix build
vix run

Single-file usage

For small scripts, examples, and experiments:

bash
vix run options.cpp

If Rix is installed globally for single-file usage:

bash
vix install -g rix/rix
vix run options.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, options);

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()
{
  rixlib::csv::Options options;

  const auto table = rix.csv.parse("name,lang\nAda,C++\n", options);

  return 0;
}

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:

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  return row[0] != "name";
};

This removes the header row.

Accessing empty rows inside a filter

Wrong:

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  return row[0] != "Skip";
};

Better:

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  return row.empty() || row[0] != "Skip";
};

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:

txt
deps = [
  "rix/rix",
]

packages is for CMake package discovery.

deps is for Vix Registry packages.

What you should remember

Create options:

cpp
rixlib::csv::Options options;

Transform fields:

cpp
options.field_transformer = [](std::string value) {
  return value;
};

Filter rows:

cpp
options.row_filter = [](const rixlib::csv::Row &row) {
  return true;
};

Parse with options:

cpp
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

Released under the MIT License.