#include #include #include #include #include "csv.h" /* === 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, size_t sz, void *cb_arg) { (void) cb_arg; explicit_bzero(ptr, sz); 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 === */ static void csv_fatal_error(csv_err_t csv_err, const csv_options_t * const csv_options) { if ( csv_options->cb_error != NULL ) { (*csv_options->cb_error)(csv_err, csv_options->cb_error_arg); } fprintf(stderr, "fatal error: %s\n", csv_err_str(csv_err)); abort(); } /* === CSV-STRING Interface === */ static 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; } static void csv_string_reset(csv_string_t *csv_string) { assert(csv_string != NULL); csv_string->pos = 0; } static int csv_string_empty(csv_string_t *csv_string) { assert(csv_string != NULL); return ( csv_string->pos == 0 ) ? 1 : 0; } static 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_memory_arg)) == NULL ) { csv_fatal_error(CSV_ERR_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_memory_arg); if ( str == NULL ) { csv_fatal_error(CSV_ERR_OUT_OF_MEMORY, csv_options); return; } csv_string->str = str; csv_string->cap = cap; } } csv_string->str[csv_string->pos++] = ch; /* append char */ } static 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_string->cap, csv_options->cb_memory_arg); /* call *_init() for sane default values; prevent possible double-free */ csv_string_init(csv_string); } /* === CSV-FIELD Interface === */ static 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; } static void csv_field_reset(csv_field_t *csv_field) { assert(csv_field != NULL); csv_field->pos = 0; } static 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_memory_arg)) == NULL ) { csv_fatal_error(CSV_ERR_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_memory_arg); if ( fields == NULL ) { csv_fatal_error(CSV_ERR_OUT_OF_MEMORY, csv_options); return; } csv_field->fields = fields; csv_field->cap = cap; } } csv_field->fields[csv_field->pos++] = idx; /* append idx */ } static 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_field->cap, csv_options->cb_memory_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_cleanup(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(CSV_ERR_OUT_OF_RANGE, 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); /* initialze if needed... */ if ( csv->csv_options == NULL ) { csv_init(csv); } /* do not try to read if EOF has already been seen */ if ( feof(in) ) { csv_cleanup(csv); return -1; } if ( ferror(in) ) { csv_fatal_error(CSV_ERR_IO_READ, csv->csv_options); } 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 ( ferror(in) ) { csv_fatal_error(CSV_ERR_IO_READ, csv->csv_options); } if ( csv_string_empty(&csv->csv_string) ) { csv_cleanup(csv); 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 */ } const char * csv_err_str(csv_err_t csv_err) { switch ( csv_err ) { case CSV_ERR_OK: return "no error"; case CSV_ERR_OUT_OF_MEMORY: return "out of memory"; case CSV_ERR_OUT_OF_RANGE: return "index out of range"; case CSV_ERR_IO_READ: return "read errro"; case CSV_ERR_IO_WRITE: return "write error"; default: return "unknown error"; } /* 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 * csv_options) { assert(out != NULL); assert(fields != NULL); if ( csv_options == NULL ) { csv_options = &csv_default_options; } const char SEP = csv_options->sep_symbol; /* process first field */ if ( 0 != n && *fields != NULL ) { --n; csv_write_field(out, *fields++, csv_options); /* process next fields */ for ( ; n && *fields != NULL; --n, ++fields ) { putc(SEP, out); csv_write_field(out, *fields, csv_options); } } fprintf(out, "\r\n"); }