// Minimal CSV parse for a client-side dictionary upload (#TBD). Handles quoted // fields, escaped quotes (""), embedded commas and newlines, and CRLF. Returns // one object per data row keyed by the trimmed header cells; blank lines are // skipped. No library needed for a dictionary of a few hundred variables. export function parseCsv(text: string): Record[] { const rows: string[][] = []; let field = ""; let row: string[] = []; let inQuotes = false; const s = text.replace(/\r\n/g, "\n").replace(/\r/g, "\n"); for (let i = 0; i < s.length; i++) { const c = s[i]; if (inQuotes) { if (c === '"') { if (s[i + 1] === '"') { field += '"'; i += 1; } else { inQuotes = false; } } else { field += c; } } else if (c === '"') { inQuotes = true; } else if (c === ",") { row.push(field); field = ""; } else if (c === "\n") { row.push(field); rows.push(row); row = []; field = ""; } else { field += c; } } if (field.length > 0 || row.length > 0) { row.push(field); rows.push(row); } if (rows.length === 0) return []; const header = rows[0].map((h) => h.trim()); return rows .slice(1) .filter((r) => r.some((cell) => cell.trim() !== "")) .map((r) => { const record: Record = {}; header.forEach((h, idx) => { record[h] = (r[idx] ?? "").trim(); }); return record; }); } // Minimal CSV serialization for client-side export downloads. No library // needed for data this small (a few hundred rows, flat objects). export function toCsv(rows: T[]): string { if (rows.length === 0) return ""; const columns = Array.from( rows.reduce((set, row) => { Object.keys(row).forEach((k) => set.add(k)); return set; }, new Set()), ); const escape = (value: unknown): string => { if (value === null || value === undefined) return ""; const s = String(value); return /[",\n]/.test(s) ? `"${s.replace(/"/g, '""')}"` : s; }; const lines = [columns.join(",")]; for (const row of rows) { const record = row as Record; lines.push(columns.map((c) => escape(record[c])).join(",")); } return lines.join("\n"); } export function downloadCsv(filename: string, rows: T[]): void { const blob = new Blob([toCsv(rows)], { type: "text/csv;charset=utf-8;" }); const url = URL.createObjectURL(blob); const link = document.createElement("a"); link.href = url; link.download = filename; link.click(); URL.revokeObjectURL(url); }