diff options
Diffstat (limited to 'readme.md')
| -rw-r--r-- | readme.md | 69 |
1 files changed, 41 insertions, 28 deletions
| @@ -41,12 +41,13 @@ for ( size_t nf; (nf = csv_read(&csv, in)) != 0; ) { /* eine Zeile aus `in` les | |||
| 41 | ~~~ | 41 | ~~~ |
| 42 | 42 | ||
| 43 | Es sind nur zwei Funktionen notwendig, um die Daten des FILE-Streams `in` vollständig zu verarbeiten: | 43 | Es sind nur zwei Funktionen notwendig, um die Daten des FILE-Streams `in` vollständig zu verarbeiten: |
| 44 | `csv_read()` liest eine Zeile aus dem Parameter `in` und liefert die Anzahl der erfolgreich geparsten Felder oder **0** bei Dateiende. | 44 | `csv_read()` liest eine Zeile aus dem Stream `in` und liefert die Anzahl der erfolgreich analysierten Felder oder **0** bei Dateiende. |
| 45 | `csv_field()` liefert einen (read only) `const char`-Zeiger für das i-te Feld. | 45 | `csv_field()` liefert einen (read only) `const char`-Zeiger für das *i*te Feld. |
| 46 | 46 | ||
| 47 | Der Speicher für das CSV-Objekt wird automatisch von `csv_read()` beim ersten Aufruf alloziert und wieder freigeben nachdem der Stream `in` vollständig gelesen wurde. | 47 | Der Speicher für das CSV-Objekt wird automatisch von `csv_read()` beim ersten Aufruf alloziert und wieder freigeben nachdem der Stream `in` vollständig gelesen wurde. |
| 48 | 48 | ||
| 49 | Mehr Arbeit ist nur noch dann notwendig, wenn eine Fehlerbehandlung zum Projekt hinzugefügt wird. Für die meisten einfachen Fälle reichen diese Schritte wahrscheinlich schon aus. | 49 | Mehr Arbeit ist nur noch dann notwendig, wenn eine Fehlerbehandlung zum Projekt hinzugefügt wird. |
| 50 | Für die meisten einfachen Fälle reichen diese Schritte wahrscheinlich schon aus. | ||
| 50 | 51 | ||
| 51 | ## Motivation | 52 | ## Motivation |
| 52 | 53 | ||
| @@ -58,7 +59,7 @@ Die meisten anderen CSV-Bibliotheken passen nicht: | |||
| 58 | - fehlerhafte Verarbeitung von "problematischen" CSV-Dateien (Zeilenumbrüche innerhalb einer Zelle) | 59 | - fehlerhafte Verarbeitung von "problematischen" CSV-Dateien (Zeilenumbrüche innerhalb einer Zelle) |
| 59 | - unvorteilhafte Lizenz (GPL) | 60 | - unvorteilhafte Lizenz (GPL) |
| 60 | - umständliche API (Callbacks zum Verarbeiten der Inhalte, seltsames Handling am Dateiende) | 61 | - umständliche API (Callbacks zum Verarbeiten der Inhalte, seltsames Handling am Dateiende) |
| 61 | - globale Variablen, die den Einsatz in Multi-Threadumgebungen unmöglich machen | 62 | - globale Variablen, die den Einsatz in Multithread-Umgebungen unmöglich machen |
| 62 | - C++ wenn ich C brauche | 63 | - C++ wenn ich C brauche |
| 63 | - Java/Python/Rust/Ruby/${YourLanguageHere} wenn ich C brauche | 64 | - Java/Python/Rust/Ruby/${YourLanguageHere} wenn ich C brauche |
| 64 | - Langsam, Aufgebläht | 65 | - Langsam, Aufgebläht |
| @@ -105,7 +106,9 @@ csv_cleanup(&csv); | |||
| 105 | 106 | ||
| 106 | Dieser Code erzeugt ein neues `csv_t`-Objekt im Speicher welches anschließend durch `csv_init()` mit Standardwerten initialisiert wird. | 107 | Dieser Code erzeugt ein neues `csv_t`-Objekt im Speicher welches anschließend durch `csv_init()` mit Standardwerten initialisiert wird. |
| 107 | 108 | ||
| 108 | Dann wird der Datenstrom `in` zeilenweise in den Speicher eingelesen und innerhalb der Schleife verarbeitet. Die Anzahl der Felder pro Zeile wird mit Hilfe der Funktion `csv_nfields()` ermittelt. Alternativ liefert auch der Returnwert von `csv_read()` die Feldanzahl: | 109 | Dann wird der Datenstrom `in` zeilenweise in den Speicher eingelesen und innerhalb der Schleife verarbeitet. |
| 110 | Die Anzahl der Felder pro Zeile wird mit Hilfe der Funktion `csv_nfields()` ermittelt. | ||
| 111 | Alternativ liefert auch der Rückgabewert von `csv_read()` die Feldanzahl: | ||
| 109 | 112 | ||
| 110 | ~~~c | 113 | ~~~c |
| 111 | // ... | 114 | // ... |
| @@ -120,13 +123,16 @@ while ( (nf = csv_read(&csv, in)) != 0 ) { | |||
| 120 | Der Rückgabewert **0** bei einem Aufruf von `csv_read()` kennzeichnet das Ende des Datenstroms. | 123 | Der Rückgabewert **0** bei einem Aufruf von `csv_read()` kennzeichnet das Ende des Datenstroms. |
| 121 | 124 | ||
| 122 | > **Hinweis:** | 125 | > **Hinweis:** |
| 123 | > Das Ende einer CSV-Datei mit 0-gelesenen Feldern zu kennzeichnen ist vollkommen legitim. Eine "leere" Zeile innerhalb der Datei würde nämlich als genau ein (leeres) Feld interpretiert, so dass der Rückgabewert hier **1** wäre. Anders gesagt: Es gibt in CSV-Dateien keine Zeilen mit 0 Feldern. | 126 | > Das Ende einer CSV-Datei mit 0-gelesenen Feldern zu kennzeichnen ist vollkommen legitim. |
| 127 | > Eine "leere" Zeile innerhalb der Datei würde nämlich als genau ein (leeres) Feld interpretiert, so dass der Rückgabewert hier **1** wäre. | ||
| 128 | > Anders gesagt: Es gibt in CSV-Dateien keine Zeilen mit 0 Feldern. | ||
| 124 | 129 | ||
| 125 | 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. | 130 | 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. |
| 126 | 131 | ||
| 127 | **2\. Optionen einstellen** | 132 | **2\. Optionen einstellen** |
| 128 | 133 | ||
| 129 | 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 (`;`) zum Trennen der einzelnen Felder. | 134 | Wenn nichts weiter angegeben ist, gehen wir von einem Komma (`,`) als Feldtrenner sowie doppelten Anführungszeichen (`"`) als Feldbegrenzer aus. |
| 135 | Die meisten CSV-Dateien im deutschsprachigen Raum verwenden jedoch ein Semikolon (`;`) zum Trennen der einzelnen Felder. | ||
| 130 | 136 | ||
| 131 | Wir berücksichtigen dies bei der Initialisierung eines `csv_options_t`-Objektes. | 137 | Wir berücksichtigen dies bei der Initialisierung eines `csv_options_t`-Objektes. |
| 132 | 138 | ||
| @@ -139,21 +145,24 @@ csv_t csv; | |||
| 139 | csv_init_opt(&csv, &csv_options); | 145 | csv_init_opt(&csv, &csv_options); |
| 140 | ~~~ | 146 | ~~~ |
| 141 | 147 | ||
| 142 | Damit wir nicht alle Felder in `csv_options` ändern müssen, initialisieren wir dieses zunächst mit `csv_default_options` und passen dann `field_delimiter` und `field_separator` für unsere Bedürfnisse an. Das `csv`-Objekt wird nun mit der Funktion `csv_init_opt()` initialisiert, die einen zusätzlichen Zeiger auf ein `csv_options_t`-Objekt erwartet. | 148 | Damit wir nicht alle Felder in `csv_options` ändern müssen, initialisieren wir dieses zunächst mit den Standardwerten aus `csv_default_options` und passen dann `field_delimiter` und `field_separator` für unsere Bedürfnisse an. |
| 149 | Das `csv`-Objekt wird nun mit der Funktion `csv_init_opt()` initialisiert, die einen zusätzlichen Zeiger auf ein `csv_options_t`-Objekt erwartet. | ||
| 143 | 150 | ||
| 144 | > **Hinweis:** | 151 | > **Hinweis:** |
| 145 | > `csv_init(&csv);` ist letztlich nur eine kürzere Schreibweise für `csv_init_opt(&csv, &csv_default_options);`. | 152 | > `csv_init(&csv);` ist letztlich nur eine kürzere Schreibweise für `csv_init_opt(&csv, &csv_default_options);`. |
| 146 | 153 | ||
| 147 | > **Hinweis:** | 154 | > **Hinweis:** |
| 148 | > Die Standardwerte für Feldtrenner und Feldbegrenzer lassen sich als Übersetzungsoptionen beim Compilieren über die Makros `CSV_DEFAULT_SEPARATOR` und `CSV_DEFAULT_DELIMITER` setzen. | 155 | > Die Standardwerte für Feldtrenner und Feldbegrenzer lassen sich als Übersetzungsoptionen beim Compilieren über die Makros `CSV_DEFAULT_SEPARATOR` und `CSV_DEFAULT_DELIMITER` setzen. |
| 149 | 156 | ||
| 150 | Der Rest der Schleife bleibt gleich. | 157 | Der Rest der Schleife bleibt gleich. |
| 151 | 158 | ||
| 152 | **3\. Fehlerbehandlung hinzufügen** | 159 | **3\. Fehlerbehandlung hinzufügen** |
| 153 | 160 | ||
| 154 | Bei der Verarbeitung können verschiedene Fehler auftreten, die ein robustes Programm selbstverständlich behandelt. Leider stellt die Programmiersprache C keinen Exception-Mechanismus zur Verfügung, so dass sich der Programmierer an dieser Stelle anders behelfen muss. | 161 | Bei der Verarbeitung können verschiedene Fehler auftreten, die ein robustes Programm selbstverständlich behandelt. |
| 162 | Leider stellt die Programmiersprache C keinen Exception-Mechanismus zur Verfügung, so dass sich der Programmierer an dieser Stelle anders behelfen muss. | ||
| 155 | 163 | ||
| 156 | 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: | 164 | Die beste Variante, die den klassischen Exceptions am nächsten kommt, sind die beiden C-Funktionen `setjmp()` und `longjmp()`, mit deren Hilfe wir nun die Fehlerbehandlung umsetzen. |
| 165 | Wir benötigen dafür die beiden Felder `cb_error` und `cb_error_arg` des `csv_options`-Objektes: | ||
| 157 | 166 | ||
| 158 | ~~~c | 167 | ~~~c |
| 159 | jmp_buf env; | 168 | jmp_buf env; |
| @@ -161,18 +170,20 @@ csv_options.cb_error = csvErrorHandler; | |||
| 161 | csv_options.cb_error_arg = &env; | 170 | csv_options.cb_error_arg = &env; |
| 162 | ~~~ | 171 | ~~~ |
| 163 | 172 | ||
| 164 | `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: | 173 | `cb_error` ist ein Zeiger auf eine Callback-Funktion, die im Falle eines Fehlers aus der CSV-Bibliothek heraus aufgerufen wird. |
| 174 | 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: | ||
| 165 | 175 | ||
| 166 | ~~~c | 176 | ~~~c |
| 167 | static void | 177 | static void |
| 168 | csvErrorHandler(csv_err_t err, void *cb_arg) | 178 | csvErrorHandler(csv_err_t err, void *cb_error_arg) |
| 169 | { | 179 | { |
| 170 | fprintf(stderr, "error while csv-processing: %s\n", csv_err_str(err)); | 180 | fprintf(stderr, "error while csv-processing: %s\n", csv_err_str(err)); |
| 171 | longjmp(*((jmp_buf *) cb_arg), err); | 181 | longjmp(*((jmp_buf *) cb_error_arg), err); |
| 172 | } | 182 | } |
| 173 | ~~~ | 183 | ~~~ |
| 174 | 184 | ||
| 175 | 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. | 185 | Mit Hilfe des Parameter `err` wird eine einfache Fehlermeldung protokolliert. |
| 186 | Anschließend rufen wir `longjmp()` auf, damit die Fehlerbehandlung auf höherer Ebene fortgesetzt wird. | ||
| 176 | 187 | ||
| 177 | Die Hauptschleife in unserer Funktion sieht nun so aus: | 188 | Die Hauptschleife in unserer Funktion sieht nun so aus: |
| 178 | 189 | ||
| @@ -216,7 +227,7 @@ case CSV_ERR_IO_READ: | |||
| 216 | 227 | ||
| 217 | ### Typen | 228 | ### Typen |
| 218 | 229 | ||
| 219 | **Fehlercodes** für die Callbackhandler: | 230 | **Fehlercodes** für die Callback-Handler: |
| 220 | 231 | ||
| 221 | ~~~c | 232 | ~~~c |
| 222 | typedef enum { | 233 | typedef enum { |
| @@ -247,7 +258,7 @@ typedef struct { | |||
| 247 | extern const csv_options_t csv_default_options; | 258 | extern const csv_options_t csv_default_options; |
| 248 | ~~~ | 259 | ~~~ |
| 249 | 260 | ||
| 250 | **Contextobjekt** zur Aufnahme der aktuell gelesenen Zeile: | 261 | **Context Objekt** zur Aufnahme der aktuell gelesenen Zeile: |
| 251 | 262 | ||
| 252 | ~~~c | 263 | ~~~c |
| 253 | typedef struct { | 264 | typedef struct { |
| @@ -284,9 +295,10 @@ void csv_cleanup(csv_t *csv); | |||
| 284 | Gibt den belegten Speicher eines `csv_t`-Objektes wieder frei. | 295 | Gibt den belegten Speicher eines `csv_t`-Objektes wieder frei. |
| 285 | 296 | ||
| 286 | Parameter | 297 | Parameter |
| 287 | : **`csv`** ein Zeiger initialisiertes `csv_t`-Objekt. | 298 | : **`csv`** ein Zeiger auf ein initialisiertes `csv_t`-Objekt. |
| 288 | 299 | ||
| 289 | `csv_cleanup()` gibt den verwendeten Speicher des csv-Objektes wieder frei. Ein direkter Aufruf dieser Funktion ist nur im Fehlerfall notwendig weil dann die automatische Speicherfreigabe der Bibliothek nicht greift. | 300 | `csv_cleanup()` gibt den verwendeten Speicher des csv-Objektes wieder frei. |
| 301 | Ein direkter Aufruf dieser Funktion ist nur im Fehlerfall notwendig weil dann die automatische Speicherfreigabe der Bibliothek nicht greift. | ||
| 290 | 302 | ||
| 291 | **Zeilenweises Lesen** | 303 | **Zeilenweises Lesen** |
| 292 | 304 | ||
| @@ -297,7 +309,7 @@ size_t csv_read(csv_t *csv, FILE *in); | |||
| 297 | Liest einen Datensatz aus dem FILE-Stream, speichert das Ergebnis in `csv` und liefert die Anzahl der gefundenen Felder zurück. | 309 | Liest einen Datensatz aus dem FILE-Stream, speichert das Ergebnis in `csv` und liefert die Anzahl der gefundenen Felder zurück. |
| 298 | 310 | ||
| 299 | Parameter | 311 | Parameter |
| 300 | : **`csv`** ein Zeiger initialisiertes `csv_t`-Objekt. | 312 | : **`csv`** ein Zeiger auf ein initialisiertes `csv_t`-Objekt. |
| 301 | : **`in`** ein Zeiger auf einen geöffneten C-FILE-Stream. | 313 | : **`in`** ein Zeiger auf einen geöffneten C-FILE-Stream. |
| 302 | 314 | ||
| 303 | Rückgabe | 315 | Rückgabe |
| @@ -313,7 +325,7 @@ const char *csv_field(const csv_t *const csv, size_t idx); | |||
| 313 | Die beiden Funktionen ermöglichen einen Zugriff auf die einzelnen Felder des zuletzt gelesenen Datensatzes. | 325 | Die beiden Funktionen ermöglichen einen Zugriff auf die einzelnen Felder des zuletzt gelesenen Datensatzes. |
| 314 | 326 | ||
| 315 | Parameter | 327 | Parameter |
| 316 | : **`csv`** ein Zeiger initialisiertes `csv_t`-Objekt. | 328 | : **`csv`** ein Zeiger auf ein initialisiertes `csv_t`-Objekt. |
| 317 | : **`idx`** Index des zu lesenden Feldes. (0 <= idx < `csv_nfields(&csv)`) | 329 | : **`idx`** Index des zu lesenden Feldes. (0 <= idx < `csv_nfields(&csv)`) |
| 318 | 330 | ||
| 319 | Rückgabe | 331 | Rückgabe |
| @@ -342,11 +354,10 @@ Die CSV-Bibliothek kann an verschiedene Bedürfnisse angepasst werden. | |||
| 342 | 354 | ||
| 343 | 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. | 355 | 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. |
| 344 | 356 | ||
| 345 | Option Beschreibung Standardwert | 357 | | Option | Beschreibung | Standardwert | |
| 346 | -------------------------- -------------- --------------------------------- | 358 | | -------------------------- | -------------- | -------------| |
| 347 | `CSV_DEFAULT_DELIMITER` Feldbegrenzer `"` | 359 | | `CSV_DEFAULT_DELIMITER` | Feldbegrenzer | `"` | |
| 348 | `CSV_DEFAULT_SEPARATOR` Feldtrenner `,` | 360 | | `CSV_DEFAULT_SEPARATOR` | Feldtrenner | `,` | |
| 349 | -------------------------- -------------- --------------------------------- | ||
| 350 | Table: Makros | 361 | Table: Makros |
| 351 | 362 | ||
| 352 | Soll beispielsweise das in Deutschland viel häufiger vorkommende Semikolon (`;`) statt einem Komma (`,`) standardmäßig als Trennsymbol zwischen den Feldern einer CSV-Datei benutzt werden sieht der Aufruf zum Compilieren so aus: | 363 | Soll beispielsweise das in Deutschland viel häufiger vorkommende Semikolon (`;`) statt einem Komma (`,`) standardmäßig als Trennsymbol zwischen den Feldern einer CSV-Datei benutzt werden sieht der Aufruf zum Compilieren so aus: |
| @@ -365,9 +376,11 @@ TODO! | |||
| 365 | 376 | ||
| 366 | ## Lizenz | 377 | ## Lizenz |
| 367 | 378 | ||
| 368 | 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. | 379 | In den letzten Jahrzehnten habe ich massiv vom Internet und seinen Inhalten profitiert. |
| 380 | 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. | ||
| 369 | 381 | ||
| 370 | Aus diesem Grund stelle ich meine Software (sofern nicht anders angegeben) unter der freizügigen [Do What the Fuck You Want to Public License](http://www.wtfpl.net) zur Verfügung. Vielleicht findet jemand den Code nützlich oder kann Erkenntnisse daraus gewinnen. | 382 | Aus diesem Grund stelle ich meine Software (sofern nicht anders angegeben) unter der freizügigen [Do What the Fuck You Want to Public License](http://www.wtfpl.net) zur Verfügung. |
| 383 | Vielleicht findet jemand den Code nützlich oder kann Erkenntnisse daraus gewinnen. | ||
| 371 | 384 | ||
| 372 | ~~~ | 385 | ~~~ |
| 373 | Copyright © 2020 Thomas Schmucker | 386 | Copyright © 2020 Thomas Schmucker |
