sourcelibs/Csv/src/Csv.xtl
1⍝# Csv: comma-separated values -- lines to fields (quoted fields kept
2⍝# whole), a table of texts, columns by number or name, numbers, and
3⍝# back to text.
4⍝# Import it with an alias of your choice: "cs:" u_se< "Csv".
5⍝# Put libs/Csv/src on XETAL_PATH ("just path"); the reference is libs/Csv/docs.
6⍝# Names with l: are exported; those under h: are private to this file.
7⍝#
8⍝# A table is a matrix of texts (Box Char), a row per line, its first
9⍝# row the header. Fields follow RFC 4180: a field in double quotes may
10⍝# hold commas, and a doubled quote inside stands for one.
11
12ᵗ⁼u̲se< "Strings"
13"q:" u̲se< "Lists"
14
15dq ← "\"" ⍝ a double quote, as text
16
17⍝## Parsing
18
19⍝# A field as written to its value: outer quotes off, "" to ".
20ʰu̲nquote ← { f →
21 2 > t̲ally f ? f
22 n̲ot (dq m̲atch 1 t̲ake f) ∧ dq m̲atch -1 t̲ake f ? f
23 ((e̲nclose dq c̲at dq) c̲at e̲nclose dq) ᵗr̲eplace 1 d̲rop -1 d̲rop f
24}
25
26⍝# f_ields line: the fields of one line. A comma separates fields
27⍝# unless an odd number of quotes comes before it (it is inside a
28⍝# quoted field).
29ˡf̲ields ← { line →
30 inside ← 1 = ('+ s̲\ 0 + line = f̲irst dq) m̲od 2
31 cut ← w̲here (line = f̲irst ",") ∧ n̲ot inside
32 starts ← 1 c̲at cut + 1
33 ends ← cut c̲at 1 + t̲ally line
34 '{ i → ʰu̲nquote ((f̲irst i s̲elect ends) − f̲irst i s̲elect starts) t̲ake ((f̲irst i s̲elect starts) − 1) d̲rop line } m̲ap r̲ange t̲ally starts
35}
36
37⍝# r_ows text: the table of a whole text, a row per line (every line
38⍝# has the same number of fields).
39ˡr̲ows ← { text →
40 lines ← ᵗl̲ines text
41 rows ← '{ l → ˡf̲ields d̲isclose l } m̲ap lines
42 ((t̲ally lines) c̲at t̲ally d̲isclose f̲irst rows) r̲eshape q:r̲aze rows
43}
44
45⍝## Columns
46
47⍝# n c_olumn table: the texts of column n, the header left out.
48ˡc̲olumn ← { n table → 1 d̲rop n s̲elect₂ table }
49
50⍝# name f_ield table: the texts of the column whose header is name.
51ˡf̲ield ← { name table → (f̲irst (f̲irst table) i̲ndexOf e̲nclose name) ˡc̲olumn table }
52
53⍝# name n_umbers table: the column whose header is name, as numbers.
54ˡn̲umbers ← { name table → n̲umbers " " ᵗj̲oin name ˡf̲ield table }
55
56⍝## Writing
57
58⍝# A value as a field: quoted (quotes doubled) when it holds a comma, a
59⍝# quote or a newline.
60ʰq̲uote ← { v →
61 n̲ot '∨ r̲/ v m̲ember? ",\n" c̲at dq ? v
62 dq c̲at (((e̲nclose dq) c̲at e̲nclose dq c̲at dq) ᵗr̲eplace v) c̲at dq
63}
64
65⍝# t_ext table: the table as comma-separated text, a line per row.
66ˡt̲ext ← { table →
67 "\n" ᵗj̲oin '{ i → "," ᵗj̲oin '{ f → ʰq̲uote d̲isclose f } m̲ap i s̲elect table } m̲ap r̲ange t̲ally table
68}