rgamble / libcsv

Fast and flexible CSV library written in pure ANSI C that can read and write CSV data.
GNU Lesser General Public License v2.1
181 stars 40 forks source link

Propose example code to parse and show CSV file content #23

Open chrisinmtown opened 3 years ago

chrisinmtown commented 3 years ago

I think it would really help if the examples/ area has a program that demonstrates how to get and process CSV data. I offer the following, maybe call it csvdump.c. I believe it frees all resources but please double check, my C skills are a little rusty.

// Parse CSV file using libcsv and emit rows on stdout.
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "csv.h"

// Storage for one row of data and parse state
typedef struct {
    size_t row;
    char **fields;
    size_t alloc;
    size_t cur_field;
    int error;
} csv_reader;

// Parser calls this function when it detects end of a field
void field_cb(void *field, size_t field_len, void *data) {
    csv_reader *cr = (csv_reader*)data;
    if (cr->error)
        return;

    // check for space to store this field
    if (cr->cur_field == cr->alloc) {
        cr->alloc *= 2;
        cr->fields = realloc(cr->fields, sizeof(char *) * cr->alloc);
        if (cr->fields == NULL) {
            fprintf(stderr, "field_cb: failed to reallocate %zu bytes",
                    sizeof(char *) * cr->alloc);
            perror(NULL);
            cr->error = 1;
            return;
        }
    }

    cr->fields[cr->cur_field] = strndup((char*)field, field_len);
    cr->cur_field += 1;
}

// Parser calls this function when it detects end of a row
void row_cb(int delim __attribute__((unused)), void *data) {
    csv_reader *cr = (csv_reader*)data;
    if (cr->error)
        return;
    printf("Row %zu:\n", cr->row);
    for (size_t i = 0; i < cr->cur_field; ++i) {
        printf("\tField %zu: %s\n", i, cr->fields[i]);
        free(cr->fields[i]);
    }
    cr->cur_field = 0;
    cr->row += 1;
}

// Parse data from file at the specified path.
// Return 0 on success, non-zero on failure.
int parse_csv_file(const char *path) {
    int retval = 1;
    struct csv_parser cp;
    int rc = csv_init(&cp, CSV_STRICT);
    if (rc != 0) {
        fputs("failed to initialize CSV parser\n", stderr);
        goto FINAL;
    }
    // Preallocate space for some fields
    csv_reader cr;
    memset((void*)&cr, 0, sizeof(csv_reader));
    cr.alloc = 16;
    cr.fields = malloc(sizeof(char *) * cr.alloc);
    if (cr.fields == NULL) {
        fprintf(stderr, "failed to allocate %zu bytes\n",
                sizeof(char *) * cr.alloc);
        goto FINAL;
    }
    FILE *fp = fopen(path, "rb");
    if (!fp) {
        fprintf(stderr, "failed to open file %s\n", path);
        retval = 1;
        goto FAIL_FP;
    } 
    char buf[4096];
    size_t bytes_read;
    while ((bytes_read = fread(buf, 1, sizeof(buf), fp)) > 0) {
        if (csv_parse(&cp, buf, bytes_read, field_cb, row_cb, &cr) != bytes_read) {
            fprintf(stderr, "Error while parsing file: %s\n",
                    csv_strerror(csv_error(&cp)) );
            goto FAIL_CR;
        }
    }
    rc = csv_fini(&cp, field_cb, row_cb, &cr); 
    if (cr.error || rc != 0) {
        fputs("Parse failed\n", stderr);
        goto FAIL_CR;
    }

    SUCCESS:
        retval = 0;
    FAIL_CR:
        fclose(fp);
    FAIL_FP:
        free(cr.fields);
    FINAL:
        return retval;
}

// Invoke the parser with the first argument as file path
int main(int argc, const char **argv) {
    if (argc != 2) {
        fprintf(stderr, "Usage: %s file.csv\n", argv[0]);
        return 1;
    }
    return parse_csv_file(argv[1]);
}
albert-mathews commented 2 years ago

for anyone wanting to read numerical data from csv, try this:

void field_cb(void *field, size_t field_len, void *data) {
  // whatever you need from the above example
   char* val = strndup((char*)field, field_len); // this gets only the number of byte just read into field
   double v = atof(val); // this converts those bytes into a double
  // whatever else you need
}