From b576c50666fec47ee6c95c7b8d31144528635c37 Mon Sep 17 00:00:00 2001 From: Thomas Schmucker Date: Thu, 18 Jun 2020 15:16:06 +0200 Subject: erster Versuch, csv als library zu sehen... --- csv.c | 445 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 445 insertions(+) create mode 100644 csv.c (limited to 'csv.c') diff --git a/csv.c b/csv.c new file mode 100644 index 0000000..dfc79c9 --- /dev/null +++ b/csv.c @@ -0,0 +1,445 @@ +#include +#include +#include +#include "csv.h" + +#define INLINE static inline + +/* === CSV-MEMORY Interface === */ + +static void * +csv_mem_allocate(size_t n, size_t sz, void *cb_arg) +{ + (void) cb_arg; + + return calloc(n, sz); +} + +static void * +csv_mem_reallocate(void *ptr, size_t n, size_t sz, void *cb_arg) +{ + (void) cb_arg; + + return reallocarray(ptr, n, sz); +} + +static void +csv_mem_free(void *ptr, void *cb_arg) +{ + (void) cb_arg; + + free(ptr); +} + +/* === CSV-OPTIONS Interface === */ + +const csv_options_t csv_default_options = { + .quote_symbol = '"', + .sep_symbol = ',', + .cb_error = NULL, + .cb_allocate = csv_mem_allocate, + .cb_reallocate = csv_mem_reallocate, + .cb_free = csv_mem_free +}; + +/* === CSV-ERROR Interface === */ + +INLINE void +csv_fatal_error(const char *msg, const csv_options_t * const csv_options) +{ + if ( csv_options->cb_error != NULL ) { + (*csv_options->cb_error)(msg, csv_options->cb_arg); + } + + fprintf(stderr, "fatal error: %s\n", msg); + abort(); +} + +/* === CSV-STRING Interface === */ + +INLINE void +csv_string_init(csv_string_t *csv_string) +{ + assert(csv_string != NULL); + + csv_string->str = NULL; + csv_string->cap = 0; + csv_string->pos = 0; +} + +INLINE void +csv_string_reset(csv_string_t *csv_string) +{ + assert(csv_string != NULL); + + csv_string->pos = 0; +} + +INLINE int +csv_string_empty(csv_string_t *csv_string) +{ + assert(csv_string != NULL); + + return ( csv_string->pos == 0 ) ? 1 : 0; +} + +INLINE void +csv_string_append(csv_string_t *csv_string, char ch, const csv_options_t * const csv_options) +{ + assert(csv_string != NULL); + assert(csv_options != NULL); + + static const int INITIAL_CAP = 16; + + if ( csv_string->pos == csv_string->cap ) { /* grow if needed */ + if ( csv_string->str == NULL ) { /* first call? */ + if ( (csv_string->str = csv_options->cb_allocate(INITIAL_CAP, 1, csv_options->cb_arg)) == NULL ) { + csv_fatal_error("out of memory...", csv_options); + return; + } + + csv_string->cap = INITIAL_CAP; + } + else { /* subsequent call */ + int cap = (csv_string->cap * 3) / 2; /* *= 1.5 */ + char *str = csv_options->cb_reallocate(csv_string->str, cap, 1, csv_options->cb_arg); + if ( str == NULL ) { + csv_fatal_error("out of memory...", csv_options); + return; + } + csv_string->str = str; + csv_string->cap = cap; + } + } + + csv_string->str[csv_string->pos++] = ch; /* append char */ +} + +INLINE void +csv_string_free(csv_string_t *csv_string, const csv_options_t * const csv_options) +{ + assert(csv_string != NULL); + assert(csv_options != NULL); + + csv_options->cb_free(csv_string->str, csv_options->cb_arg); + + /* call *_init() for sane default values; prevent possible double-free */ + csv_string_init(csv_string); +} + +/* === CSV-FIELD Interface === */ + +INLINE void +csv_field_init(csv_field_t *csv_field) +{ + assert(csv_field != NULL); + + csv_field->fields = NULL; + csv_field->cap = 0; + csv_field->pos = 0; +} + +INLINE void +csv_field_reset(csv_field_t *csv_field) +{ + assert(csv_field != NULL); + + csv_field->pos = 0; +} + +INLINE void +csv_field_append(csv_field_t *csv_field, int idx, const csv_options_t * const csv_options) +{ + assert(csv_field != NULL); + assert(csv_options != NULL); + + static const size_t INITIAL_CAP = 16; + + if ( csv_field->pos == csv_field->cap ) { /* grow if needed */ + if ( csv_field->fields == NULL ) { + if ( (csv_field->fields = csv_options->cb_allocate(INITIAL_CAP, sizeof(csv_field->fields[0]), csv_options->cb_arg)) == NULL ) { + csv_fatal_error("out of memory", csv_options); + return; + } + + csv_field->cap = INITIAL_CAP; + } + else { + int cap = (csv_field->cap * 3) / 2; /* *= 1.5 */ + int *fields = csv_options->cb_reallocate(csv_field->fields, cap, sizeof(csv_field->fields[0]), csv_options->cb_arg); + if ( fields == NULL ) { + csv_fatal_error("out of memory", csv_options); + return; + } + csv_field->fields = fields; + csv_field->cap = cap; + } + } + + csv_field->fields[csv_field->pos++] = idx; /* append idx */ +} + +INLINE void +csv_field_free(csv_field_t *csv_field, const csv_options_t * const csv_options) +{ + assert(csv_field != NULL); + assert(csv_options != NULL); + + csv_options->cb_free(csv_field->fields, csv_options->cb_arg); + + csv_field_init(csv_field); +} + +/* === CSV Interface === */ + +void +csv_init(csv_t *csv) +{ + assert(csv != NULL); + + csv_init_opt(csv, NULL); +} + +void +csv_init_opt(csv_t *csv, const csv_options_t * const csv_options) +{ + assert(csv != NULL); + + if ( csv_options != NULL ) { + csv->csv_options = csv_options; + } + else { + csv->csv_options = &csv_default_options; + } + + csv_string_init(&csv->csv_string); + csv_field_init(&csv->csv_field); +} + +void +csv_free(csv_t *csv) +{ + assert(csv != NULL); + + csv_string_free(&csv->csv_string, csv->csv_options); + csv_field_free(&csv->csv_field, csv->csv_options); +} + +int +csv_nfields(csv_t *csv) +{ + assert(csv != NULL); + + if ( csv->csv_string.pos != 0 ) { + return csv->csv_field.pos; + } + return 0; +} + +const char * +csv_field(csv_t *csv, int idx) +{ + assert(csv != NULL); + assert(idx >= 0 && idx < csv->csv_field.pos); + + if ( idx < 0 || idx >= csv->csv_field.pos ) { + csv_fatal_error("range error", csv->csv_options); + } + + return &csv->csv_string.str[csv->csv_field.fields[idx]]; +} + +int +csv_read(csv_t *csv, FILE *in) +{ + assert(csv != NULL); + assert(in != NULL); + + enum { + STATE_START_FIELD, + STATE_QUOTED_FIELD, + STATE_SIMPLE_FIELD, + STATE_END_FIELD, + STATE_END_LINE, + STATE_END_FILE, + }; + + register const int QUOTE = csv->csv_options->quote_symbol; + register const int SEP = csv->csv_options->sep_symbol; + + csv_string_reset(&csv->csv_string); + csv_field_reset(&csv->csv_field); + + for ( int state = STATE_START_FIELD; ; ) { + int ch; + + switch ( state ) { + case STATE_START_FIELD: + csv_field_append(&csv->csv_field, csv->csv_string.pos, csv->csv_options); + + ch = getc(in); + if ( ch == EOF ) { + state = STATE_END_FILE; + } + else if ( ch == '\r' ) { /* Teste auf CR.. */ + ch = getc(in); + if ( ch != '\n' ) { /* ... LF */ + ungetc(ch, in); + } + state = STATE_END_LINE; + } + else if ( ch == '\n' ) { + state = STATE_END_LINE; + } + else if ( ch == SEP ) { + state = STATE_END_FIELD; + } + else if ( ch == QUOTE ) { + state = STATE_QUOTED_FIELD; + } + else { + csv_string_append(&csv->csv_string, ch, csv->csv_options); + state = STATE_SIMPLE_FIELD; + } + break; + + case STATE_QUOTED_FIELD: + do { + ch = getc(in); + if ( ch == EOF ) { + state = STATE_END_FILE; + } + else if ( ch == QUOTE ) { + ch = getc(in); + if ( ch == EOF ) { + state = STATE_END_FILE; + } + else if ( ch == QUOTE ) { + csv_string_append(&csv->csv_string, QUOTE, csv->csv_options); + } + else if ( ch == SEP ) { + state = STATE_END_FIELD; + } + else if ( ch == '\r' ) { + ch = getc(in); + if ( ch != '\n' ) { + ungetc(ch, in); + } + state = STATE_END_LINE; + } + else if ( ch == '\n' ) { + state = STATE_END_LINE; + } + else { + csv_string_append(&csv->csv_string, QUOTE, csv->csv_options); + ungetc(ch, in); /* zuviel gelesenes Zeichen zurückstellen */ + } + } + else { + csv_string_append(&csv->csv_string, ch, csv->csv_options); + } + } while ( state == STATE_QUOTED_FIELD ); + break; + + case STATE_SIMPLE_FIELD: + do { + ch = getc(in); + if ( ch == EOF ) { + state = STATE_END_FILE; + } + else if ( ch == SEP ) { + state = STATE_END_FIELD; + } + else if ( ch == '\r' ) { + ch = getc(in); + if ( ch != '\n' ) { + ungetc(ch, in); + } + state = STATE_END_LINE; + } + else if ( ch == '\n' ) { + state = STATE_END_LINE; + } + else { + csv_string_append(&csv->csv_string, ch, csv->csv_options); + } + } while ( state == STATE_SIMPLE_FIELD ); + break; + + case STATE_END_FIELD: + csv_string_append(&csv->csv_string, '\0', csv->csv_options); + state = STATE_START_FIELD; + break; + + case STATE_END_LINE: + csv_string_append(&csv->csv_string, '\0', csv->csv_options); + return csv->csv_field.pos; + + case STATE_END_FILE: + if ( csv_string_empty(&csv->csv_string) ) { + return -1; /* EOF reached */ + } + + /* EOF wurde gelesen, allerdings sind Daten zur Vearbeitung vorhanden! */ + /* => aktuelles Feld abschließen... */ + csv_string_append(&csv->csv_string, '\0', csv->csv_options); + + /* ... und die Anzahl der Felder zurückliefern! */ + return csv->csv_field.pos; + + default: + assert(!"this should never be happen..."); + break; + } + } + /* NOT REACHED */ +} + +static void +csv_write_field(FILE *out, const char *field, const csv_options_t * const csv_options) +{ + assert(csv_options != NULL); + assert(field != NULL); + assert(out != NULL); + + const char QUOTE = csv_options->quote_symbol; + + if ( *field != '\0' ) { + putc(QUOTE, out); + + for ( ; *field != '\0'; ++field ) { + if ( *field == QUOTE ) { + putc(QUOTE, out); + putc(QUOTE, out); + } + else { + putc(*field, out); + } + } + + putc(QUOTE, out); + } +} + +void +csv_write(FILE *out, int n, const char *fields[], const csv_options_t * const csv_options) +{ + assert(csv_options != NULL); + assert(fields != NULL); + assert(out != NULL); + + const char SEP = csv_options->quote_symbol; + + /* process first field */ + if ( 0 != n && fields[0] != NULL ) { + csv_write_field(out, fields[0], csv_options); + + /* process next fields */ + for ( int i = 1; i != n && fields[i] != NULL; ++i ) { + putc(SEP, out); + csv_write_field(out, fields[i], csv_options); + } + } + fprintf(out, "\r\n"); +} + -- cgit v1.3