2022-02-22 06:30:37 +00:00
|
|
|
function unwrapDq(item: string): string {
|
|
|
|
const s = item.at(0);
|
|
|
|
const e = item.at(-1);
|
|
|
|
if (s === '"' && e === '"') {
|
|
|
|
return item.slice(1, -1);
|
|
|
|
}
|
|
|
|
|
|
|
|
return item;
|
|
|
|
}
|
|
|
|
|
2022-04-03 11:52:57 +00:00
|
|
|
function splitCsvBlock(text: string, splitter: string = '\r\n'): string[] {
|
|
|
|
if (!text.endsWith(splitter)) {
|
|
|
|
text += splitter;
|
2022-02-22 06:30:37 +00:00
|
|
|
}
|
|
|
|
const lines = [];
|
|
|
|
let line = '';
|
|
|
|
let inDq = false;
|
|
|
|
|
|
|
|
for (let i = 0; i <= text.length; i++) {
|
|
|
|
const c = text[i];
|
|
|
|
|
|
|
|
if (
|
|
|
|
c === '"' &&
|
|
|
|
((c[i + 1] === '"' && c[i + 2] === '"') || c[i + 1] !== '"')
|
|
|
|
) {
|
|
|
|
inDq = !inDq;
|
|
|
|
}
|
|
|
|
|
2022-04-03 11:52:57 +00:00
|
|
|
const isEnd = [...splitter]
|
|
|
|
.slice(1)
|
|
|
|
.map((s, j) => text[i + j + 1] === s)
|
|
|
|
.every(Boolean);
|
|
|
|
|
|
|
|
if (!inDq && c === splitter[0] && isEnd) {
|
2022-02-22 06:30:37 +00:00
|
|
|
lines.push(line);
|
|
|
|
line = '';
|
2022-04-03 11:52:57 +00:00
|
|
|
i = i + splitter.length - 1;
|
2022-02-22 06:30:37 +00:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
line += c;
|
|
|
|
}
|
|
|
|
|
|
|
|
return lines;
|
|
|
|
}
|
|
|
|
|
|
|
|
function splitCsvLine(line: string): string[] {
|
|
|
|
if (line.at(-1) !== ',') {
|
|
|
|
// if conforming to spec, it should not end with ','
|
|
|
|
line += ',';
|
|
|
|
}
|
|
|
|
|
|
|
|
const items = [];
|
|
|
|
let item = '';
|
|
|
|
let inDq = false;
|
|
|
|
|
|
|
|
for (let i = 0; i < line.length; i++) {
|
|
|
|
const c = line[i];
|
|
|
|
|
|
|
|
if (
|
|
|
|
c === '"' &&
|
|
|
|
((c[i + 1] === '"' && c[i + 2] === '"') || c[i + 1] !== '"')
|
|
|
|
) {
|
|
|
|
inDq = !inDq;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!inDq && c === ',') {
|
|
|
|
item = unwrapDq(item);
|
|
|
|
item = item.replaceAll('""', '"');
|
|
|
|
items.push(item);
|
|
|
|
item = '';
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
|
|
|
item += c;
|
|
|
|
}
|
|
|
|
|
|
|
|
return items;
|
|
|
|
}
|
|
|
|
|
|
|
|
export function parseCSV(text: string): string[][] {
|
|
|
|
// Works on RFC 4180
|
2022-04-03 11:52:57 +00:00
|
|
|
let rows = splitCsvBlock(text);
|
|
|
|
if (rows.length === 1) {
|
|
|
|
rows = splitCsvBlock(text, '\n');
|
|
|
|
}
|
2022-02-22 06:30:37 +00:00
|
|
|
return rows.map(splitCsvLine);
|
|
|
|
}
|