#include #include #include #include #include #include #include #include #define INLINE static inline /* === CSV-OPTIONS Interface === */ typedef struct { char quote_symbol; char sep_symbol; void *cb_arg; void (*cb_func)(const char *, void *); } csv_options_t; extern const csv_options_t csv_default_options; const csv_options_t csv_default_options = { .quote_symbol = '"', .sep_symbol = ',', .cb_func = NULL }; /* === CSV-ERROR Interface === */ INLINE void csv_fatal_error(const char *msg, const csv_options_t * const csv_options) { if ( csv_options->cb_func != NULL ) { (*csv_options->cb_func)(msg, csv_options->cb_arg); } fprintf(stderr, "fatal error: %s\n", msg); abort(); } /* === CSV-STRING Interface === */ typedef struct { char *str; int cap, pos; } csv_string_t; 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 bool csv_string_empty(csv_string_t *csv_string) { assert(csv_string != NULL); return ( csv_string->pos == 0 ) ? true : false; } 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 = malloc(INITIAL_CAP)) == 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 = realloc(csv_string->str, cap); 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) { assert(csv_string != NULL); free(csv_string->str); /* call *_init() for sane default values; prevent possible double-free */ csv_string_init(csv_string); } /* === CSV-FIELD Interface === */ typedef struct { int *fields; int cap, pos; } csv_field_t; 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 = calloc(INITIAL_CAP, sizeof(csv_field->fields[0]))) == 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 = reallocarray(csv_field->fields, cap, sizeof(csv_field->fields[0])); 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) { assert(csv_field != NULL); free(csv_field->fields); csv_field_init(csv_field); } /* === CSV Interface === */ typedef struct { const csv_options_t *csv_options; csv_string_t csv_string; csv_field_t csv_field; } csv_t; void csv_init(csv_t *csv); void csv_init_opt(csv_t *csv, const csv_options_t * const csv_options); void csv_free(csv_t *csv); int csv_nfields(csv_t *csv); const char * csv_field(csv_t *csv, int idx); int csv_read(csv_t *csv, FILE *in); void csv_write(const csv_options_t * const csv_options, int n, const char *fields[], FILE *out); 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_field_free(&csv->csv_field); } 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(const csv_options_t * const csv_options, const char *field, FILE *out) { 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(const csv_options_t * const csv_options, int n, const char *fields[], FILE *out) { 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(csv_options, fields[0], out); /* process next fields */ for ( int i = 1; i != n && fields[i] != NULL; ++i ) { putc(SEP, out); csv_write_field(csv_options, fields[i], out); } } fprintf(out, "\r\n"); } jmp_buf env; static void sig_handler(int sig) { (void) sig; longjmp(env, 1); } int main(void) { csv_t csv[1]; int n, line = 0; signal(SIGABRT, sig_handler); FILE *in = fopen("500000 Records.csv", "r"); if ( in == NULL ) { fprintf(stderr, "failed to open testfile...\n"); return EXIT_FAILURE; } clock_t start = clock(); csv_init(csv); if ( setjmp(env) == 0 ) { while ( (n = csv_read(csv, in)) != -1 ) { //if ( line == 500000 ) csv_field(csv, -1); ++line; } } else { printf("Fehler beim Verarbeiten der Datei...\n"); } csv_free(csv); clock_t end = clock(); fclose(in); printf("%d lines processed, duration: %.3lf sec\n", line, ((double)(end - start)) / CLOCKS_PER_SEC); return EXIT_SUCCESS; }