function parseCsv(content) { const rows = []; let row = []; let field = ""; let inQuotes = false; // Character-level parser to correctly handle commas/newlines inside quotes. for (let i = 0; i < content.length; i += 1) { const char = content[i]; const next = content[i + 1]; if (char === "\"") { if (inQuotes && next === "\"") { field += "\""; i += 1; } else { inQuotes = !inQuotes; } continue; } if (char === "," && !inQuotes) { row.push(field); field = ""; continue; } if ((char === "\n" || char === "\r") && !inQuotes) { if (char === "\r" && next === "\n") { i += 1; } row.push(field); field = ""; if (row.length > 1 || (row.length === 1 && row[0] !== "")) { rows.push(row); } row = []; continue; } field += char; } if (field.length || row.length) { row.push(field); rows.push(row); } if (!rows.length) { return { headers: [], records: [] }; } const headers = rows[0].map((h) => h.trim()); const records = rows.slice(1).map((r) => { const obj = {}; headers.forEach((h, idx) => { obj[h] = (r[idx] || "").trim(); }); return obj; }); return { headers, records }; } function escapeCsvValue(value) { const s = String(value ?? ""); if (/[",\n\r]/.test(s)) { return `"${s.replace(/"/g, "\"\"")}"`; } return s; } function toCsv(headers, records) { const lines = [headers.map(escapeCsvValue).join(",")]; records.forEach((record) => { const row = headers.map((h) => escapeCsvValue(record[h] ?? "")); lines.push(row.join(",")); }); return `${lines.join("\n")}\n`; } module.exports = { parseCsv, toCsv };