diff options
Diffstat (limited to 'csv-test.c')
| -rw-r--r-- | csv-test.c | 491 |
1 files changed, 54 insertions, 437 deletions
| @@ -1,470 +1,95 @@ | |||
| 1 | #include <stdio.h> | 1 | #include <stdio.h> |
| 2 | #include <stdlib.h> | 2 | #include <stdlib.h> |
| 3 | #include <string.h> | ||
| 4 | #include <time.h> | ||
| 5 | #include <stdbool.h> | ||
| 6 | #include <setjmp.h> | 3 | #include <setjmp.h> |
| 7 | #include <assert.h> | ||
| 8 | #include <signal.h> | 4 | #include <signal.h> |
| 5 | #include <time.h> | ||
| 9 | 6 | ||
| 10 | #include "csv.h" | 7 | #include "csv.h" |
| 11 | 8 | ||
| 12 | #define INLINE static inline | 9 | static int |
| 13 | 10 | test_exception(FILE *f) | |
| 14 | /* === CSV-MEMORY Interface === */ | ||
| 15 | |||
| 16 | static void * | ||
| 17 | csv_mem_allocate(size_t n, size_t sz, void *cb_arg) | ||
| 18 | { | 11 | { |
| 19 | (void) cb_arg; | 12 | int line = 0; |
| 20 | 13 | csv_t csv[1]; | |
| 21 | return calloc(n, sz); | ||
| 22 | } | ||
| 23 | 14 | ||
| 24 | static void * | 15 | csv_init(csv); |
| 25 | csv_mem_reallocate(void *ptr, size_t n, size_t sz, void *cb_arg) | 16 | for ( int n; (n = csv_read(csv, f)) != -1; ) { |
| 26 | { | 17 | if (++line == 500000) csv_field(csv, -1); |
| 27 | (void) cb_arg; | 18 | } |
| 19 | csv_free(csv); | ||
| 28 | 20 | ||
| 29 | return reallocarray(ptr, n, sz); | 21 | return line; |
| 30 | } | 22 | } |
| 31 | 23 | ||
| 32 | static void | 24 | static void |
| 33 | csv_mem_free(void *ptr, void *cb_arg) | 25 | my_signal_handler(int sig) |
| 34 | { | ||
| 35 | (void) cb_arg; | ||
| 36 | |||
| 37 | free(ptr); | ||
| 38 | } | ||
| 39 | |||
| 40 | /* === CSV-OPTIONS Interface === */ | ||
| 41 | |||
| 42 | const csv_options_t csv_default_options = { | ||
| 43 | .quote_symbol = '"', | ||
| 44 | .sep_symbol = ',', | ||
| 45 | .cb_error = NULL, | ||
| 46 | .cb_allocate = csv_mem_allocate, | ||
| 47 | .cb_reallocate = csv_mem_reallocate, | ||
| 48 | .cb_free = csv_mem_free | ||
| 49 | }; | ||
| 50 | |||
| 51 | /* === CSV-ERROR Interface === */ | ||
| 52 | |||
| 53 | INLINE void | ||
| 54 | csv_fatal_error(const char *msg, const csv_options_t * const csv_options) | ||
| 55 | { | 26 | { |
| 56 | if ( csv_options->cb_error != NULL ) { | 27 | if ( sig == SIGABRT ) { |
| 57 | (*csv_options->cb_error)(msg, csv_options->cb_arg); | 28 | fprintf(stderr, "my_signal_handler!\n"); |
| 29 | exit(1); | ||
| 58 | } | 30 | } |
| 59 | |||
| 60 | fprintf(stderr, "fatal error: %s\n", msg); | ||
| 61 | abort(); | ||
| 62 | } | 31 | } |
| 63 | 32 | ||
| 64 | /* === CSV-STRING Interface === */ | 33 | static int |
| 65 | 34 | test_exception_with_signal(FILE *f) | |
| 66 | INLINE void | ||
| 67 | csv_string_init(csv_string_t *csv_string) | ||
| 68 | { | 35 | { |
| 69 | assert(csv_string != NULL); | 36 | void (*old_handler)(int) = signal(SIGABRT, my_signal_handler); |
| 70 | |||
| 71 | csv_string->str = NULL; | ||
| 72 | csv_string->cap = 0; | ||
| 73 | csv_string->pos = 0; | ||
| 74 | } | ||
| 75 | |||
| 76 | INLINE void | ||
| 77 | csv_string_reset(csv_string_t *csv_string) | ||
| 78 | { | ||
| 79 | assert(csv_string != NULL); | ||
| 80 | |||
| 81 | csv_string->pos = 0; | ||
| 82 | } | ||
| 83 | 37 | ||
| 84 | INLINE bool | 38 | int line = 0; |
| 85 | csv_string_empty(csv_string_t *csv_string) | 39 | csv_t csv[1]; |
| 86 | { | ||
| 87 | assert(csv_string != NULL); | ||
| 88 | |||
| 89 | return ( csv_string->pos == 0 ) ? true : false; | ||
| 90 | } | ||
| 91 | |||
| 92 | INLINE void | ||
| 93 | csv_string_append(csv_string_t *csv_string, char ch, const csv_options_t * const csv_options) | ||
| 94 | { | ||
| 95 | assert(csv_string != NULL); | ||
| 96 | assert(csv_options != NULL); | ||
| 97 | |||
| 98 | static const int INITIAL_CAP = 16; | ||
| 99 | |||
| 100 | if ( csv_string->pos == csv_string->cap ) { /* grow if needed */ | ||
| 101 | if ( csv_string->str == NULL ) { /* first call? */ | ||
| 102 | if ( (csv_string->str = csv_options->cb_allocate(INITIAL_CAP, 1, csv_options->cb_arg)) == NULL ) { | ||
| 103 | csv_fatal_error("out of memory...", csv_options); | ||
| 104 | return; | ||
| 105 | } | ||
| 106 | 40 | ||
| 107 | csv_string->cap = INITIAL_CAP; | 41 | csv_init(csv); |
| 108 | } | 42 | for ( int n; (n = csv_read(csv, f)) != -1; ) { |
| 109 | else { /* subsequent call */ | 43 | if (++line == 500000) csv_field(csv, -1); |
| 110 | int cap = (csv_string->cap * 3) / 2; /* *= 1.5 */ | ||
| 111 | char *str = csv_options->cb_reallocate(csv_string->str, cap, 1, csv_options->cb_arg); | ||
| 112 | if ( str == NULL ) { | ||
| 113 | csv_fatal_error("out of memory...", csv_options); | ||
| 114 | return; | ||
| 115 | } | ||
| 116 | csv_string->str = str; | ||
| 117 | csv_string->cap = cap; | ||
| 118 | } | ||
| 119 | } | 44 | } |
| 45 | csv_free(csv); | ||
| 120 | 46 | ||
| 121 | csv_string->str[csv_string->pos++] = ch; /* append char */ | 47 | signal(SIGABRT, old_handler); |
| 122 | } | ||
| 123 | |||
| 124 | INLINE void | ||
| 125 | csv_string_free(csv_string_t *csv_string, const csv_options_t * const csv_options) | ||
| 126 | { | ||
| 127 | assert(csv_string != NULL); | ||
| 128 | assert(csv_options != NULL); | ||
| 129 | |||
| 130 | csv_options->cb_free(csv_string->str, csv_options->cb_arg); | ||
| 131 | 48 | ||
| 132 | /* call *_init() for sane default values; prevent possible double-free */ | 49 | return line; |
| 133 | csv_string_init(csv_string); | ||
| 134 | } | 50 | } |
| 135 | 51 | ||
| 136 | /* === CSV-FIELD Interface === */ | 52 | static void |
| 137 | 53 | my_error_handler(const char *msg, void *env) | |
| 138 | INLINE void | ||
| 139 | csv_field_init(csv_field_t *csv_field) | ||
| 140 | { | 54 | { |
| 141 | assert(csv_field != NULL); | 55 | fprintf(stderr, "my_error_handler: %s\n", msg); |
| 142 | 56 | longjmp(*((jmp_buf *) env), 1); | |
| 143 | csv_field->fields = NULL; | ||
| 144 | csv_field->cap = 0; | ||
| 145 | csv_field->pos = 0; | ||
| 146 | } | 57 | } |
| 147 | 58 | ||
| 148 | INLINE void | 59 | static int |
| 149 | csv_field_reset(csv_field_t *csv_field) | 60 | test_exception_longjmp(FILE *f) |
| 150 | { | 61 | { |
| 151 | assert(csv_field != NULL); | 62 | /* start with default options */ |
| 152 | 63 | csv_options_t csv_options = csv_default_options; | |
| 153 | csv_field->pos = 0; | ||
| 154 | } | ||
| 155 | 64 | ||
| 156 | INLINE void | 65 | /* setup error handler */ |
| 157 | csv_field_append(csv_field_t *csv_field, int idx, const csv_options_t * const csv_options) | 66 | jmp_buf env; |
| 158 | { | 67 | csv_options.cb_error = my_error_handler; |
| 159 | assert(csv_field != NULL); | 68 | csv_options.cb_arg = &env; |
| 160 | assert(csv_options != NULL); | ||
| 161 | 69 | ||
| 162 | static const size_t INITIAL_CAP = 16; | 70 | /* setup csv-Object with custom options */ |
| 71 | csv_t csv[1]; | ||
| 72 | csv_init_opt(csv, &csv_options); | ||
| 163 | 73 | ||
| 164 | if ( csv_field->pos == csv_field->cap ) { /* grow if needed */ | 74 | if ( setjmp(env) == 0 ) { |
| 165 | if ( csv_field->fields == NULL ) { | 75 | int line = 0; |
| 166 | if ( (csv_field->fields = csv_options->cb_allocate(INITIAL_CAP, sizeof(csv_field->fields[0]), csv_options->cb_arg)) == NULL ) { | ||
| 167 | csv_fatal_error("out of memory", csv_options); | ||
| 168 | return; | ||
| 169 | } | ||
| 170 | 76 | ||
| 171 | csv_field->cap = INITIAL_CAP; | 77 | for ( int n; (n = csv_read(csv, f)) != -1; ) { |
| 172 | } | 78 | // process data... |
| 173 | else { | 79 | if (++line == 500000) csv_field(csv, -1); |
| 174 | int cap = (csv_field->cap * 3) / 2; /* *= 1.5 */ | ||
| 175 | int *fields = csv_options->cb_reallocate(csv_field->fields, cap, sizeof(csv_field->fields[0]), csv_options->cb_arg); | ||
| 176 | if ( fields == NULL ) { | ||
| 177 | csv_fatal_error("out of memory", csv_options); | ||
| 178 | return; | ||
| 179 | } | ||
| 180 | csv_field->fields = fields; | ||
| 181 | csv_field->cap = cap; | ||
| 182 | } | 80 | } |
| 183 | } | 81 | csv_free(csv); |
| 184 | 82 | return line; | |
| 185 | csv_field->fields[csv_field->pos++] = idx; /* append idx */ | ||
| 186 | } | ||
| 187 | |||
| 188 | INLINE void | ||
| 189 | csv_field_free(csv_field_t *csv_field, const csv_options_t * const csv_options) | ||
| 190 | { | ||
| 191 | assert(csv_field != NULL); | ||
| 192 | assert(csv_options != NULL); | ||
| 193 | |||
| 194 | csv_options->cb_free(csv_field->fields, csv_options->cb_arg); | ||
| 195 | |||
| 196 | csv_field_init(csv_field); | ||
| 197 | } | ||
| 198 | |||
| 199 | /* === CSV Interface === */ | ||
| 200 | |||
| 201 | void | ||
| 202 | csv_init(csv_t *csv) | ||
| 203 | { | ||
| 204 | assert(csv != NULL); | ||
| 205 | |||
| 206 | csv_init_opt(csv, NULL); | ||
| 207 | } | ||
| 208 | |||
| 209 | void | ||
| 210 | csv_init_opt(csv_t *csv, const csv_options_t * const csv_options) | ||
| 211 | { | ||
| 212 | assert(csv != NULL); | ||
| 213 | |||
| 214 | if ( csv_options != NULL ) { | ||
| 215 | csv->csv_options = csv_options; | ||
| 216 | } | 83 | } |
| 217 | else { | 84 | else { |
| 218 | csv->csv_options = &csv_default_options; | 85 | csv_free(csv); |
| 219 | } | 86 | return -1; |
| 220 | |||
| 221 | csv_string_init(&csv->csv_string); | ||
| 222 | csv_field_init(&csv->csv_field); | ||
| 223 | } | ||
| 224 | |||
| 225 | void | ||
| 226 | csv_free(csv_t *csv) | ||
| 227 | { | ||
| 228 | assert(csv != NULL); | ||
| 229 | |||
| 230 | csv_string_free(&csv->csv_string, csv->csv_options); | ||
| 231 | csv_field_free(&csv->csv_field, csv->csv_options); | ||
| 232 | } | ||
| 233 | |||
| 234 | int | ||
| 235 | csv_nfields(csv_t *csv) | ||
| 236 | { | ||
| 237 | assert(csv != NULL); | ||
| 238 | |||
| 239 | if ( csv->csv_string.pos != 0 ) { | ||
| 240 | return csv->csv_field.pos; | ||
| 241 | } | ||
| 242 | return 0; | ||
| 243 | } | ||
| 244 | |||
| 245 | const char * | ||
| 246 | csv_field(csv_t *csv, int idx) | ||
| 247 | { | ||
| 248 | assert(csv != NULL); | ||
| 249 | assert(idx >= 0 && idx < csv->csv_field.pos); | ||
| 250 | |||
| 251 | if ( idx < 0 || idx >= csv->csv_field.pos ) { | ||
| 252 | csv_fatal_error("range error", csv->csv_options); | ||
| 253 | } | ||
| 254 | |||
| 255 | return &csv->csv_string.str[csv->csv_field.fields[idx]]; | ||
| 256 | } | ||
| 257 | |||
| 258 | int | ||
| 259 | csv_read(csv_t *csv, FILE *in) | ||
| 260 | { | ||
| 261 | assert(csv != NULL); | ||
| 262 | assert(in != NULL); | ||
| 263 | |||
| 264 | enum { | ||
| 265 | STATE_START_FIELD, | ||
| 266 | STATE_QUOTED_FIELD, | ||
| 267 | STATE_SIMPLE_FIELD, | ||
| 268 | STATE_END_FIELD, | ||
| 269 | STATE_END_LINE, | ||
| 270 | STATE_END_FILE, | ||
| 271 | }; | ||
| 272 | |||
| 273 | register const int QUOTE = csv->csv_options->quote_symbol; | ||
| 274 | register const int SEP = csv->csv_options->sep_symbol; | ||
| 275 | |||
| 276 | csv_string_reset(&csv->csv_string); | ||
| 277 | csv_field_reset(&csv->csv_field); | ||
| 278 | |||
| 279 | for ( int state = STATE_START_FIELD; ; ) { | ||
| 280 | int ch; | ||
| 281 | |||
| 282 | switch ( state ) { | ||
| 283 | case STATE_START_FIELD: | ||
| 284 | csv_field_append(&csv->csv_field, csv->csv_string.pos, csv->csv_options); | ||
| 285 | |||
| 286 | ch = getc(in); | ||
| 287 | if ( ch == EOF ) { | ||
| 288 | state = STATE_END_FILE; | ||
| 289 | } | ||
| 290 | else if ( ch == '\r' ) { /* Teste auf CR.. */ | ||
| 291 | ch = getc(in); | ||
| 292 | if ( ch != '\n' ) { /* ... LF */ | ||
| 293 | ungetc(ch, in); | ||
| 294 | } | ||
| 295 | state = STATE_END_LINE; | ||
| 296 | } | ||
| 297 | else if ( ch == '\n' ) { | ||
| 298 | state = STATE_END_LINE; | ||
| 299 | } | ||
| 300 | else if ( ch == SEP ) { | ||
| 301 | state = STATE_END_FIELD; | ||
| 302 | } | ||
| 303 | else if ( ch == QUOTE ) { | ||
| 304 | state = STATE_QUOTED_FIELD; | ||
| 305 | } | ||
| 306 | else { | ||
| 307 | csv_string_append(&csv->csv_string, ch, csv->csv_options); | ||
| 308 | state = STATE_SIMPLE_FIELD; | ||
| 309 | } | ||
| 310 | break; | ||
| 311 | |||
| 312 | case STATE_QUOTED_FIELD: | ||
| 313 | do { | ||
| 314 | ch = getc(in); | ||
| 315 | if ( ch == EOF ) { | ||
| 316 | state = STATE_END_FILE; | ||
| 317 | } | ||
| 318 | else if ( ch == QUOTE ) { | ||
| 319 | ch = getc(in); | ||
| 320 | if ( ch == EOF ) { | ||
| 321 | state = STATE_END_FILE; | ||
| 322 | } | ||
| 323 | else if ( ch == QUOTE ) { | ||
| 324 | csv_string_append(&csv->csv_string, QUOTE, csv->csv_options); | ||
| 325 | } | ||
| 326 | else if ( ch == SEP ) { | ||
| 327 | state = STATE_END_FIELD; | ||
| 328 | } | ||
| 329 | else if ( ch == '\r' ) { | ||
| 330 | ch = getc(in); | ||
| 331 | if ( ch != '\n' ) { | ||
| 332 | ungetc(ch, in); | ||
| 333 | } | ||
| 334 | state = STATE_END_LINE; | ||
| 335 | } | ||
| 336 | else if ( ch == '\n' ) { | ||
| 337 | state = STATE_END_LINE; | ||
| 338 | } | ||
| 339 | else { | ||
| 340 | csv_string_append(&csv->csv_string, QUOTE, csv->csv_options); | ||
| 341 | ungetc(ch, in); /* zuviel gelesenes Zeichen zurückstellen */ | ||
| 342 | } | ||
| 343 | } | ||
| 344 | else { | ||
| 345 | csv_string_append(&csv->csv_string, ch, csv->csv_options); | ||
| 346 | } | ||
| 347 | } while ( state == STATE_QUOTED_FIELD ); | ||
| 348 | break; | ||
| 349 | |||
| 350 | case STATE_SIMPLE_FIELD: | ||
| 351 | do { | ||
| 352 | ch = getc(in); | ||
| 353 | if ( ch == EOF ) { | ||
| 354 | state = STATE_END_FILE; | ||
| 355 | } | ||
| 356 | else if ( ch == SEP ) { | ||
| 357 | state = STATE_END_FIELD; | ||
| 358 | } | ||
| 359 | else if ( ch == '\r' ) { | ||
| 360 | ch = getc(in); | ||
| 361 | if ( ch != '\n' ) { | ||
| 362 | ungetc(ch, in); | ||
| 363 | } | ||
| 364 | state = STATE_END_LINE; | ||
| 365 | } | ||
| 366 | else if ( ch == '\n' ) { | ||
| 367 | state = STATE_END_LINE; | ||
| 368 | } | ||
| 369 | else { | ||
| 370 | csv_string_append(&csv->csv_string, ch, csv->csv_options); | ||
| 371 | } | ||
| 372 | } while ( state == STATE_SIMPLE_FIELD ); | ||
| 373 | break; | ||
| 374 | |||
| 375 | case STATE_END_FIELD: | ||
| 376 | csv_string_append(&csv->csv_string, '\0', csv->csv_options); | ||
| 377 | state = STATE_START_FIELD; | ||
| 378 | break; | ||
| 379 | |||
| 380 | case STATE_END_LINE: | ||
| 381 | csv_string_append(&csv->csv_string, '\0', csv->csv_options); | ||
| 382 | return csv->csv_field.pos; | ||
| 383 | |||
| 384 | case STATE_END_FILE: | ||
| 385 | if ( csv_string_empty(&csv->csv_string) ) { | ||
| 386 | return -1; /* EOF reached */ | ||
| 387 | } | ||
| 388 | |||
| 389 | /* EOF wurde gelesen, allerdings sind Daten zur Vearbeitung vorhanden! */ | ||
| 390 | /* => aktuelles Feld abschließen... */ | ||
| 391 | csv_string_append(&csv->csv_string, '\0', csv->csv_options); | ||
| 392 | |||
| 393 | /* ... und die Anzahl der Felder zurückliefern! */ | ||
| 394 | return csv->csv_field.pos; | ||
| 395 | |||
| 396 | default: | ||
| 397 | assert(!"this should never be happen..."); | ||
| 398 | break; | ||
| 399 | } | ||
| 400 | } | ||
| 401 | /* NOT REACHED */ | ||
| 402 | } | ||
| 403 | |||
| 404 | static void | ||
| 405 | csv_write_field(FILE *out, const char *field, const csv_options_t * const csv_options) | ||
| 406 | { | ||
| 407 | assert(csv_options != NULL); | ||
| 408 | assert(field != NULL); | ||
| 409 | assert(out != NULL); | ||
| 410 | |||
| 411 | const char QUOTE = csv_options->quote_symbol; | ||
| 412 | |||
| 413 | if ( *field != '\0' ) { | ||
| 414 | putc(QUOTE, out); | ||
| 415 | |||
| 416 | for ( ; *field != '\0'; ++field ) { | ||
| 417 | if ( *field == QUOTE ) { | ||
| 418 | putc(QUOTE, out); | ||
| 419 | putc(QUOTE, out); | ||
| 420 | } | ||
| 421 | else { | ||
| 422 | putc(*field, out); | ||
| 423 | } | ||
| 424 | } | ||
| 425 | |||
| 426 | putc(QUOTE, out); | ||
| 427 | } | ||
| 428 | } | ||
| 429 | |||
| 430 | void | ||
| 431 | csv_write(FILE *out, int n, const char *fields[], const csv_options_t * const csv_options) | ||
| 432 | { | ||
| 433 | assert(csv_options != NULL); | ||
| 434 | assert(fields != NULL); | ||
| 435 | assert(out != NULL); | ||
| 436 | |||
| 437 | const char SEP = csv_options->quote_symbol; | ||
| 438 | |||
| 439 | /* process first field */ | ||
| 440 | if ( 0 != n && fields[0] != NULL ) { | ||
| 441 | csv_write_field(out, fields[0], csv_options); | ||
| 442 | |||
| 443 | /* process next fields */ | ||
| 444 | for ( int i = 1; i != n && fields[i] != NULL; ++i ) { | ||
| 445 | putc(SEP, out); | ||
| 446 | csv_write_field(out, fields[i], csv_options); | ||
| 447 | } | ||
| 448 | } | 87 | } |
| 449 | fprintf(out, "\r\n"); | ||
| 450 | } | ||
| 451 | |||
| 452 | jmp_buf env; | ||
| 453 | |||
| 454 | static void sig_handler(int sig) | ||
| 455 | { | ||
| 456 | (void) sig; | ||
| 457 | longjmp(env, 1); | ||
| 458 | } | 88 | } |
| 459 | 89 | ||
| 460 | int | 90 | int |
| 461 | main(void) | 91 | main(void) |
| 462 | { | 92 | { |
| 463 | csv_t csv[1]; | ||
| 464 | int n, line = 0; | ||
| 465 | |||
| 466 | signal(SIGABRT, sig_handler); | ||
| 467 | |||
| 468 | FILE *in = fopen("500000 Records.csv", "r"); | 93 | FILE *in = fopen("500000 Records.csv", "r"); |
| 469 | if ( in == NULL ) { | 94 | if ( in == NULL ) { |
| 470 | fprintf(stderr, "failed to open testfile...\n"); | 95 | fprintf(stderr, "failed to open testfile...\n"); |
| @@ -473,23 +98,15 @@ main(void) | |||
| 473 | 98 | ||
| 474 | clock_t start = clock(); | 99 | clock_t start = clock(); |
| 475 | 100 | ||
| 476 | csv_init(csv); | 101 | (void) test_exception; |
| 477 | if ( setjmp(env) == 0 ) { | 102 | (void) test_exception_with_signal; |
| 478 | while ( (n = csv_read(csv, in)) != -1 ) { | 103 | int lines = test_exception_longjmp(in); |
| 479 | //if ( line == 500000 ) csv_field(csv, -1); | ||
| 480 | ++line; | ||
| 481 | } | ||
| 482 | } | ||
| 483 | else { | ||
| 484 | printf("Fehler beim Verarbeiten der Datei...\n"); | ||
| 485 | } | ||
| 486 | csv_free(csv); | ||
| 487 | 104 | ||
| 488 | clock_t end = clock(); | 105 | clock_t end = clock(); |
| 489 | 106 | ||
| 490 | fclose(in); | 107 | fclose(in); |
| 491 | 108 | ||
| 492 | printf("%d lines processed, duration: %.3lf sec\n", line, ((double)(end - start)) / CLOCKS_PER_SEC); | 109 | printf("%d lines processed, duration: %.3lf sec\n", lines, ((double)(end - start)) / CLOCKS_PER_SEC); |
| 493 | 110 | ||
| 494 | return EXIT_SUCCESS; | 111 | return EXIT_SUCCESS; |
| 495 | } | 112 | } |
