blob: 4c6297b0e8b000669f5ae749effc22d4ca2223a0 (
plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
|
(in-package #:cepis)
(defun parse-csv (text)
"Parse TEXT as RFC 4180 CSV and return a list of rows, each a list of strings.
Values may be double-quoted, in which case they may contain commas, newlines
and doubled quotes; that is why the batch endpoint cannot be read by splitting
on commas. A byte order mark and CRLF line endings are both tolerated."
(let ((rows '())
(row '())
(buffer (make-string-output-stream))
(in-quotes nil)
(dirty nil)
(index 0)
(end (length text)))
(labels ((flush-field ()
(push (get-output-stream-string buffer) row))
(flush-row ()
(flush-field)
(push (nreverse row) rows)
(setf row '()
dirty nil)))
(when (and (plusp end) (char= (char text 0) (code-char #xFEFF)))
(incf index))
(loop while (< index end)
do (let ((character (char text index)))
(cond (in-quotes
(cond ((not (char= character #\"))
(write-char character buffer))
((and (< (1+ index) end) (char= (char text (1+ index)) #\"))
(write-char #\" buffer)
(incf index))
(t (setf in-quotes nil))))
((char= character #\")
(setf in-quotes t
dirty t))
((char= character #\,)
(flush-field)
(setf dirty t))
((char= character #\Newline)
(flush-row))
((char= character #\Return))
(t
(write-char character buffer)
(setf dirty t))))
(incf index))
(when (or dirty row)
(flush-row))
(nreverse rows))))
(defun csv-row-table (header row)
"Zip HEADER and ROW into a hash table, so a CSV row reads like a JSON object.
Columns the row does not reach are filled with the empty string, which is what
the API sends for a field it does not have."
(let ((table (make-hash-table :test #'equal)))
(loop for column in header
for rest = row then (cdr rest)
do (setf (gethash column table) (or (car rest) "")))
table))
|