blob: 1a9431c494734e15d3c22d132f1140a6c37030ce (
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
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
|
/**
* A minimal RFC 4180 reader, for the `/csv` batch endpoint.
*
* Values may contain commas and are then double-quoted with `""` escaping, so
* splitting on `,` corrupts every row that carries a street name like
* `"Rua Um, Dois"`. This is here because the platform ships no CSV parser and
* the library ships no dependencies.
*
* @module
*/
/** One parsed CSV record, keyed by the header row. */
export type CsvRecord = Record<string, string>;
function parseRows(text: string): string[][] {
const rows: string[][] = [];
let row: string[] = [];
let field = "";
let quoted = false;
let started = false;
for (let i = 0; i < text.length; i += 1) {
const char = text[i];
if (quoted) {
if (char === '"') {
if (text[i + 1] === '"') {
field += '"';
i += 1;
} else {
quoted = false;
}
} else {
field += char;
}
continue;
}
if (char === '"') {
quoted = true;
started = true;
} else if (char === ",") {
row.push(field);
field = "";
started = true;
} else if (char === "\n") {
row.push(field);
rows.push(row);
row = [];
field = "";
started = false;
} else if (char !== "\r") {
field += char;
started = true;
}
}
if (started || field !== "" || row.length > 0) {
row.push(field);
rows.push(row);
}
return rows;
}
/**
* Parse CSV text into records keyed by its header row.
*
* A leading byte-order mark is dropped, `CRLF` and `LF` are both accepted, and
* a row shorter than the header has its remaining columns filled with the
* empty string rather than `undefined`. An empty input yields no records.
*/
export function parseCsv(text: string): CsvRecord[] {
const rows = parseRows(text.replace(/^\uFEFF/, ""));
const header = rows.shift();
if (header === undefined) {
return [];
}
const records: CsvRecord[] = [];
for (const row of rows) {
if (row.length === 1 && row[0] === "") {
continue;
}
const record: CsvRecord = {};
for (let i = 0; i < header.length; i += 1) {
record[header[i] as string] = row[i] ?? "";
}
records.push(record);
}
return records;
}
|