From 0ee2ec205c32de9b2563cc4175d1f6d3b65eb220 Mon Sep 17 00:00:00 2001 From: Thomas Schmucker Date: Fri, 21 Aug 2020 13:37:11 +0200 Subject: fix(doku): Dokumentation überarbeitet und deutlich ausgebaut. MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 1 + csv-tutorial.c | 118 +++++++++++++++++++++++++++++++++++++++++++++++ doc/csv.md | 142 +++++++++++++++++++++++++++++++++++++++++++++++++++------ makefile | 7 ++- 4 files changed, 251 insertions(+), 17 deletions(-) create mode 100644 csv-tutorial.c diff --git a/.gitignore b/.gitignore index 6b6b758..287f41c 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ csv-test csv-perf seq-read +csv-tutorial pop-csv.c 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 @@ +#include +#include +#include +#include + +#include "csv.h" + +bool +processFile(const char *filename) +{ + FILE *in; + + if ( (in = fopen(filename, "r")) == NULL ) { + return false; + } + + csv_t csv; + csv_init(&csv); + + while ( csv_read(&csv, in) ) { + const size_t nf = csv_nfields(&csv); + (void) nf; + // ... + } + csv_cleanup(&csv); + + fclose(in); + return true; +} + +bool +processFile_options(const char *filename) +{ + FILE *in; + + if ( (in = fopen(filename, "r")) == NULL ) { + return false; + } + + csv_options_t csv_options = csv_default_options; + csv_options.field_delimiter = '"'; + csv_options.field_separator = ','; + + csv_t csv; + csv_init_opt(&csv, &csv_options); + + while ( csv_read(&csv, in) ) { + const size_t nf = csv_nfields(&csv); + (void) nf; + // ... + } + csv_cleanup(&csv); + + fclose(in); + return true; +} + +static void +csvErrorHandler(csv_err_t err, void *cb_arg) +{ + longjmp(*((jmp_buf *) cb_arg), err); +} + +bool +processFile_error(const char *filename) +{ + FILE *in; + + if ( (in = fopen(filename, "r")) == NULL ) { + return false; + } + + csv_options_t csv_options = csv_default_options; + csv_options.field_delimiter = '"'; + csv_options.field_separator = ','; + + jmp_buf env; + csv_options.cb_error = csvErrorHandler; + csv_options.cb_error_arg = &env; + + csv_t csv; + csv_init_opt(&csv, &csv_options); + + switch ( setjmp(env) ) { + case CSV_ERR_OK: + while ( csv_read(&csv, in) ) { + const size_t nf = csv_nfields(&csv); + (void) nf; + // ... + } + break; + + case CSV_ERR_OUT_OF_MEMORY: + csv_cleanup(&csv); + break; + + case CSV_ERR_OUT_OF_RANGE: + csv_cleanup(&csv); + break; + + case CSV_ERR_IO_READ: + csv_cleanup(&csv); + break; + + case CSV_ERR_IO_WRITE: + csv_cleanup(&csv); + break; + } + + fclose(in); + return true; +} + +int +main(void) +{ + return EXIT_SUCCESS; +} diff --git a/doc/csv.md b/doc/csv.md index cfdd8ee..ea775aa 100644 --- a/doc/csv.md +++ b/doc/csv.md @@ -66,7 +66,132 @@ Die meisten anderen CSV-Bibliotheken passen nicht: ## Tutorial -to be written +**1\. Ausgangspunkt: ein Grundgerüst** + +Nehmen wir an, wir schreiben eine Funktion zur CSV-Verarbeitung. +Das Grundgerüst sieht dann etwa so aus: + +~~~ +bool +processFile(const char *filename) +{ + FILE *in; + + if ( (in = fopen(filename, "r")) == NULL ) { + return false; + } + + // [ HIERHER KOMMT DER CSV-HANDLING CODE ] + + fclose(in); + return true; +} +~~~ + +Wir fügen als erstes etwas Code hinzu: + +~~~ +csv_t csv; +csv_init(&csv); + +while ( csv_read(&csv, in) ) { + const size_t nf = csv_nfields(&csv); + // ... +} +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. + +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. + +Wir berücksichtigen dies bei der Initialisierung eines `csv_options_t`-Objektes. + +~~~ +csv_options_t csv_options = csv_default_options; +csv_options.field_delimiter = '"'; +csv_options.field_separator = ','; + +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`. + +> **Hinweis:** +> `csv_init(&csv);` ist letztlich nur eine kürzere Schreibweise für `csv_init_opt(&csv, &csv_default_options);`. + +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. + +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: + +~~~ +jmp_buf env; +csv_options.cb_error = csvErrorHandler; +csv_options.cb_error_arg = &env; +~~~ + +`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: + +~~~ +static void +csvErrorHandler(csv_err_t err, void *cb_arg) +{ + fprintf(stderr, "error while csv-processing: %s\n", csv_err_str(err)); + longjmp(*((jmp_buf *) cb_arg), err); +} +~~~ + +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. + +Die Hauptschleife in unserer Funktion sieht nun so aus: + +~~~ +csv_options_t csv_options = csv_default_options; +csv_options.field_delimiter = '"'; +csv_options.field_separator = ','; + +jmp_buf env; +csv_options.cb_error = csvErrorHandler; +csv_options.cb_error_arg = &env; + +csv_t csv; +csv_init_opt(&csv, &csv_options); + +switch ( setjmp(env) ) { +case CSV_ERR_OK: + while ( csv_read(&csv, in) ) { + const size_t nf = csv_nfields(&csv); + // ... + } + // kein csv_cleanup() notwendig weil die Datei an + // dieser Stelle vollständig verarbeitet wurde. + break; + +case CSV_ERR_OUT_OF_MEMORY: + csv_cleanup(&csv); + break; + +case CSV_ERR_OUT_OF_RANGE: + csv_cleanup(&csv); + break; + +case CSV_ERR_IO_READ: + csv_cleanup(&csv); + break; +} +~~~ ## API @@ -110,7 +235,6 @@ typedef struct { csv_options_t *csv_options; /* additional internal fields */ } csv_t; - ~~~ ### Funktionen @@ -147,10 +271,6 @@ const char *csv_field(const csv_t *const csv, size_t idx); const char *csv_err_str(csv_err_t csv_err); ~~~ -## Fehlerbehandlung - -to be written - ## Anpassungen Die CSV-Bibliothek kann an verschiedene Bedürfnisse angepasst werden. @@ -159,7 +279,7 @@ Die CSV-Bibliothek kann an verschiedene Bedürfnisse angepasst 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 +Option Beschreibung Standardwert -------------------------- -------------- --------------------------------- `CSV_DEFAULT_DELIMITER` Feldbegrenzer `"` `CSV_DEFAULT_SEPARATOR` Feldtrenner `,` @@ -176,14 +296,6 @@ Anschließend wird die Datei `csv-german.o` statt `csv.o` zum Projekt dazugelink Die Headerdatei (`csv.h`) bleibt unverändert. -### Eigener Speicherallozierer - -to be written - -## Performance - -to be written - ## Lizenz 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. diff --git a/makefile b/makefile index 5866c43..5b850c7 100644 --- a/makefile +++ b/makefile @@ -1,6 +1,6 @@ include config.mk -all: csv-test csv-perf seq-read +all: csv-test csv-perf seq-read csv-tutorial csv-test: csv-test.c csv.c csv.h $(CC) $(CFLAGS-TEST) -DNDEBUG -o $@ csv-test.c csv.c @@ -16,10 +16,13 @@ csv-perf: csv-perf.c csv.o csv.h seq-read: seq-read.c $(CC) $(CFLAGS) -o $@ $< +csv-tutorial: csv-tutorial.c csv.o csv.h + $(CC) $(CFLAGS) -o $@ csv-tutorial.c csv.o + csv.o: csv.c csv.h $(CC) $(CFLAGS) -DNDEBUG -o $@ -c $< clean: - rm csv-test csv-perf seq-read csv.o + rm -f csv-test csv-perf seq-read csv.o csv-tutorial .PHONY: all clean coverage -- cgit v1.3