diff options
Diffstat (limited to 'src/csv.c')
| -rw-r--r-- | src/csv.c | 168 |
1 files changed, 168 insertions, 0 deletions
diff --git a/src/csv.c b/src/csv.c new file mode 100644 index 0000000..ea9859b --- /dev/null +++ b/src/csv.c @@ -0,0 +1,168 @@ +#include "csv.h" + +#include <stdbool.h> +#include <stdlib.h> +#include <string.h> + +struct csv_reader { + const char *p; + const char *end; +}; + +typedef struct { + char *data; + size_t len; + size_t cap; +} csv_buf_t; + +csv_reader_t *csv_reader_new(const char *data, size_t len) +{ + csv_reader_t *reader; + + if (data == NULL) + return NULL; + reader = calloc(1, sizeof(*reader)); + if (reader == NULL) + return NULL; + reader->p = data; + reader->end = data + len; + if (len >= 3 && memcmp(data, "\xEF\xBB\xBF", 3) == 0) + reader->p += 3; + return reader; +} + +void csv_reader_free(csv_reader_t *reader) +{ + free(reader); +} + +void csv_row_free(csv_row_t *row) +{ + size_t i; + + if (row == NULL) + return; + for (i = 0; i < row->count; i++) + free(row->fields[i]); + free(row->fields); + row->fields = NULL; + row->count = 0; +} + +static bool buf_push(csv_buf_t *buf, char c) +{ + if (buf->len + 2 > buf->cap) { + size_t cap = buf->cap ? buf->cap * 2 : 32; + char *grown = realloc(buf->data, cap); + if (grown == NULL) + return false; + buf->data = grown; + buf->cap = cap; + } + buf->data[buf->len++] = c; + buf->data[buf->len] = '\0'; + return true; +} + +static bool row_push(csv_row_t *row, size_t *cap, char *field) +{ + if (row->count + 1 > *cap) { + size_t next = *cap ? *cap * 2 : 16; + char **grown = realloc(row->fields, next * sizeof(*grown)); + if (grown == NULL) + return false; + row->fields = grown; + *cap = next; + } + row->fields[row->count++] = field; + return true; +} + +static char *parse_field(csv_reader_t *reader) +{ + csv_buf_t field = {NULL, 0, 0}; + bool quoted = false; + + if (!buf_push(&field, '\0')) + return NULL; + field.len = 0; + field.data[0] = '\0'; + + if (reader->p < reader->end && *reader->p == '"') { + quoted = true; + reader->p++; + } + + while (reader->p < reader->end) { + char c = *reader->p; + + if (quoted) { + reader->p++; + if (c != '"') { + if (!buf_push(&field, c)) + goto fail; + continue; + } + if (reader->p < reader->end && *reader->p == '"') { + reader->p++; + if (!buf_push(&field, '"')) + goto fail; + continue; + } + quoted = false; + continue; + } + if (c == ',' || c == '\n' || c == '\r') + break; + reader->p++; + if (!buf_push(&field, c)) + goto fail; + } + + if (quoted) + goto fail; + return field.data; + +fail: + free(field.data); + return NULL; +} + +int csv_reader_next(csv_reader_t *reader, csv_row_t *out) +{ + csv_row_t row = {NULL, 0}; + size_t cap = 0; + + if (reader == NULL || out == NULL) + return -1; + if (reader->p >= reader->end) + return 0; + + for (;;) { + char *field = parse_field(reader); + + if (field == NULL) { + csv_row_free(&row); + return -1; + } + if (!row_push(&row, &cap, field)) { + free(field); + csv_row_free(&row); + return -1; + } + if (reader->p >= reader->end) + break; + if (*reader->p == ',') { + reader->p++; + continue; + } + if (*reader->p == '\r') + reader->p++; + if (reader->p < reader->end && *reader->p == '\n') + reader->p++; + break; + } + + *out = row; + return 1; +} |