CSV Media file

The Fossil Media CSV library is a lightweight, dependency-free C library for parsing, manipulating, and writing CSV (Comma-Separated Values) data. It supports configurable delimiters and quoting rules, making it compatible with standard RFC4180 CSV files as well as custom formats. The C API exposes efficient functions for working with CSV documents at a structural level, while the C++ RAII wrapper provides a safer, object-oriented interface with automatic memory management. This makes the library suitable for everything from embedded systems to modern C++ applications that require robust, portable CSV handling.

HEADER REFERENCE #

#ifndef FOSSIL_MEDIA_CSV_H
#define FOSSIL_MEDIA_CSV_H

#include <stddef.h>
#include <stdint.h>

#ifdef __cplusplus
extern "C"
{
#endif

/**
 * @file fossil_media_csv.h
 * @brief Fossil Media CSV parsing and writing library (pure C, no dependencies).
 *
 * Supports basic CSV parsing with configurable delimiter and quoting rules.
 * Handles RFC4180-compatible CSV by default but allows customization.
 */

/* Error codes for CSV parsing/writing */
typedef enum fossil_media_csv_error_t {
    FOSSIL_MEDIA_CSV_OK = 0,          /**< No error */
    FOSSIL_MEDIA_CSV_ERR_MEMORY,      /**< Memory allocation failed */
    FOSSIL_MEDIA_CSV_ERR_SYNTAX,      /**< Syntax error in CSV input */
    FOSSIL_MEDIA_CSV_ERR_IO,          /**< I/O error */
    FOSSIL_MEDIA_CSV_ERR_INVALID_ARG  /**< Invalid argument */
} fossil_media_csv_error_t;

/* CSV row structure: array of strings (fields) */
typedef struct fossil_media_csv_row_t {
    char **fields;       /**< Array of NUL-terminated strings */
    size_t field_count;  /**< Number of fields */
} fossil_media_csv_row_t;

/* CSV document structure: array of rows */
typedef struct fossil_media_csv_doc_t {
    fossil_media_csv_row_t *rows; /**< Array of CSV rows */
    size_t row_count;             /**< Number of rows */
} fossil_media_csv_doc_t;

/**
 * @brief Parse a CSV-formatted string into a document.
 *
 * @param csv_text   NUL-terminated CSV text.
 * @param delimiter  Field delimiter (usually ',' or ';').
 * @param err_out    Optional pointer to error code.
 * @return Pointer to a parsed CSV document (caller must free with fossil_media_csv_free()).
 */
fossil_media_csv_doc_t *
fossil_media_csv_parse(const char *csv_text, char delimiter, fossil_media_csv_error_t *err_out);

/**
 * @brief Free a CSV document and all associated memory.
 *
 * @param doc  Pointer to document (can be NULL).
 */
void fossil_media_csv_free(fossil_media_csv_doc_t *doc);

/**
 * @brief Convert a CSV document back to a string.
 *
 * @param doc        CSV document to stringify.
 * @param delimiter  Field delimiter to use.
 * @param err_out    Optional pointer to error code.
 * @return Heap-allocated CSV string (caller frees with free()).
 */
char *
fossil_media_csv_stringify(const fossil_media_csv_doc_t *doc, char delimiter, fossil_media_csv_error_t *err_out);

/**
 * @brief Append a row to the CSV document.
 *
 * @param doc        CSV document.
 * @param fields     Array of field strings.
 * @param field_cnt  Number of fields.
 * @return 0 on success, non-zero on error.
 */
int fossil_media_csv_append_row(fossil_media_csv_doc_t *doc, const char **fields, size_t field_cnt);

#ifdef __cplusplus
}
#include <string>
#include <stdexcept>
#include <vector>
#include <utility>

namespace fossil {

    namespace media {

        /**
         * @class Csv
         * @brief C++ RAII wrapper for fossil_media_csv_doc_t.
         *
         * Provides convenient C++ interface for parsing, manipulating,
         * and serializing CSV data using the underlying C library.
         */
        class Csv {
        public:
            /**
             * @brief Construct from CSV string.
             * @param csv_text   CSV input as std::string.
             * @param delimiter  Field delimiter (default: ',').
             * @throws std::runtime_error on parse error.
             */
            Csv(const std::string& csv_text, char delimiter = ',') {
                fossil_media_csv_error_t err = FOSSIL_MEDIA_CSV_OK;
                doc_ = fossil_media_csv_parse(csv_text.c_str(), delimiter, &err);
                if (!doc_ || err != FOSSIL_MEDIA_CSV_OK) {
                    throw std::runtime_error("CSV parse error");
                }
                delimiter_ = delimiter;
            }

            /**
             * @brief Destructor. Frees all resources.
             */
            ~Csv() {
                if (doc_) {
                    fossil_media_csv_free(doc_);
                }
            }

            // Non-copyable
            Csv(const Csv&) = delete;
            Csv& operator=(const Csv&) = delete;

