chickadee » csv-abnf

csv-abnf

Description

The csv-abnf library contains procedures for parsing and formatting of comma-separated values (CSV) as described in RFC 4180. There are several differences with the RFC:

See also csv-xml and tabular, a related library for parsing and formatting comma- and delimiter-separated values as well as fixed-width columns, implemented with a different set of combinators.

Library Procedures

csv-record? Xprocedure

Returns #t if the given object is a csv-record, #f otherwise.

list->csv-record LISTprocedure

Takes in a list of values and creates a csv-record object.

csv-record->list CSV-RECORDprocedure

Returns the list of values contained in the given csv-record object.

Parsing Procedures: Preliminaries

The grammar in csv-abnf is built with abnf's abnf-lens component, which makes every rule bidirectional: the same rule produces both the parser behind make-parser below and the printer behind make-format further down (see the abnf library for more on abnf-lens, and the "Bidirectional grammar rules" section below for the rules themselves).

Parsing Procedures: csv-abnf

make-parser #!optional DELIMITERprocedure

make-parser returns a CSV parsing procedure. Optional argument DELIMITER specifies the field delimiter (comma by default). DELIMITER can be a character, or an SRFI-14 character set. The returned procedure takes a string (or, for backwards compatibility, a list of characters) and returns a list of csv-record objects, one per record:

 (<#csv-record (FIELD1 FIELD2 ...)> <#csv-record ...> ...)
(import csv-abnf)

(define parse-csv (make-parser #\|))

(parse-csv "a|b|c")

(map csv-record->list (parse-csv "a|b|c"))

 ==> (("a" "b" "c"))

Formatting procedures

make-format #!optional DELIMITERprocedure

Returns procedures for outputting individual field values, CSV records, and lists of CSV records, where each list is printed on a separate line.

Procedure FORMAT-CELL takes in a value, obtains its string representation via format, and surrounds the string with quotes, if it contains characters that need to be escaped (such as quote characters, the delimiter character, or newlines).

Procedure FORMAT-RECORD takes in a row and returns its string representation, based on the strings produced by FORMAT-CELL and the delimiter character. A row can be given either as a bare list of field values, or as a csv-record (e.g. one obtained back from make-parser) -- there is no need to wrap a plain list with list->csv-record before printing it.

Procedure FORMAT-CSV takes in a list of rows (each a bare list or a csv-record; the two can be freely mixed) and produces a string representation using FORMAT-RECORD, with a trailing CRLF after every record.

Example:

(import csv-abnf)

(define-values (fmt-cell fmt-record fmt-csv) (make-format #\;))

(fmt-cell "hello") => "hello"

;; This is quoted because it contains delimiter-characters
(fmt-cell "one;two;three") => "\"one;two;three\""

;; This is quoted because it contains quotes, which are then doubled for escaping
(fmt-cell "say \"hi\"") => "\"say \"\"hi\"\"\""

;; Rows are plain lists -- no list->csv-record wrapping needed
(fmt-record '("hi there" "let's say \"hello world\" again" "until we are bored"))
=> "hi there;\"let's say \"\"hello world\"\" again\";until we are bored"

;; And an example of how to quickly convert a list of lists
;; to a CSV string containing the entire CSV file
(fmt-csv '(("one" "two")
           ("and another \"line\"" "of csv stuff")))
=> "one;two\r\n\"and another \"\"line\"\"\";of csv stuff\r\n"

;; csv-record objects (e.g. rows just read back with make-parser) work
;; directly too, with no unwrap/rewrap step:
(fmt-csv (map list->csv-record
              '(("one" "two")
                ("and another \"line\"" "of csv stuff"))))
=> "one;two\r\n\"and another \"\"line\"\"\";of csv stuff\r\n"

Bidirectional grammar rules

The three rules below are what make-parser and make-format are each built from. Every rule pairs a parser with a printer. The rules are exported directly, in case you want to use them to embed the CSV grammar as part of a larger abnf-lens grammar.

Each rule is a bp value, used with the bp-parse and bp-print procedures. Both the bp record and these two procedures are documented in the abnf page, along with bi-iso, bi-seq, and the other combinators the rules below are built from.

bi-field DELIMITERprocedure

A single field: a string, quoted only when it needs to be, i.e. when it contains DELIMITER, a quote character, a CR, or a LF.

bi-record DELIMITERprocedure

One record: a csv-record whose fields are separated by DELIMITER, with no trailing line ending. Its printer also accepts a raw list of field values, the same as FORMAT-RECORD above.

bi-csv DELIMITERprocedure

A whole CSV file: a list of rows, each followed by a line ending. Its printer accepts a list whose elements are each either a csv-record or a raw list of field values.

(import csv-abnf abnf-lens)

;; bp-parse returns (list (list matched-value) remaining-stream);
;; (car (car ...)) unwraps it to the list of csv-records.
(car (car (bp-parse (bi-csv #\,) "a,b\r\nc,d\r\n" (lambda (s) (error "parse failed" s)))))
;; => (list (list->csv-record '("a" "b")) (list->csv-record '("c" "d")))

(bp-print (bi-csv #\,) '(("a" "b") ("c" "d")))
;; => "a,b\r\nc,d\r\n"

Repository

https://github.com/iraikov/chicken-csv-abnf

Version History

License

 Copyright 2009-2026 Ivan Raikov


 This program is free software: you can redistribute it and/or
 modify it under the terms of the GNU General Public License as
 published by the Free Software Foundation, either version 3 of the
 License, or (at your option) any later version.

 This program is distributed in the hope that it will be useful, but
 WITHOUT ANY WARRANTY; without even the implied warranty of
 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 General Public License for more details.

 A full copy of the GPL license can be found at
 <http://www.gnu.org/licenses/>.

Contents »