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