            /**
             * @brief Move constructor.
             */
            Csv(Csv&& other) noexcept : doc_(other.doc_), delimiter_(other.delimiter_) {
                other.doc_ = nullptr;
            }

            /**
             * @brief Move assignment.
             */
            Csv& operator=(Csv&& other) noexcept {
                if (this != &other) {
                    if (doc_) fossil_media_csv_free(doc_);
                    doc_ = other.doc_;
                    delimiter_ = other.delimiter_;
                    other.doc_ = nullptr;
                }
                return *this;
            }

            /**
             * @brief Get number of rows in the CSV document.
             * @return Row count.
             */
            size_t row_count() const {
                return doc_ ? doc_->row_count : 0;
            }

            /**
             * @brief Get number of fields in a given row.
             * @param row Row index.
             * @return Number of fields, or 0 if out of bounds.
             */
            size_t field_count(size_t row) const {
                if (!doc_ || row >= doc_->row_count) return 0;
                return doc_->rows[row].field_count;
            }

            /**
             * @brief Get field value as string.
             * @param row Row index.
             * @param col Column index.
             * @return Field value, or empty string if out of bounds.
             */
            std::string field(size_t row, size_t col) const {
                if (!doc_ || row >= doc_->row_count) return {};
                const fossil_media_csv_row_t& r = doc_->rows[row];
                if (col >= r.field_count) return {};
                return r.fields[col] ? r.fields[col] : "";
            }

            /**
             * @brief Append a row to the CSV document.
             * @param fields Vector of field strings.
             * @throws std::runtime_error on error.
             */
            void append_row(const std::vector<std::string>& fields) {
                std::vector<const char*> cfields;
                for (const auto& f : fields) cfields.push_back(f.c_str());
                if (fossil_media_csv_append_row(doc_, cfields.data(), cfields.size()) != 0) {
                    throw std::runtime_error("CSV append_row error");
                }
            }

            /**
             * @brief Convert the CSV document back to a CSV-formatted string.
             * @return CSV string.
             * @throws std::runtime_error on error.
             */
            std::string to_string() const {
                fossil_media_csv_error_t err = FOSSIL_MEDIA_CSV_OK;
                char* cstr = fossil_media_csv_stringify(doc_, delimiter_, &err);
                if (!cstr || err != FOSSIL_MEDIA_CSV_OK) {
                    throw std::runtime_error("CSV stringify error");
                }
                std::string result(cstr);
                free(cstr);
                return result;
            }

        private:
            fossil_media_csv_doc_t* doc_ = nullptr; /**< Underlying CSV document pointer */
            char delimiter_ = ',';                  /**< Field delimiter */
        };

    } // namespace media

} // namespace fossil

#endif

#endif /* FOSSIL_MEDIA_CSV_H */

SAMPLE CODE C #

#include "fossil/media/csv.h"
#include <stdio.h>
#include <stdlib.h>

int main(void) {
    const char *csv_text = "name,age,city\nAlice,30,London\nBob,25,Paris";
    fossil_media_csv_error_t err;

    // Parse CSV
    fossil_media_csv_doc_t *doc = fossil_media_csv_parse(csv_text, ',', &err);
    if (!doc || err != FOSSIL_MEDIA_CSV_OK) {
        fprintf(stderr, "CSV parse failed (error %d)\n", err);
        return 1;
    }

    // Print all rows/fields
    for (size_t i = 0; i < doc->row_count; i++) {
        for (size_t j = 0; j < doc->rows[i].field_count; j++) {
            printf("[%s]", doc->rows[i].fields[j]);
        }
        printf("\n");
    }

    // Append a new row
    const char *new_fields[] = {"Charlie", "28", "Berlin"};
    if (fossil_media_csv_append_row(doc, new_fields, 3) != 0) {
        fprintf(stderr, "Failed to append row\n");
    }

    // Convert back to string
    char *csv_out = fossil_media_csv_stringify(doc, ',', &err);
    if (csv_out && err == FOSSIL_MEDIA_CSV_OK) {
        printf("\nCSV Output:\n%s\n", csv_out);
        free(csv_out);
    }

    // Cleanup
    fossil_media_csv_free(doc);
    return 0;
}

SAMPLE CODE C++ #

#include "fossil/media/csv.h"
#include <iostream>
#include <vector>

int main() {
    try {
        std::string csv_text = "name,age,city\nAlice,30,London\nBob,25,Paris";
        fossil::media::Csv csv(csv_text); // RAII wrapper

        // Print fields
        for (size_t i = 0; i < csv.row_count(); i++) {
            for (size_t j = 0; j < csv.field_count(i); j++) {
                std::cout << "[" << csv.field(i, j) << "]";
            }
            std::cout << "\n";
        }

        // Append a row
        csv.append_row({"Charlie", "28", "Berlin"});

        // Output as string
        std::cout << "\nCSV Output:\n" << csv.to_string() << "\n";

    } catch (const std::runtime_error &e) {
        std::cerr << "CSV error: " << e.what() << "\n";
        return 1;
    }

    return 0;
}

What are your feelings

Updated on August 21, 2025