From 8b29decb942b2e598040d0fd8d01892796846833 Mon Sep 17 00:00:00 2001 From: Thomas Schmucker Date: Sat, 29 Aug 2020 09:37:29 +0200 Subject: Aktualisiere Dokumentation --- doc/csv.md | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++------------ 1 file changed, 76 insertions(+), 18 deletions(-) (limited to 'doc') diff --git a/doc/csv.md b/doc/csv.md index ea775aa..ccbeba8 100644 --- a/doc/csv.md +++ b/doc/csv.md @@ -13,27 +13,27 @@ Kurz: suckless! ## Schnellstart für Ungeduldige -1\. Füge die beiden Dateien [csv.c](csv.c) und [csv.h](csv.h) Deinem C/C++ Projekt hinzu. +**1\. Füge die beiden Dateien [csv.c](csv.c) und [csv.h](csv.h) Deinem C/C++ Projekt hinzu.** -2\. Inkludiere die Headerdatei `csv.h`. +**2\. Inkludiere die Headerdatei `csv.h`:** ~~~ #include "csv.h" ~~~ -3\. Erstelle und initialisiere ein CSV-Objekt. +**3\. Erstelle und initialisiere ein CSV-Objekt:** ~~~ csv_t csv = { 0 }; oder csv_t csv; csv_init(&csv); ~~~ -4\. Verarbeite die CSV-Datei +**4\. Verarbeite die CSV-Datei:** ~~~ for ( size_t nf; (nf = csv_read(&csv, in)) != 0; ) { /* eine Zeile aus `in` lesen */ - for ( size_t i = 0; i != nf; i++ ) { /* `nf` beinhaltet die Feldanzahl */ - const char *s = csv_field(&csv, i); /* Feld `i` der gelesenen Zeile ansprechen */ + for ( size_t i = 0; i != nf; i++ ) { /* `nf` beinhaltet die Feldanzahl */ + const char *s = csv_field(&csv, i); /* Feld `i` der gelesenen Zeile ansprechen */ /* ... */ } } @@ -45,15 +45,16 @@ Es sind nur zwei Funktionen notwendig, um die Daten des FILE-Streams `in` vollst 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. +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. + ## Motivation Die meisten anderen CSV-Bibliotheken passen nicht: - falsche Verarbeitung bei eingeklammerten Feldern: C++: `getline()` -> `stringstream` -> `getline()`; - C: `fgets()` -> `strtok`() - Irgendwelche regulären Ausdrücke -- fehlerhafte Verarbeitung von "problematischen" CSV-Dateien (Linebreaks innerhalb einer Zelle) + C: `fgets()` -> `strtok()` +- fehlerhafte Verarbeitung von "problematischen" CSV-Dateien (Zeilenumbrüche innerhalb einer Zelle) - unvorteilhafte Lizenz (GPL) - umständliche API (Callbacks zum Verarbeiten der Inhalte, seltsames Handling am Dateiende) - globale Variablen, die den Einsatz in Multi-Threadumgebungen unmöglich machen @@ -62,7 +63,7 @@ Die meisten anderen CSV-Bibliotheken passen nicht: - Langsam, Aufgebläht - schlecht oder gar nicht anpassbar -=> Selber schreiben! +Lösung: => Selber schreiben! ## Tutorial @@ -81,7 +82,7 @@ processFile(const char *filename) return false; } - // [ HIERHER KOMMT DER CSV-HANDLING CODE ] + // HIERHER KOMMT DER CSV-HANDLING CODE fclose(in); return true; @@ -103,13 +104,27 @@ csv_cleanup(&csv); Dieser Code erzeugt ein neues `csv_t`-Objekt im Speicher welches anschließend mit `csv_init()` mit Standardwerten initialisiert wird. -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. +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: + +~~~ +// ... +size_t nf; +while ( (nf = csv_read(&csv, in)) != 0 ) { + // ... +} +// ... +~~~ + +Der Rückgabewert 0 bei einem Aufruf von `csv_read()` kennzeichnet das Ende des Datenstroms. + +> **Hinweis:** +> 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. 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. **2\. Optionen einstellen** -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. +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. Wir berücksichtigen dies bei der Initialisierung eines `csv_options_t`-Objektes. @@ -122,7 +137,7 @@ csv_t csv; csv_init_opt(&csv, &csv_options); ~~~ -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`. +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` 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. > **Hinweis:** > `csv_init(&csv);` ist letztlich nur eine kürzere Schreibweise für `csv_init_opt(&csv, &csv_default_options);`. @@ -131,8 +146,7 @@ Der Rest der Schleife bleibt gleich. **3\. Fehlerbehandlung hinzufügen** -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. +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. 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: @@ -246,18 +260,44 @@ void csv_init(csv_t *csv); void csv_init_opt(csv_t *csv, const csv_options_t *const csv_options); ~~~ +Initialisiert ein `csv_t`-Objekt. + +Parameter +: **`csv`** ein Zeiger vom Typ `csv_t`. +: **`csv_options`** ein Zeiger vom Typ `csv_options_t` + +`csv_init(&c);` ist eine kürzere Schreibweise für `csv_init_opt(&c, &csv_csv_default_options);` + +Mit der Funktion `csv_init_opt()` und einem angepassten `csv_options_t`-Objektes kann das Verhalten des CSV-Parsers zur Laufzeit parametrisiert werden. + **Manuelles Freigeben** ~~~ void csv_cleanup(csv_t *csv); ~~~ +Gibt den belegten Speicher eines `csv_t`-Objektes wieder frei. + +Parameter +: **`csv`** ein Zeiger initialisiertes `csv_t`-Objekt. + +`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. + **Zeilenweises Lesen** ~~~ size_t csv_read(csv_t *csv, FILE *in); ~~~ +Liest einen Datensatz aus dem FILE-Stream, speichert das Ergebnis in `csv` und liefert die Anzahl der gefundenen Felder zurück. + +Parameter +: **`csv`** ein Zeiger initialisiertes `csv_t`-Objekt. +: **`in`** ein Zeiger auf einen geöffneten C-FILE-Stream. + +Rückgabe +: Anzahl der gelesenen CSV-Felder einer Zeile oder 0 bei Dateiende. + **Feldzugriff** ~~~ @@ -265,19 +305,37 @@ size_t csv_nfields(const csv_t *const csv); const char *csv_field(const csv_t *const csv, size_t idx); ~~~ +Die beiden Funktionen ermöglichen einen Zugriff auf die einzelnen Felder des zuletzt gelesenen Datensatzes. + +Parameter +: **`csv`** ein Zeiger initialisiertes `csv_t`-Objekt. +: **`idx`** Index des zu lesenden Feldes. (0 <= idx < `csv_nfields(&csv)`) + +Rückgabe +: `csv_nfields()` liefert die Anzahl der Felder des zuletzt gelesenen Datensatzes. +: `csv_field()` liefert einen Zeiger auf das Feld `idx` des zuletzt gelesenen Datensatzes. + **Fehlercodes als Textmeldung** ~~~ const char *csv_err_str(csv_err_t csv_err); ~~~ +Liefere eine textuelle Meldung zum Fehlercode `csv_err`. + +Parameter +: **`csv_err`** ein Fehlercode vom Typ `csv_err_t`. + +Rückgabe +: Zeiger auf die entspreche Fehlermeldung. + ## Anpassungen Die CSV-Bibliothek kann an verschiedene Bedürfnisse angepasst werden. ### Übersetzungsoptionen -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. +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. Option Beschreibung Standardwert -------------------------- -------------- --------------------------------- @@ -286,7 +344,7 @@ Option Beschreibung Standardwert -------------------------- -------------- --------------------------------- Table: Makros -Soll beispielsweise das in Deutschland viel häufiger vorkommende Semikolon (`;`) statt einem Komma (`,`) als Trennsymbol zwischen den Feldern einer CSV-Datei benutzt werden sieht der Aufruf zum Compilieren so aus: +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: ~~~ cc -DCSV_DEFAULT_SEPARATOR="';'" -o csv-german.o -c csv.c -- cgit v1.3