kaikai_test/src/csv.js
2026-05-14 18:53:53 +08:00

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;
}