Skip to content

Latest commit

 

History

History
998 lines (687 loc) · 13.3 KB

File metadata and controls

998 lines (687 loc) · 13.3 KB

CSV API

This page documents the public rix/csv API.

Use CSV through the unified Rix facade:

#include <rix.hpp>

Then access CSV with:

rix.csv

The CSV API provides helpers for:

parsing CSV text
writing CSV text
working with rows and fields
using parse options
using write options
transforming fields
filtering rows

Package

The CSV package is:

rix/csv

For facade usage, install:

vix add rix/rix
vix install

In vix.app:

deps = [
  "rix/rix",
]

For independent package usage, install:

vix add rix/csv
vix install

In vix.app:

deps = [
  "rix/csv",
]

Header

Facade usage:

#include <rix.hpp>

Independent usage:

#include <rix/csv.hpp>

Most application examples should use the facade.

Facade member

The CSV facade member is:

rix.csv

Example:

#include <rix.hpp>

int main()
{
  const auto table = rix.csv.parse(
      "name,language\n"
      "Ada,C++\n");

  return table.empty() ? 1 : 0;
}

Main operations

Common CSV operations are:

rix.csv.parse(...)
rix.csv.write(...)

Use parse to convert CSV text into a table.

Use write to convert a table back into CSV text.

Table type

CSV data is represented as a table.

A table is a list of rows.

Each row is a list of fields.

Common shape:

Table
  Row
    Field
    Field
  Row
    Field
    Field

Example:

const auto table = rix.csv.parse(
    "name,language\n"
    "Ada,C++\n");

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

Parse CSV text

Use:

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

Example:

#include <rix.hpp>

int main()
{
  const auto input =
      "name,language\n"
      "Ada,C++\n"
      "Gaspard,Vix\n";

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

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

  return 0;
}

Access rows

Iterate over the table:

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

Access a row by index:

const auto &header = table[0];

Check that the table is not empty before indexing.

Access fields

Fields are stored as strings.

Example:

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

Access a field by index:

const auto &name = table[1][0];
const auto &language = table[1][1];

Check row sizes before indexing.

Safe indexing

Wrong:

rix.debug.print(table[1][0]);

Better:

if (table.size() > 1 && table[1].size() > 0)
{
  rix.debug.print(table[1][0]);
}

CSV input can be incomplete, so code should check sizes when reading by index.

Print a table

#include <rix.hpp>

int main()
{
  const auto table = rix.csv.parse(
      "name,language\n"
      "Ada,C++\n"
      "Gaspard,Vix\n");

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

  return 0;
}

For real Vix application logs, prefer the Vix logging system.

For examples and small tools, rix.debug.print is fine.

Write CSV text

Use:

const auto output = rix.csv.write(table);

Example:

#include <rix.hpp>

int main()
{
  const auto table = rix.csv.parse(
      "name,language\n"
      "Ada,C++\n");

  const auto output = rix.csv.write(table);

  rix.debug.print(output);

  return 0;
}

Use write when you need to export table data as CSV text.

Round trip example

#include <rix.hpp>

int main()
{
  const auto input =
      "name,language\n"
      "Ada,C++\n"
      "Gaspard,Vix\n";

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

  const auto output = rix.csv.write(table);

  rix.debug.print(output);

  return 0;
}

This parses CSV text and writes it back as CSV text.

Parse with options

CSV parsing can use options when you need custom behavior.

Example shape:

rixlib::csv::ParseOptions options;

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

Use options for custom parsing behavior such as trimming, delimiter changes, field transforms, or row filtering when supported by the package version.

Field transformer

A field transformer can normalize fields while parsing.

Example:

rixlib::csv::ParseOptions 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);

Use a field transformer when parsed values need light normalization.

Row filter

A row filter can skip rows while parsing.

Example:

rixlib::csv::ParseOptions options;

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

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

Use a row filter when you want to remove empty rows or application-specific rows.

Complete parse options example

#include <rix.hpp>

#include <string>

int main()
{
  const std::string input =
      "name,language\n"
      "Ada,C++\n"
      "Gaspard,Vix\n"
      "\n";

  rixlib::csv::ParseOptions 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);

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

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

  return 0;
}

Run:

vix run csv.cpp

Write with options

CSV writing can use options when you need custom output behavior.

Example shape:

rixlib::csv::WriteOptions options;

const auto output = rix.csv.write(table, options);

Use write options when you need to control output format such as delimiter or newline behavior when supported by the package version.

Table construction

You can build a table manually.

Example shape:

rixlib::csv::Table table;

table.push_back({"name", "language"});
table.push_back({"Ada", "C++"});
table.push_back({"Gaspard", "Vix"});

Then write it:

const auto output = rix.csv.write(table);

Complete write example

#include <rix.hpp>

int main()
{
  rixlib::csv::Table table;

  table.push_back({"name", "language"});
  table.push_back({"Ada", "C++"});
  table.push_back({"Gaspard", "Vix.cpp"});

  const auto output = rix.csv.write(table);

  rix.debug.print(output);

  return 0;
}

