# (chibi csv)

## CSV Grammars

CSV is a simple and compact format for tabular data, which has made it popular for a variety of tasks since the early days of computing. Unfortunately, there are many incompatible dialects requiring a grammar to specify all of the different options.

#### `(csv-grammar spec)`

Creates a new CSV grammar from the given spec, an alist of symbols to values. The following options are supported:

- `'separator-chars` - A non-empty list of characters used to delimit fields, by default `'(#\,)` (comma-separated).
- `'quote-char` - A single character used to quote fields containing special characters, or `#f` to disable quoting, by default `#\"` (a double-quote).
- `'quote-doubling-escapes?` - If true, two successive `quote-char`s within quotes are treated as a single escaped `quote-char` (default true).
- `'escape-char` - A single character used to escape characters within quoted fields, or `#f` to disable escapes, by default `#f` (no explicit escape, use quote doubling).
- `'record-separator` - A single character used to delimit the record (row), or one of the symbols `'cr`, `'crlf`, `'lf` or `'lax`. These correspond to sequences of carriage return and line feed, or in the case of `'lax` any of the other three sequences. Defaults to `'lax`.
- `'comment-chars` - A list of characters which if found at the start of a record indicate it is a comment, discarding all characters through to the next record-separator. Defaults to the empty list (no comments).

Example Gecos grammar:

    (csv-grammar?
      (csv-grammar
        '((separator-chars #\:)
          (quote-char . #f))))

`=> #t`

#### `default-csv-grammar`

The default CSV grammar for convenience, with all of the defaults from `csv-grammar`, i.e. comma-delimited with `#} for quoting, doubled to escape.`

#### `default-tsv-grammar`

The default TSV grammar for convenience, splitting fields only on tabs, with no quoting or escaping.

## CSV Parsers

#### `csv-parser`

Parsers are low-level utilities to perform operations on records a field at a time. You generally want to work with readers, which build on this to build records into familiar data structures. Parsers follow the rules of a grammar to parse a single CSV record, possible comprised of multiple fields. A parser is a procedure of three arguments which performs a fold operation over the fields of the record. The parser signature is: `(parser kons knil in)`, where `kons` itself is a procedure of three arguments: `(proc acc index field)`. `proc` is called on each field of the record, in order, along with its zero-based `index` and the accumulated result of the last call, starting with `knil`. Returns a new CSV parser for the given `grammar`. The parser by itself can be used to parse a record at a time.

    (let ((parse (csv-parser)))
      (parse (lambda (vec i field) (vector-set! vec i (string->number field)) vec)
             (make-vector 3)
             (open-input-string "1,2,3")))

`=> #(1 2 3)`

#### `(csv-skip-line in grammar)`

#### `csv-num-rows`

Returns the number of rows in the input.

## CSV Readers

#### `csv-read->list`

A CSV reader reads a single record, returning some representation of it. You can either loop manually with these or pass them to one of the high-level utilities to operate on a whole CSV file at a time. The simplest reader, simply returns the field string values in order as a list.

    ((csv-read->list) (open-input-string "foo,bar,baz"))

`=> ("foo" "bar" "baz")`

#### `csv-read->vector`

The equivalent of `csv-read->list` but returns a vector.

    ((csv-read->vector) (open-input-string "foo,bar,baz"))

`=> #("foo" "bar" "baz")`

#### `csv-read->fixed-vector`

The same as `csv-read->vector` but requires the vector to be of a fixed size, and may be more efficient.

    ((csv-read->fixed-vector 3) (open-input-string "foo,bar,baz"))

`=> #("foo" "bar" "baz")`

#### `csv-read->sxml`

Returns an SXML representation of the record, as a row with multiple named columns.

    ((csv-read->sxml 'city '(name latitude longitude))
     (open-input-string "Tokyo,35°41′23″N,139°41′32″E"))

`=> (city (name "Tokyo") (latitude "35°41′23″N") (longitude "139°41′32″E"))`

## CSV Utilities

#### `csv-fold`

A folding operation on records. `proc` is called successively on each row and the accumulated result.

    (csv-fold
     (lambda (row acc) (cons (cadr (assq 'name (cdr row))) acc))
     '()
     (csv-read->sxml 'city '(name latitude longitude))
     (open-input-string
      "Tokyo,35°41′23″N,139°41′32″E
    Paris,48°51′24″N,2°21′03″E"))

`=> ("Paris" "Tokyo")`

#### `csv-for-each`

An iterator which simply calls `proc` on each record in the input in order.

    (let ((count 0))
      (csv-for-each
       (lambda (row) (if (string->number (car row)) (set! count (+ 1 count))))
       (csv-read->list)
       (open-input-string
         "1,foo\n2,bar\nthree,baz\n4,qux"))
       count)

`=> 3`

#### `csv-map`

Returns a list containing the result of calling `proc` on each element in the input.

    (csv-map
     (lambda (row) (string->symbol (cadr row)))
     (csv-read->list)
     (open-input-string
       "1,foo\n2,bar\nthree,baz\n4,qux"))

`=> (foo bar baz qux)`

#### `csv->list`

Returns a list of all of the read records in the input.

    (csv->list
     (csv-read->list)
     (open-input-string
       "1,foo\n2,bar\nthree,baz\n4,qux"))

`=> (("1" "foo") ("2" "bar") ("three" "baz") ("4" "qux"))`

#### `csv->sxml`

Returns an SXML representation of the CSV.

    ((csv->sxml 'city '(name latitude longitude))
     (open-input-string
      "Tokyo,35°41′23″N,139°41′32″E
    Paris,48°51′24″N,2°21′03″E"))

`=> (*TOP* (city (name "Tokyo") (latitude "35°41′23″N") (longitude "139°41′32″E")) (city (name "Paris") (latitude "48°51′24″N") (longitude "2°21′03″E")))`

## CSV Writers

#### `csv-writer`

#### `csv-write`