diff options
| author | Thomas Schmucker <ts@its1.de> | 2020-08-21 13:37:11 +0200 |
|---|---|---|
| committer | Thomas Schmucker <ts@its1.de> | 2020-08-21 13:37:11 +0200 |
| commit | 0ee2ec205c32de9b2563cc4175d1f6d3b65eb220 (patch) | |
| tree | f075fcf2a1f66c83f8fc7076a03ba57e2e6719e4 | |
| parent | b24ed5ebcd10d3295b009691833dec8f685ff4f6 (diff) | |
| download | libcsv-0ee2ec205c32de9b2563cc4175d1f6d3b65eb220.tar.gz libcsv-0ee2ec205c32de9b2563cc4175d1f6d3b65eb220.tar.bz2 libcsv-0ee2ec205c32de9b2563cc4175d1f6d3b65eb220.zip | |
fix(doku): Dokumentation überarbeitet und deutlich ausgebaut.
| -rw-r--r-- | .gitignore | 1 | ||||
| -rw-r--r-- | csv-tutorial.c | 118 | ||||
| -rw-r--r-- | doc/csv.md | 142 | ||||
| -rw-r--r-- | makefile | 7 |
4 files changed, 251 insertions, 17 deletions
| @@ -4,6 +4,7 @@ | |||
| 4 | csv-test | 4 | csv-test |
| 5 | csv-perf | 5 | csv-perf |
| 6 | seq-read | 6 | seq-read |
| 7 | csv-tutorial | ||
| 7 | 8 | ||
| 8 | pop-csv.c | 9 | pop-csv.c |
| 9 | t/ | 10 | t/ |
diff --git a/csv-tutorial.c b/csv-tutorial.c new file mode 100644 index 0000000..17b95c8 --- /dev/null +++ b/csv-tutorial.c | |||
| @@ -0,0 +1,118 @@ | |||
| 1 | #include <setjmp.h> | ||
| 2 | #include <stdbool.h> | ||
| 3 | #include <stdio.h> | ||
| 4 | #include <stdlib.h> | ||
| 5 | |||
| 6 | #include "csv.h" | ||
| 7 | |||
| 8 | bool | ||
| 9 | processFile(const char *filename) | ||
| 10 | { | ||
| 11 | FILE *in; | ||
| 12 | |||
| 13 | if ( (in = fopen(filename, "r")) == NULL ) { | ||
| 14 | return false; | ||
| 15 | } | ||
| 16 | |||
| 17 | csv_t csv; | ||
| 18 | csv_init(&csv); | ||
| 19 | |||
| 20 | while ( csv_read(&csv, in) ) { | ||
| 21 | const size_t nf = csv_nfields(&csv); | ||
| 22 | (void) nf; | ||
| 23 | // ... | ||
| 24 | } | ||
| 25 | csv_cleanup(&csv); | ||
| 26 | |||
| 27 | fclose(in); | ||
| 28 | return true; | ||
| 29 | } | ||
| 30 | |||
| 31 | bool | ||
| 32 | processFile_options(const char *filename) | ||
| 33 | { | ||
| 34 | FILE *in; | ||
| 35 | |||
| 36 | if ( (in = fopen(filename, "r")) == NULL ) { | ||
| 37 | return false; | ||
| 38 | } | ||
| 39 | |||
| 40 | csv_options_t csv_options = csv_default_options; | ||
| 41 | csv_options.field_delimiter = '"'; | ||
| 42 | csv_options.field_separator = ','; | ||
| 43 | |||
| 44 | csv_t csv; | ||
| 45 | csv_init_opt(&csv, &csv_options); | ||
| 46 | |||
| 47 | while ( csv_read(&csv, in) ) { | ||
| 48 | const size_t nf = csv_nfields(&csv); | ||
| 49 | (void) nf; | ||
| 50 | // ... | ||
| 51 | } | ||
| 52 | csv_cleanup(&csv); | ||
| 53 | |||
| 54 | fclose(in); | ||
| 55 | return true; | ||
| 56 | } | ||
| 57 | |||
| 58 | static void | ||
| 59 | csvErrorHandler(csv_err_t err, void *cb_arg) | ||
| 60 | { | ||
| 61 | longjmp(*((jmp_buf *) cb_arg), err); | ||
| 62 | } | ||
| 63 | |||
| 64 | bool | ||
| 65 | processFile_error(const char *filename) | ||
| 66 | { | ||
| 67 | FILE *in; | ||
| 68 | |||
| 69 | if ( (in = fopen(filename, "r")) == NULL ) { | ||
| 70 | return false; | ||
| 71 | } | ||
| 72 | |||
| 73 | csv_options_t csv_options = csv_default_options; | ||
| 74 | csv_options.field_delimiter = '"'; | ||
| 75 | csv_options.field_separator = ','; | ||
| 76 | |||
| 77 | jmp_buf env; | ||
| 78 | csv_options.cb_error = csvErrorHandler; | ||
| 79 | csv_options.cb_error_arg = &env; | ||
| 80 | |||
| 81 | csv_t csv; | ||
| 82 | csv_init_opt(&csv, &csv_options); | ||
| 83 | |||
| 84 | switch ( setjmp(env) ) { | ||
| 85 | case CSV_ERR_OK: | ||
| 86 | while ( csv_read(&csv, in) ) { | ||
| 87 | const size_t nf = csv_nfields(&csv); | ||
| 88 | (void) nf; | ||
| 89 | // ... | ||
| 90 | } | ||
| 91 | break; | ||
| 92 | |||
| 93 | case CSV_ERR_OUT_OF_MEMORY: | ||
| 94 | csv_cleanup(&csv); | ||
| 95 | break; | ||
| 96 | |||
| 97 | case CSV_ERR_OUT_OF_RANGE: | ||
| 98 | csv_cleanup(&csv); | ||
| 99 | break; | ||
| 100 | |||
| 101 | case CSV_ERR_IO_READ: | ||
| 102 | csv_cleanup(&csv); | ||
| 103 | break; | ||
| 104 | |||
| 105 | case CSV_ERR_IO_WRITE: | ||
| 106 | csv_cleanup(&csv); | ||
| 107 | break; | ||
| 108 | } | ||
| 109 | |||
| 110 | fclose(in); | ||
| 111 | return true; | ||
| 112 | } | ||
| 113 | |||
| 114 | int | ||
| 115 | main(void) | ||
| 116 | { | ||
| 117 | return EXIT_SUCCESS; | ||
| 118 | } | ||
| @@ -66,7 +66,132 @@ Die meisten anderen CSV-Bibliotheken passen nicht: | |||
| 66 | 66 | ||
| 67 | ## Tutorial | 67 | ## Tutorial |
| 68 | 68 | ||
| 69 | to be written | 69 | **1\. Ausgangspunkt: ein Grundgerüst** |
| 70 | |||
| 71 | Nehmen wir an, wir schreiben eine Funktion zur CSV-Verarbeitung. | ||
| 72 | Das Grundgerüst sieht dann etwa so aus: | ||
| 73 | |||
| 74 | ~~~ | ||
| 75 | bool | ||
| 76 | processFile(const char *filename) | ||
| 77 | { | ||
| 78 | FILE *in; | ||
| 79 | |||
| 80 | if ( (in = fopen(filename, "r")) == NULL ) { | ||
| 81 | return false; | ||
| 82 | } | ||
| 83 | |||
| 84 | // [ HIERHER KOMMT DER CSV-HANDLING CODE ] | ||
| 85 | |||
| 86 | fclose(in); | ||
| 87 | return true; | ||
| 88 | } | ||
| 89 | ~~~ | ||
| 90 | |||
| 91 | Wir fügen als erstes etwas Code hinzu: | ||
| 92 | |||
| 93 | ~~~ | ||
| 94 | csv_t csv; | ||
| 95 | csv_init(&csv); | ||
| 96 | |||
| 97 | while ( csv_read(&csv, in) ) { | ||
| 98 | const size_t nf = csv_nfields(&csv); | ||
| 99 | // ... | ||
| 100 | } | ||
| 101 | csv_cleanup(&csv); | ||
| 102 | ~~~ | ||
| 103 | |||
| 104 | Dieser Code erzeugt ein neues `csv_t`-Objekt im Speicher welches anschließend mit `csv_init()` mit Standardwerten initialisiert wird. | ||
| 105 | |||
| 106 | Dann wird der Datenstrom `in` zeilenweise in den Speicher eingelesen und innerhalb der Schleife verarbeitet. Die Anzahl der Felder pro Zeile wird in der Schleife mit Hilfe der Funktion `csv_nfields()` ermittelt. Alternativ liefert auch der Returnwert des vorherigen `csv_read()`-Aufrufs die Feldanzahl. | ||
| 107 | |||
| 108 | Der Aufruf von `csv_cleanup()` ist nicht zwingend notwendig, da alle belegten Ressourcen beim letzten `csv_read()` automatisch freigegeben werden, wenn es keinen Fehler bei der Dateiverarbeitung gab. | ||
| 109 | |||
| 110 | **2\. Optionen einstellen** | ||
| 111 | |||
| 112 | Wenn nichts weiter angegeben ist, gehen wir von einem Komma (`,`) als Feldtrenner sowie doppelten Anführungszeichen (`"`) als Feldbegrenzer aus. Die meisten CSV-Dateien im deutschsprachigen Raum verwenden jedoch ein Semikolon (`;`) als Feldtrenner. | ||
| 113 | |||
| 114 | Wir berücksichtigen dies bei der Initialisierung eines `csv_options_t`-Objektes. | ||
| 115 | |||
| 116 | ~~~ | ||
| 117 | csv_options_t csv_options = csv_default_options; | ||
| 118 | csv_options.field_delimiter = '"'; | ||
| 119 | csv_options.field_separator = ','; | ||
| 120 | |||
| 121 | csv_t csv; | ||
| 122 | csv_init_opt(&csv, &csv_options); | ||
| 123 | ~~~ | ||
| 124 | |||
| 125 | Das `csv`-Objekt wird nun mit der Funktion `csv_init_opt()` initialisiert, die einen zusätzlichen Zeiger auf ein `csv_options_t`-Objekt erwartet. Damit wir nicht alle Felder von `csv_options` ändern müssen, initialisieren wir dieses zunächst mit `csv_default_options`. | ||
| 126 | |||
| 127 | > **Hinweis:** | ||
| 128 | > `csv_init(&csv);` ist letztlich nur eine kürzere Schreibweise für `csv_init_opt(&csv, &csv_default_options);`. | ||
| 129 | |||
| 130 | Der Rest der Schleife bleibt gleich. | ||
| 131 | |||
| 132 | **3\. Fehlerbehandlung hinzufügen** | ||
| 133 | |||
| 134 | Bei der Verarbeitung können verschiedene Fehler auftreten, die ein robustes Programm selbstverständlich behandelt. | ||
| 135 | Leider stellt die Programmiersprache C keinen Exception-Mechanismus zur Verfügung, so dass sich der Programmierer an dieser Stelle anders behelfen muss. | ||
| 136 | |||
| 137 | Die beste Variante, die dem klassischen Exceptions am nächsten kommt, sind die beiden C-Funktionen `setjmp()` und `longjmp()`, mit deren Hilfe wir nun die Fehlerbehandlung umsetzen. Wir benötigen dafür die beiden Felder `cb_error` und `cb_error_arg` des `csv_options`-Objektes: | ||
| 138 | |||
| 139 | ~~~ | ||
| 140 | jmp_buf env; | ||
| 141 | csv_options.cb_error = csvErrorHandler; | ||
| 142 | csv_options.cb_error_arg = &env; | ||
| 143 | ~~~ | ||
| 144 | |||
| 145 | `cb_error` ist ein Zeiger auf eine Callback-Funktion, die im Falle eines Fehlers aus der CSV-Bibliothek heraus aufgerufen wird. Ihr werden ein Fehlerwert vom Typ `csv_err_t` sowie ein Zeiger auf benutzerdefinierte Daten -- in unserem Fall ein Zeiger auf eine `jmp_buf`-Variable -- mit übergeben: | ||
| 146 | |||
| 147 | ~~~ | ||
| 148 | static void | ||
| 149 | csvErrorHandler(csv_err_t err, void *cb_arg) | ||
| 150 | { | ||
| 151 | fprintf(stderr, "error while csv-processing: %s\n", csv_err_str(err)); | ||
| 152 | longjmp(*((jmp_buf *) cb_arg), err); | ||
| 153 | } | ||
| 154 | ~~~ | ||
| 155 | |||
| 156 | Mit Hilfe des Parameter `err` wird eine einfache Fehlermeldung protokolliert. Anschließend rufen wir `longjmp()` auf, damit die Fehlerbehandlung auf höherer Ebene fortgesetzt wird. | ||
| 157 | |||
| 158 | Die Hauptschleife in unserer Funktion sieht nun so aus: | ||
| 159 | |||
| 160 | ~~~ | ||
| 161 | csv_options_t csv_options = csv_default_options; | ||
| 162 | csv_options.field_delimiter = '"'; | ||
| 163 | csv_options.field_separator = ','; | ||
| 164 | |||
| 165 | jmp_buf env; | ||
| 166 | csv_options.cb_error = csvErrorHandler; | ||
| 167 | csv_options.cb_error_arg = &env; | ||
| 168 | |||
| 169 | csv_t csv; | ||
| 170 | csv_init_opt(&csv, &csv_options); | ||
| 171 | |||
| 172 | switch ( setjmp(env) ) { | ||
| 173 | case CSV_ERR_OK: | ||
| 174 | while ( csv_read(&csv, in) ) { | ||
| 175 | const size_t nf = csv_nfields(&csv); | ||
| 176 | // ... | ||
| 177 | } | ||
| 178 | // kein csv_cleanup() notwendig weil die Datei an | ||
| 179 | // dieser Stelle vollständig verarbeitet wurde. | ||
| 180 | break; | ||
| 181 | |||
| 182 | case CSV_ERR_OUT_OF_MEMORY: | ||
| 183 | csv_cleanup(&csv); | ||
| 184 | break; | ||
| 185 | |||
| 186 | case CSV_ERR_OUT_OF_RANGE: | ||
| 187 | csv_cleanup(&csv); | ||
| 188 | break; | ||
| 189 | |||
| 190 | case CSV_ERR_IO_READ: | ||
| 191 | csv_cleanup(&csv); | ||
| 192 | break; | ||
| 193 | } | ||
| 194 | ~~~ | ||
| 70 | 195 | ||
| 71 | ## API | 196 | ## API |
| 72 | 197 | ||
| @@ -110,7 +235,6 @@ typedef struct { | |||
| 110 | csv_options_t *csv_options; | 235 | csv_options_t *csv_options; |
| 111 | /* additional internal fields */ | 236 | /* additional internal fields */ |
| 112 | } csv_t; | 237 | } csv_t; |
| 113 | |||
| 114 | ~~~ | 238 | ~~~ |
| 115 | 239 | ||
| 116 | ### Funktionen | 240 | ### Funktionen |
| @@ -147,10 +271,6 @@ const char *csv_field(const csv_t *const csv, size_t idx); | |||
| 147 | const char *csv_err_str(csv_err_t csv_err); | 271 | const char *csv_err_str(csv_err_t csv_err); |
| 148 | ~~~ | 272 | ~~~ |
| 149 | 273 | ||
| 150 | ## Fehlerbehandlung | ||
| 151 | |||
| 152 | to be written | ||
| 153 | |||
| 154 | ## Anpassungen | 274 | ## Anpassungen |
| 155 | 275 | ||
| 156 | Die CSV-Bibliothek kann an verschiedene Bedürfnisse angepasst werden. | 276 | Die CSV-Bibliothek kann an verschiedene Bedürfnisse angepasst werden. |
| @@ -159,7 +279,7 @@ Die CSV-Bibliothek kann an verschiedene Bedürfnisse angepasst werden. | |||
| 159 | 279 | ||
| 160 | Beim Compilieren der Datei csv.c können die Standardwerte für Feldbegrenzer und Feldtrenner als Makrodefinitionen `CSV_DEFAULT_DELIMITER` und `CSV_DEFAULT_SEPARATOR` voreingestellt werden. | 280 | Beim Compilieren der Datei csv.c können die Standardwerte für Feldbegrenzer und Feldtrenner als Makrodefinitionen `CSV_DEFAULT_DELIMITER` und `CSV_DEFAULT_SEPARATOR` voreingestellt werden. |
| 161 | 281 | ||
| 162 | Option Beschreibung Standardwert | 282 | Option Beschreibung Standardwert |
| 163 | -------------------------- -------------- --------------------------------- | 283 | -------------------------- -------------- --------------------------------- |
| 164 | `CSV_DEFAULT_DELIMITER` Feldbegrenzer `"` | 284 | `CSV_DEFAULT_DELIMITER` Feldbegrenzer `"` |
| 165 | `CSV_DEFAULT_SEPARATOR` Feldtrenner `,` | 285 | `CSV_DEFAULT_SEPARATOR` Feldtrenner `,` |
| @@ -176,14 +296,6 @@ Anschließend wird die Datei `csv-german.o` statt `csv.o` zum Projekt dazugelink | |||
| 176 | 296 | ||
| 177 | Die Headerdatei (`csv.h`) bleibt unverändert. | 297 | Die Headerdatei (`csv.h`) bleibt unverändert. |
| 178 | 298 | ||
| 179 | ### Eigener Speicherallozierer | ||
| 180 | |||
| 181 | to be written | ||
| 182 | |||
| 183 | ## Performance | ||
| 184 | |||
| 185 | to be written | ||
| 186 | |||
| 187 | ## Lizenz | 299 | ## Lizenz |
| 188 | 300 | ||
| 189 | In den letzten Jahrzehnten habe ich massiv vom Internet und seinen Inhalten profitiert. Ein gutes Stück meiner Kenntnisse und Fähigkeiten verdanke ich den vielen Foren, Webseiten, Newsgroups und Individuen die Informationen und Software kostenfrei und allgemein zugänglich bereitstellen. | 301 | In den letzten Jahrzehnten habe ich massiv vom Internet und seinen Inhalten profitiert. Ein gutes Stück meiner Kenntnisse und Fähigkeiten verdanke ich den vielen Foren, Webseiten, Newsgroups und Individuen die Informationen und Software kostenfrei und allgemein zugänglich bereitstellen. |
| @@ -1,6 +1,6 @@ | |||
| 1 | include config.mk | 1 | include config.mk |
| 2 | 2 | ||
| 3 | all: csv-test csv-perf seq-read | 3 | all: csv-test csv-perf seq-read csv-tutorial |
| 4 | 4 | ||
| 5 | csv-test: csv-test.c csv.c csv.h | 5 | csv-test: csv-test.c csv.c csv.h |
| 6 | $(CC) $(CFLAGS-TEST) -DNDEBUG -o $@ csv-test.c csv.c | 6 | $(CC) $(CFLAGS-TEST) -DNDEBUG -o $@ csv-test.c csv.c |
| @@ -16,10 +16,13 @@ csv-perf: csv-perf.c csv.o csv.h | |||
| 16 | seq-read: seq-read.c | 16 | seq-read: seq-read.c |
| 17 | $(CC) $(CFLAGS) -o $@ $< | 17 | $(CC) $(CFLAGS) -o $@ $< |
| 18 | 18 | ||
| 19 | csv-tutorial: csv-tutorial.c csv.o csv.h | ||
| 20 | $(CC) $(CFLAGS) -o $@ csv-tutorial.c csv.o | ||
| 21 | |||
| 19 | csv.o: csv.c csv.h | 22 | csv.o: csv.c csv.h |
| 20 | $(CC) $(CFLAGS) -DNDEBUG -o $@ -c $< | 23 | $(CC) $(CFLAGS) -DNDEBUG -o $@ -c $< |
| 21 | 24 | ||
| 22 | clean: | 25 | clean: |
| 23 | rm csv-test csv-perf seq-read csv.o | 26 | rm -f csv-test csv-perf seq-read csv.o csv-tutorial |
| 24 | 27 | ||
| 25 | .PHONY: all clean coverage | 28 | .PHONY: all clean coverage |