Run:

vix run csv.cpp

CSV and PDF

CSV tables can be converted into PDF tables.

Example:

#include <rix.hpp>

int main()
{
  const auto csv = rix.csv.parse(
      "Name,Language,Project\n"
      "Ada,C++,Rix\n"
      "Gaspard,C++,Vix.cpp\n");

  auto doc = rix.pdf.document();

  auto &page = doc.add_page();

  auto y = page.heading(
      page.x_left(),
      page.y_top(),
      "CSV table",
      1);

  y -= 20.0F;

  rixlib::pdf::Table table;

  table.set_column_widths({
      160.0F,
      160.0F,
      160.0F});

  if (!csv.empty() && csv[0].size() >= 3)
  {
    table.add_header({
        csv[0][0],
        csv[0][1],
        csv[0][2]});
  }

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

    if (row.size() >= 3)
    {
      table.add_row({
          row[0],
          row[1],
          row[2]});
    }
  }

  page.table(
      page.x_left(),
      y,
      table);

  auto saved = rix.pdf.save(doc, "csv-table.pdf");

  return saved.ok() ? 0 : 1;
}

This example uses:

rix.csv
rix.pdf

so the unified facade is the right dependency.

CSV and debug

CSV examples often use debug output:

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

rix.debug.print("rows:", table.size());
rix.debug.inspect(table);

rix.debug is useful for examples, quick checks, and small tools.

For real Vix application logs, prefer the Vix logging system.

Use in a Vix project

Create a Vix app:

vix new rix-csv-api --app
cd rix-csv-api

Add Rix:

vix add rix/rix
vix install

Make sure vix.app contains:

deps = [
  "rix/rix",
]

Use in src/main.cpp:

#include <rix.hpp>

Build and run:

vix build
vix run

Single-file usage

Create a file:

mkdir -p ~/rix-csv-api
cd ~/rix-csv-api
touch csv.cpp

Add:

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

Run:

vix run csv.cpp

If Rix is not available globally:

vix install -g rix/rix
vix run csv.cpp

Independent usage

Install only CSV:

vix add rix/csv
vix install

In vix.app:

deps = [
  "rix/csv",
]

Then include:

#include <rix/csv.hpp>

Example:

#include <rix/csv.hpp>

int main()
{
  auto csv = rixlib::csv::module();

  const auto table = csv.parse(
      "name,language\n"
      "Ada,C++\n");

  return table.empty() ? 1 : 0;
}

Use independent package APIs when you intentionally do not want the unified facade.

For most documentation and application examples, prefer:

#include <rix.hpp>

and:

rix.csv

Enable only CSV in the facade

Use feature macros when 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\n"
      "Ada\n");

  return table.empty() ? 1 : 0;
}

If you also want debug output:

#define RIX_ENABLE_CSV
#define RIX_ENABLE_DEBUG
#include <rix.hpp>

int main()
{
  const auto table = rix.csv.parse(
      "name\n"
      "Ada\n");

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

  return 0;
}

Complete CSV example

#include <rix.hpp>

#include <sstream>
#include <string>

namespace
{
  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("loaded rows:", table.size());
  print_table(table);

  return 0;
}

Run:

vix run csv.cpp

Common mistakes

Forgetting to install Rix

If your code uses:

#include <rix.hpp>

install:

vix add rix/rix
vix install

If your code uses:

#include <rix/csv.hpp>

install:

vix add rix/csv
vix install

Putting Rix in packages

Wrong:

packages = [
  "rix/csv",
]

Correct:

deps = [
  "rix/csv",
]

deps is for Vix Registry packages.

packages is for CMake package discovery.

Installing rix/csv but including <rix.hpp>

If your code uses:

#include <rix.hpp>

then install:

vix add rix/rix
vix install

If your code uses:

#include <rix/csv.hpp>

then install:

vix add rix/csv
vix install

Accessing fields without checking row size

Wrong:

rix.debug.print(row[2]);

Better:

if (row.size() > 2)
{
  rix.debug.print(row[2]);
}

Assuming all rows have the same number of fields

CSV files can have incomplete rows.

Check row sizes when reading fields by index.

Using debug output as production logging

rix.debug is useful for examples and small tools.

For real Vix application logs, prefer the Vix logging system.

Mixing facade and independent usage

Avoid this without a clear reason:

#include <rix.hpp>
#include <rix/csv.hpp>

Use one style per file.

Facade:

#include <rix.hpp>

Independent:

#include <rix/csv.hpp>

What you should remember

Parse CSV:

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

Write CSV:

const auto output = rix.csv.write(table);

Iterate rows:

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

Use the facade for most examples:

#include <rix.hpp>

and:

rix.csv

For project usage:

vix add rix/rix
vix install

and keep:

deps = [
  "rix/rix",
]

For independent CSV usage:

vix add rix/csv
vix install

and:

#include <rix/csv.hpp>

Always check row sizes before indexed field access.

Next step

Continue with the debug API.

Next: Debug API