96 lines
1.9 KiB
JavaScript
96 lines
1.9 KiB
JavaScript
export function parseCsv(content) {
|
|
const rows = parseRows(content);
|
|
if (rows.length === 0) return [];
|
|
|
|
const headers = rows[0].map((header) => header.trim());
|
|
return rows.slice(1)
|
|
.filter((row) => row.some((cell) => cell.trim() !== ""))
|
|
.map((row) => {
|
|
const record = {};
|
|
headers.forEach((header, index) => {
|
|
record[header] = row[index] ?? "";
|
|
});
|
|
return record;
|
|
});
|
|
}
|
|
|
|
export function stringifyCsv(records) {
|
|
if (records.length === 0) return "";
|
|
|
|
const headers = [
|
|
"platform",
|
|
"metric_label",
|
|
"name",
|
|
"url",
|
|
"hotness_raw",
|
|
"hotness_number",
|
|
"unit",
|
|
"confidence",
|
|
"evidence",
|
|
"status",
|
|
"fetched_at",
|
|
"error",
|
|
];
|
|
|
|
const lines = [headers.join(",")];
|
|
for (const record of records) {
|
|
lines.push(headers.map((header) => csvEscape(record[header] ?? "")).join(","));
|
|
}
|
|
return `${lines.join("\n")}\n`;
|
|
}
|
|
|
|
function parseRows(content) {
|
|
const rows = [];
|
|
let row = [];
|
|
let cell = "";
|
|
let inQuotes = false;
|
|
|
|
for (let i = 0; i < content.length; i += 1) {
|
|
const char = content[i];
|
|
const next = content[i + 1];
|
|
|
|
if (char === "\"" && inQuotes && next === "\"") {
|
|
cell += "\"";
|
|
i += 1;
|
|
continue;
|
|
}
|
|
|
|
if (char === "\"") {
|
|
inQuotes = !inQuotes;
|
|
continue;
|
|
}
|
|
|
|
if (char === "," && !inQuotes) {
|
|
row.push(cell);
|
|
cell = "";
|
|
continue;
|
|
}
|
|
|
|
if ((char === "\n" || char === "\r") && !inQuotes) {
|
|
if (char === "\r" && next === "\n") i += 1;
|
|
row.push(cell);
|
|
rows.push(row);
|
|
row = [];
|
|
cell = "";
|
|
continue;
|
|
}
|
|
|
|
cell += char;
|
|
}
|
|
|
|
if (cell.length > 0 || row.length > 0) {
|
|
row.push(cell);
|
|
rows.push(row);
|
|
}
|
|
|
|
return rows;
|
|
}
|
|
|
|
function csvEscape(value) {
|
|
const text = String(value);
|
|
if (/[",\r\n]/.test(text)) {
|
|
return `"${text.replace(/"/g, "\"\"")}"`;
|
|
}
|
|
return text;
|
|
}
|