jtcsv 2.2.7 → 3.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (140) hide show
  1. package/README.md +31 -1
  2. package/bin/jtcsv.js +891 -821
  3. package/bin/jtcsv.ts +2534 -0
  4. package/csv-to-json.js +168 -145
  5. package/dist/jtcsv-core.cjs.js +1407 -0
  6. package/dist/jtcsv-core.cjs.js.map +1 -0
  7. package/dist/jtcsv-core.esm.js +1379 -0
  8. package/dist/jtcsv-core.esm.js.map +1 -0
  9. package/dist/jtcsv-core.umd.js +1413 -0
  10. package/dist/jtcsv-core.umd.js.map +1 -0
  11. package/dist/jtcsv-full.cjs.js +1912 -0
  12. package/dist/jtcsv-full.cjs.js.map +1 -0
  13. package/dist/jtcsv-full.esm.js +1880 -0
  14. package/dist/jtcsv-full.esm.js.map +1 -0
  15. package/dist/jtcsv-full.umd.js +1918 -0
  16. package/dist/jtcsv-full.umd.js.map +1 -0
  17. package/dist/jtcsv-workers.esm.js +759 -0
  18. package/dist/jtcsv-workers.esm.js.map +1 -0
  19. package/dist/jtcsv-workers.umd.js +773 -0
  20. package/dist/jtcsv-workers.umd.js.map +1 -0
  21. package/dist/jtcsv.cjs.js +61 -19
  22. package/dist/jtcsv.cjs.js.map +1 -1
  23. package/dist/jtcsv.esm.js +61 -19
  24. package/dist/jtcsv.esm.js.map +1 -1
  25. package/dist/jtcsv.umd.js +61 -19
  26. package/dist/jtcsv.umd.js.map +1 -1
  27. package/errors.js +188 -2
  28. package/examples/advanced/conditional-transformations.js +446 -0
  29. package/examples/advanced/conditional-transformations.ts +446 -0
  30. package/examples/advanced/csv-parser.worker.js +89 -0
  31. package/examples/advanced/csv-parser.worker.ts +89 -0
  32. package/examples/advanced/nested-objects-example.js +306 -0
  33. package/examples/advanced/nested-objects-example.ts +306 -0
  34. package/examples/advanced/performance-optimization.js +504 -0
  35. package/examples/advanced/performance-optimization.ts +504 -0
  36. package/examples/advanced/run-demo-server.js +116 -0
  37. package/examples/advanced/run-demo-server.ts +116 -0
  38. package/examples/advanced/web-worker-usage.html +874 -0
  39. package/examples/async-multithreaded-example.ts +335 -0
  40. package/examples/cli-advanced-usage.md +288 -0
  41. package/examples/cli-batch-processing.ts +38 -0
  42. package/examples/cli-tool.js +0 -3
  43. package/examples/cli-tool.ts +183 -0
  44. package/examples/error-handling.js +21 -7
  45. package/examples/error-handling.ts +356 -0
  46. package/examples/express-api.js +0 -3
  47. package/examples/express-api.ts +164 -0
  48. package/examples/large-dataset-example.js +0 -3
  49. package/examples/large-dataset-example.ts +204 -0
  50. package/examples/ndjson-processing.js +1 -1
  51. package/examples/ndjson-processing.ts +456 -0
  52. package/examples/plugin-excel-exporter.js +3 -4
  53. package/examples/plugin-excel-exporter.ts +406 -0
  54. package/examples/react-integration.tsx +637 -0
  55. package/examples/schema-validation.ts +640 -0
  56. package/examples/simple-usage.js +254 -254
  57. package/examples/simple-usage.ts +194 -0
  58. package/examples/streaming-example.js +4 -5
  59. package/examples/streaming-example.ts +419 -0
  60. package/examples/web-workers-advanced.ts +28 -0
  61. package/index.d.ts +1 -3
  62. package/index.js +15 -1
  63. package/json-save.js +9 -3
  64. package/json-to-csv.js +168 -21
  65. package/package.json +69 -10
  66. package/plugins/express-middleware/README.md +21 -2
  67. package/plugins/express-middleware/example.js +3 -4
  68. package/plugins/express-middleware/example.ts +135 -0
  69. package/plugins/express-middleware/index.d.ts +1 -1
  70. package/plugins/express-middleware/index.js +270 -118
  71. package/plugins/express-middleware/index.ts +557 -0
  72. package/plugins/fastify-plugin/index.js +2 -4
  73. package/plugins/fastify-plugin/index.ts +443 -0
  74. package/plugins/hono/index.ts +226 -0
  75. package/plugins/nestjs/index.ts +201 -0
  76. package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
  77. package/plugins/nextjs-api/examples/api-convert.js +0 -2
  78. package/plugins/nextjs-api/examples/api-convert.ts +67 -0
  79. package/plugins/nextjs-api/index.tsx +339 -0
  80. package/plugins/nextjs-api/route.js +2 -3
  81. package/plugins/nextjs-api/route.ts +370 -0
  82. package/plugins/nuxt/index.ts +94 -0
  83. package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
  84. package/plugins/nuxt/runtime/plugin.ts +71 -0
  85. package/plugins/remix/index.js +1 -1
  86. package/plugins/remix/index.ts +260 -0
  87. package/plugins/sveltekit/index.js +1 -1
  88. package/plugins/sveltekit/index.ts +301 -0
  89. package/plugins/trpc/index.ts +267 -0
  90. package/src/browser/browser-functions.ts +402 -0
  91. package/src/browser/core.js +92 -0
  92. package/src/browser/core.ts +152 -0
  93. package/src/browser/csv-to-json-browser.d.ts +3 -0
  94. package/src/browser/csv-to-json-browser.js +36 -14
  95. package/src/browser/csv-to-json-browser.ts +264 -0
  96. package/src/browser/errors-browser.ts +303 -0
  97. package/src/browser/extensions/plugins.js +92 -0
  98. package/src/browser/extensions/plugins.ts +93 -0
  99. package/src/browser/extensions/workers.js +39 -0
  100. package/src/browser/extensions/workers.ts +39 -0
  101. package/src/browser/globals.d.ts +5 -0
  102. package/src/browser/index.ts +192 -0
  103. package/src/browser/json-to-csv-browser.d.ts +3 -0
  104. package/src/browser/json-to-csv-browser.js +13 -3
  105. package/src/browser/json-to-csv-browser.ts +262 -0
  106. package/src/browser/streams.js +12 -2
  107. package/src/browser/streams.ts +336 -0
  108. package/src/browser/workers/csv-parser.worker.ts +377 -0
  109. package/src/browser/workers/worker-pool.ts +548 -0
  110. package/src/core/delimiter-cache.js +22 -8
  111. package/src/core/delimiter-cache.ts +310 -0
  112. package/src/core/node-optimizations.ts +449 -0
  113. package/src/core/plugin-system.js +29 -11
  114. package/src/core/plugin-system.ts +400 -0
  115. package/src/core/transform-hooks.ts +558 -0
  116. package/src/engines/fast-path-engine-new.ts +347 -0
  117. package/src/engines/fast-path-engine.ts +854 -0
  118. package/src/errors.ts +72 -0
  119. package/src/formats/ndjson-parser.ts +469 -0
  120. package/src/formats/tsv-parser.ts +334 -0
  121. package/src/index-with-plugins.js +16 -9
  122. package/src/index-with-plugins.ts +395 -0
  123. package/src/types/index.ts +255 -0
  124. package/src/utils/bom-utils.js +259 -0
  125. package/src/utils/bom-utils.ts +373 -0
  126. package/src/utils/encoding-support.js +124 -0
  127. package/src/utils/encoding-support.ts +155 -0
  128. package/src/utils/schema-validator.js +19 -19
  129. package/src/utils/schema-validator.ts +819 -0
  130. package/src/utils/transform-loader.js +1 -1
  131. package/src/utils/transform-loader.ts +389 -0
  132. package/src/utils/zod-adapter.js +170 -0
  133. package/src/utils/zod-adapter.ts +280 -0
  134. package/src/web-server/index.js +10 -10
  135. package/src/web-server/index.ts +683 -0
  136. package/src/workers/csv-multithreaded.ts +310 -0
  137. package/src/workers/csv-parser.worker.ts +227 -0
  138. package/src/workers/worker-pool.ts +409 -0
  139. package/stream-csv-to-json.js +26 -8
  140. package/stream-json-to-csv.js +1 -0
@@ -0,0 +1,854 @@
1
+ /**
2
+ * Fast-Path Engine для оптимизации CSV парсинга
3
+ * Автоматически выбирает оптимальный парсер на основе структуры CSV
4
+ *
5
+ * @version 1.0.0
6
+ * @date 2026-01-22
7
+ */
8
+
9
+ class FastPathEngine {
10
+ compilers: Map<any, any>;
11
+ rowCompilers: Map<any, any>;
12
+ stats: {
13
+ simpleParserCount: number;
14
+ quoteAwareParserCount: number;
15
+ standardParserCount: number;
16
+ cacheHits: number;
17
+ cacheMisses: number;
18
+ };
19
+
20
+ constructor() {
21
+ this.compilers = new Map();
22
+ this.rowCompilers = new Map();
23
+ this.stats = {
24
+ simpleParserCount: 0,
25
+ quoteAwareParserCount: 0,
26
+ standardParserCount: 0,
27
+ cacheHits: 0,
28
+ cacheMisses: 0
29
+ };
30
+ }
31
+
32
+ _hasQuotes(csv) {
33
+ return csv.indexOf('"') !== -1;
34
+ }
35
+
36
+ _hasEscapedQuotes(csv) {
37
+ return csv.indexOf('""') !== -1;
38
+ }
39
+
40
+ _hasBackslashes(csv) {
41
+ return csv.indexOf('\\') !== -1;
42
+ }
43
+
44
+ _getStructureForParse(csv: any, options: any) {
45
+ const sampleSize = Math.min(1000, csv.length);
46
+ const sample = csv.substring(0, sampleSize);
47
+ const structure = this.analyzeStructure(sample, options);
48
+ const hasBackslashes = this._hasBackslashes(csv);
49
+ const hasQuotes = structure.hasQuotes ? true : this._hasQuotes(csv);
50
+ const hasEscapedQuotes = structure.hasEscapedQuotes
51
+ ? true
52
+ : (hasQuotes ? this._hasEscapedQuotes(csv) : false);
53
+
54
+ let normalized = {
55
+ ...structure,
56
+ hasQuotes,
57
+ hasEscapedQuotes,
58
+ hasBackslashes
59
+ };
60
+
61
+ if (structure.recommendedEngine === 'SIMPLE' && hasQuotes) {
62
+ normalized = {
63
+ ...normalized,
64
+ hasNewlinesInFields: true,
65
+ recommendedEngine: 'QUOTE_AWARE'
66
+ };
67
+ }
68
+
69
+ if (options && options.forceEngine) {
70
+ normalized = {
71
+ ...normalized,
72
+ recommendedEngine: options.forceEngine
73
+ };
74
+ }
75
+
76
+ return normalized;
77
+ }
78
+
79
+ /**
80
+ * Анализирует структуру CSV и определяет оптимальный парсер
81
+ */
82
+ analyzeStructure(sample: any, options: any = {}) {
83
+ const delimiter = options.delimiter || this._detectDelimiter(sample);
84
+ const lines = sample.split('\n').slice(0, 10);
85
+
86
+ let hasQuotes = false;
87
+ let hasNewlinesInFields = false;
88
+ let hasEscapedQuotes = false;
89
+ let maxFields = 0;
90
+ let totalFields = 0;
91
+
92
+ for (const line of lines) {
93
+ if (line.includes('"')) {
94
+ hasQuotes = true;
95
+ if (line.includes('""')) {
96
+ hasEscapedQuotes = true;
97
+ }
98
+ }
99
+
100
+ const quoteCount = (line.match(/"/g) || []).length;
101
+ if (quoteCount % 2 !== 0) {
102
+ hasNewlinesInFields = true;
103
+ }
104
+
105
+ const fieldCount = line.split(delimiter).length;
106
+ totalFields += fieldCount;
107
+ if (fieldCount > maxFields) {
108
+ maxFields = fieldCount;
109
+ }
110
+ }
111
+
112
+ const avgFieldsPerLine = totalFields / lines.length;
113
+ const fieldConsistency = maxFields === avgFieldsPerLine;
114
+
115
+ return {
116
+ delimiter,
117
+ hasQuotes,
118
+ hasEscapedQuotes,
119
+ hasNewlinesInFields,
120
+ fieldConsistency,
121
+ avgFieldsPerLine,
122
+ maxFields,
123
+ recommendedEngine: this._selectEngine(hasQuotes, hasNewlinesInFields, fieldConsistency)
124
+ };
125
+ }
126
+
127
+ /**
128
+ * Автоматически определяет разделитель
129
+ */
130
+ _detectDelimiter(sample) {
131
+ const candidates = [',', ';', '\t', '|'];
132
+ const firstLine = sample.split('\n')[0];
133
+
134
+ let bestDelimiter = ',';
135
+ let bestScore = 0;
136
+
137
+ for (const delimiter of candidates) {
138
+ const fields = firstLine.split(delimiter);
139
+ const score = fields.length;
140
+
141
+ // Если разделитель не найден в строке, пропускаем его
142
+ if (score === 1 && !firstLine.includes(delimiter)) {
143
+ continue;
144
+ }
145
+
146
+ const avgLength = fields.reduce((sum, field) => sum + field.length, 0) / fields.length;
147
+ const variance = fields.reduce((sum, field) => sum + Math.pow(field.length - avgLength, 2), 0) / fields.length;
148
+
149
+ const finalScore = score / (variance + 1);
150
+
151
+ /* istanbul ignore next */
152
+ if (finalScore > bestScore) {
153
+ bestScore = finalScore;
154
+ bestDelimiter = delimiter;
155
+ }
156
+ }
157
+
158
+ return bestDelimiter;
159
+ }
160
+
161
+ /**
162
+ * Выбирает оптимальный движок парсинга
163
+ */
164
+ _selectEngine(hasQuotes: any, hasNewlinesInFields: any, _fieldConsistency: any) {
165
+ if (!hasQuotes && !hasNewlinesInFields) {
166
+ return 'SIMPLE';
167
+ }
168
+
169
+ if (hasQuotes && !hasNewlinesInFields) {
170
+ return 'QUOTE_AWARE';
171
+ }
172
+
173
+ return 'STANDARD';
174
+ }
175
+
176
+ /**
177
+ * Создает простой парсер (разделитель без кавычек)
178
+ */
179
+ _createSimpleParser(structure) {
180
+ const { delimiter, hasBackslashes } = structure;
181
+
182
+ return (csv) => {
183
+ const rows = [];
184
+ if (hasBackslashes) {
185
+ this._emitSimpleRowsEscaped(csv, delimiter, (row) => rows.push(row));
186
+ } else {
187
+ this._emitSimpleRows(csv, delimiter, (row) => rows.push(row));
188
+ }
189
+
190
+ return rows;
191
+ };
192
+ }
193
+
194
+ _emitSimpleRows(csv: any, delimiter: any, onRow: any) {
195
+ let currentRow = [];
196
+ let rowHasData = false;
197
+ let fieldStart = 0;
198
+ let i = 0;
199
+
200
+ while (i <= csv.length) {
201
+ const char = i < csv.length ? csv[i] : '\n';
202
+
203
+ if (char !== '\r' && char !== '\n' && char !== ' ' && char !== '\t') {
204
+ rowHasData = true;
205
+ }
206
+
207
+ if (char === delimiter || char === '\n' || char === '\r' || i === csv.length) {
208
+ const field = csv.slice(fieldStart, i);
209
+ currentRow.push(field);
210
+
211
+ if (char === '\n' || char === '\r' || i === csv.length) {
212
+ if (rowHasData) {
213
+ onRow(currentRow);
214
+ }
215
+ currentRow = [];
216
+ rowHasData = false;
217
+ }
218
+
219
+ if (char === '\r' && csv[i + 1] === '\n') {
220
+ i++;
221
+ }
222
+
223
+ fieldStart = i + 1;
224
+ }
225
+
226
+ i++;
227
+ }
228
+ }
229
+
230
+ _emitSimpleRowsEscaped(csv: any, delimiter: any, onRow: any) {
231
+ let currentRow = [];
232
+ let currentField = '';
233
+ let rowHasData = false;
234
+ let escapeNext = false;
235
+ let i = 0;
236
+
237
+ while (i <= csv.length) {
238
+ const char = i < csv.length ? csv[i] : '\n';
239
+ const nextChar = i + 1 < csv.length ? csv[i + 1] : '';
240
+
241
+ if (char !== '\r' && char !== '\n' && char !== ' ' && char !== '\t') {
242
+ rowHasData = true;
243
+ }
244
+
245
+ if (escapeNext) {
246
+ currentField += char;
247
+ escapeNext = false;
248
+ i++;
249
+ continue;
250
+ }
251
+
252
+ if (char === '\\') {
253
+ if (i + 1 >= csv.length) {
254
+ currentField += '\\';
255
+ i++;
256
+ continue;
257
+ }
258
+
259
+ if (nextChar === '\\') {
260
+ currentField += '\\';
261
+ i += 2;
262
+ continue;
263
+ }
264
+
265
+ if (nextChar === '\n' || nextChar === '\r') {
266
+ currentField += '\\';
267
+ i++;
268
+ continue;
269
+ }
270
+
271
+ escapeNext = true;
272
+ i++;
273
+ continue;
274
+ }
275
+
276
+ if (char === delimiter || char === '\n' || char === '\r' || i === csv.length) {
277
+ currentRow.push(currentField);
278
+ currentField = '';
279
+
280
+ if (char === '\n' || char === '\r' || i === csv.length) {
281
+ if (rowHasData) {
282
+ onRow(currentRow);
283
+ }
284
+ currentRow = [];
285
+ rowHasData = false;
286
+ }
287
+
288
+ if (char === '\r' && csv[i + 1] === '\n') {
289
+ i++;
290
+ }
291
+
292
+ i++;
293
+ continue;
294
+ }
295
+
296
+ currentField += char;
297
+ i++;
298
+ }
299
+ }
300
+
301
+ *_simpleRowsGenerator(csv: any, delimiter: any) {
302
+ let currentRow = [];
303
+ let rowHasData = false;
304
+ let fieldStart = 0;
305
+ let i = 0;
306
+
307
+ while (i <= csv.length) {
308
+ const char = i < csv.length ? csv[i] : '\n';
309
+
310
+ if (char !== '\r' && char !== '\n' && char !== ' ' && char !== '\t') {
311
+ rowHasData = true;
312
+ }
313
+
314
+ if (char === delimiter || char === '\n' || char === '\r' || i === csv.length) {
315
+ const field = csv.slice(fieldStart, i);
316
+ currentRow.push(field);
317
+
318
+ if (char === '\n' || char === '\r' || i === csv.length) {
319
+ if (rowHasData) {
320
+ yield currentRow;
321
+ }
322
+ currentRow = [];
323
+ rowHasData = false;
324
+ }
325
+
326
+ if (char === '\r' && csv[i + 1] === '\n') {
327
+ i++;
328
+ }
329
+
330
+ fieldStart = i + 1;
331
+ }
332
+
333
+ i++;
334
+ }
335
+ }
336
+
337
+ *_simpleEscapedRowsGenerator(csv: any, delimiter: any) {
338
+ let currentRow = [];
339
+ let currentField = '';
340
+ let rowHasData = false;
341
+ let escapeNext = false;
342
+ let i = 0;
343
+
344
+ while (i <= csv.length) {
345
+ const char = i < csv.length ? csv[i] : '\n';
346
+ const nextChar = i + 1 < csv.length ? csv[i + 1] : '';
347
+
348
+ if (char !== '\r' && char !== '\n' && char !== ' ' && char !== '\t') {
349
+ rowHasData = true;
350
+ }
351
+
352
+ if (escapeNext) {
353
+ currentField += char;
354
+ escapeNext = false;
355
+ i++;
356
+ continue;
357
+ }
358
+
359
+ if (char === '\\') {
360
+ if (i + 1 >= csv.length) {
361
+ currentField += '\\';
362
+ i++;
363
+ continue;
364
+ }
365
+
366
+ if (nextChar === '\\') {
367
+ currentField += '\\';
368
+ i += 2;
369
+ continue;
370
+ }
371
+
372
+ if (nextChar === '\n' || nextChar === '\r') {
373
+ currentField += '\\';
374
+ i++;
375
+ continue;
376
+ }
377
+
378
+ escapeNext = true;
379
+ i++;
380
+ continue;
381
+ }
382
+
383
+ if (char === delimiter || char === '\n' || char === '\r' || i === csv.length) {
384
+ currentRow.push(currentField);
385
+ currentField = '';
386
+
387
+ if (char === '\n' || char === '\r' || i === csv.length) {
388
+ if (rowHasData) {
389
+ yield currentRow;
390
+ }
391
+ currentRow = [];
392
+ rowHasData = false;
393
+ }
394
+
395
+ if (char === '\r' && csv[i + 1] === '\n') {
396
+ i++;
397
+ }
398
+
399
+ i++;
400
+ continue;
401
+ }
402
+
403
+ currentField += char;
404
+ i++;
405
+ }
406
+ }
407
+
408
+ /**
409
+ * Simple row emitter that avoids storing all rows in memory.
410
+ */
411
+ _createSimpleRowEmitter(structure) {
412
+ const { delimiter, hasBackslashes } = structure;
413
+
414
+ return (csv: any, onRow: any) => {
415
+ if (hasBackslashes) {
416
+ this._emitSimpleRowsEscaped(csv, delimiter, onRow);
417
+ } else {
418
+ this._emitSimpleRows(csv, delimiter, onRow);
419
+ }
420
+ };
421
+ }
422
+
423
+ /**
424
+ * State machine парсер для CSV с кавычками (RFC 4180)
425
+ */
426
+ _createQuoteAwareParser(structure) {
427
+ const { delimiter, hasEscapedQuotes, hasBackslashes } = structure;
428
+
429
+ return (csv) => {
430
+ const rows = [];
431
+ /* istanbul ignore next */
432
+ const iterator = hasBackslashes
433
+ ? this._quoteAwareEscapedRowsGenerator(csv, delimiter, hasEscapedQuotes)
434
+ : this._quoteAwareRowsGenerator(csv, delimiter, hasEscapedQuotes);
435
+
436
+ for (const row of iterator) {
437
+ rows.push(row);
438
+ }
439
+
440
+ return rows;
441
+ };
442
+ }
443
+
444
+ /**
445
+ * Quote-aware row emitter that avoids storing all rows in memory.
446
+ */
447
+ _createQuoteAwareRowEmitter(structure) {
448
+ const { delimiter, hasEscapedQuotes, hasBackslashes } = structure;
449
+
450
+ return (csv: any, onRow: any) => {
451
+ const iterator = hasBackslashes
452
+ ? this._quoteAwareEscapedRowsGenerator(csv, delimiter, hasEscapedQuotes)
453
+ : this._quoteAwareRowsGenerator(csv, delimiter, hasEscapedQuotes);
454
+
455
+ for (const row of iterator) {
456
+ onRow(row);
457
+ }
458
+ };
459
+ }
460
+
461
+ *_quoteAwareRowsGenerator(csv: any, delimiter: any, hasEscapedQuotes: any) {
462
+ let currentRow = [];
463
+ let currentField = '';
464
+ let rowHasData = false;
465
+ let insideQuotes = false;
466
+ let lineNumber = 1;
467
+ let i = 0;
468
+
469
+ while (i < csv.length) {
470
+ const char = csv[i];
471
+ const nextChar = csv[i + 1];
472
+
473
+ if (char !== '\r' && char !== '\n' && char !== ' ' && char !== '\t') {
474
+ rowHasData = true;
475
+ }
476
+
477
+ if (char === '"') {
478
+ if (insideQuotes) {
479
+ if (hasEscapedQuotes && nextChar === '"') {
480
+ const afterNext = csv[i + 2];
481
+ const isLineEnd = i + 2 >= csv.length || afterNext === '\n' || afterNext === '\r';
482
+
483
+ currentField += '"';
484
+ if (isLineEnd) {
485
+ insideQuotes = false;
486
+ i += 2;
487
+ continue;
488
+ }
489
+
490
+ i += 2;
491
+
492
+ let j = i;
493
+ while (j < csv.length && (csv[j] === ' ' || csv[j] === '\t')) {
494
+ j++;
495
+ }
496
+ if (j >= csv.length || csv[j] === delimiter || csv[j] === '\n' || csv[j] === '\r') {
497
+ insideQuotes = false;
498
+ }
499
+ continue;
500
+ }
501
+
502
+ let j = i + 1;
503
+ while (j < csv.length && (csv[j] === ' ' || csv[j] === '\t')) {
504
+ j++;
505
+ }
506
+ if (j >= csv.length || csv[j] === delimiter || csv[j] === '\n' || csv[j] === '\r') {
507
+ insideQuotes = false;
508
+ i++;
509
+ continue;
510
+ }
511
+
512
+ currentField += '"';
513
+ i++;
514
+ continue;
515
+ }
516
+
517
+ insideQuotes = true;
518
+ i++;
519
+ continue;
520
+ }
521
+
522
+ if (!insideQuotes && (char === delimiter || char === '\n' || char === '\r')) {
523
+ currentRow.push(currentField);
524
+ currentField = '';
525
+
526
+ if (char === '\n' || char === '\r') {
527
+ /* istanbul ignore next */
528
+ if (rowHasData) {
529
+ yield currentRow;
530
+ }
531
+ currentRow = [];
532
+ rowHasData = false;
533
+ lineNumber++;
534
+
535
+ if (char === '\r' && nextChar === '\n') {
536
+ i++;
537
+ }
538
+ }
539
+
540
+ i++;
541
+ continue;
542
+ }
543
+
544
+ currentField += char;
545
+ i++;
546
+ }
547
+
548
+ if (insideQuotes) {
549
+ const error = new Error('Unclosed quotes in CSV');
550
+ (error as any).code = 'FAST_PATH_UNCLOSED_QUOTES';
551
+ (error as any).lineNumber = lineNumber;
552
+ throw error;
553
+ }
554
+
555
+ if (currentField !== '' || currentRow.length > 0) {
556
+ currentRow.push(currentField);
557
+ /* istanbul ignore next */
558
+ if (rowHasData) {
559
+ yield currentRow;
560
+ }
561
+ }
562
+ }
563
+
564
+ *_quoteAwareEscapedRowsGenerator(csv: any, delimiter: any, hasEscapedQuotes: any) {
565
+ let currentRow = [];
566
+ let currentField = '';
567
+ let rowHasData = false;
568
+ let insideQuotes = false;
569
+ let escapeNext = false;
570
+ let lineNumber = 1;
571
+ let i = 0;
572
+
573
+ while (i < csv.length) {
574
+ const char = csv[i];
575
+ const nextChar = csv[i + 1];
576
+
577
+ if (char !== '\r' && char !== '\n' && char !== ' ' && char !== '\t') {
578
+ rowHasData = true;
579
+ }
580
+
581
+ if (escapeNext) {
582
+ currentField += char;
583
+ escapeNext = false;
584
+ i++;
585
+ continue;
586
+ }
587
+
588
+ if (char === '\\') {
589
+ if (i + 1 >= csv.length) {
590
+ currentField += '\\';
591
+ i++;
592
+ continue;
593
+ }
594
+
595
+ if (!insideQuotes && (nextChar === '\n' || nextChar === '\r')) {
596
+ currentField += '\\';
597
+ i++;
598
+ continue;
599
+ }
600
+
601
+ if (nextChar === '\\') {
602
+ currentField += '\\';
603
+ i += 2;
604
+ continue;
605
+ }
606
+
607
+ escapeNext = true;
608
+ i++;
609
+ continue;
610
+ }
611
+
612
+ if (char === '"') {
613
+ if (insideQuotes) {
614
+ if (hasEscapedQuotes && nextChar === '"') {
615
+ const afterNext = csv[i + 2];
616
+ const isLineEnd = i + 2 >= csv.length || afterNext === '\n' || afterNext === '\r';
617
+
618
+ currentField += '"';
619
+ if (isLineEnd) {
620
+ insideQuotes = false;
621
+ i += 2;
622
+ continue;
623
+ }
624
+
625
+ i += 2;
626
+
627
+ let j = i;
628
+ while (j < csv.length && (csv[j] === ' ' || csv[j] === '\t')) {
629
+ j++;
630
+ }
631
+ if (j >= csv.length || csv[j] === delimiter || csv[j] === '\n' || csv[j] === '\r') {
632
+ insideQuotes = false;
633
+ }
634
+ continue;
635
+ }
636
+
637
+ let j = i + 1;
638
+ while (j < csv.length && (csv[j] === ' ' || csv[j] === '\t')) {
639
+ j++;
640
+ }
641
+ if (j >= csv.length || csv[j] === delimiter || csv[j] === '\n' || csv[j] === '\r') {
642
+ insideQuotes = false;
643
+ i++;
644
+ continue;
645
+ }
646
+
647
+ currentField += '"';
648
+ i++;
649
+ continue;
650
+ }
651
+
652
+ insideQuotes = true;
653
+ i++;
654
+ continue;
655
+ }
656
+
657
+ if (!insideQuotes && (char === delimiter || char === '\n' || char === '\r')) {
658
+ currentRow.push(currentField);
659
+ currentField = '';
660
+
661
+ if (char === '\n' || char === '\r') {
662
+ /* istanbul ignore next */
663
+ if (rowHasData) {
664
+ yield currentRow;
665
+ }
666
+ currentRow = [];
667
+ rowHasData = false;
668
+ lineNumber++;
669
+
670
+ if (char === '\r' && nextChar === '\n') {
671
+ i++;
672
+ }
673
+ }
674
+
675
+ i++;
676
+ continue;
677
+ }
678
+
679
+ currentField += char;
680
+ i++;
681
+ }
682
+
683
+ /* istanbul ignore next */
684
+ if (escapeNext) {
685
+ currentField += '\\';
686
+ }
687
+
688
+ if (insideQuotes) {
689
+ const error = new Error('Unclosed quotes in CSV');
690
+ (error as any).code = 'FAST_PATH_UNCLOSED_QUOTES';
691
+ (error as any).lineNumber = lineNumber;
692
+ throw error;
693
+ }
694
+
695
+ if (currentField !== '' || currentRow.length > 0) {
696
+ currentRow.push(currentField);
697
+ /* istanbul ignore next */
698
+ if (rowHasData) {
699
+ yield currentRow;
700
+ }
701
+ }
702
+ }
703
+
704
+ compileParser(structure) {
705
+ const cacheKey = JSON.stringify(structure);
706
+
707
+ // Проверяем кеш
708
+ if (this.compilers.has(cacheKey)) {
709
+ this.stats.cacheHits++;
710
+ return this.compilers.get(cacheKey);
711
+ }
712
+
713
+ this.stats.cacheMisses++;
714
+
715
+ let parser;
716
+ switch (structure.recommendedEngine) {
717
+ case 'SIMPLE':
718
+ parser = this._createSimpleParser(structure);
719
+ this.stats.simpleParserCount++;
720
+ break;
721
+ case 'QUOTE_AWARE':
722
+ parser = this._createQuoteAwareParser(structure);
723
+ this.stats.quoteAwareParserCount++;
724
+ break;
725
+ case 'STANDARD':
726
+ parser = this._createQuoteAwareParser(structure);
727
+ this.stats.standardParserCount++;
728
+ break;
729
+ default:
730
+ parser = this._createQuoteAwareParser(structure);
731
+ this.stats.standardParserCount++;
732
+ }
733
+
734
+ // Кешируем парсер
735
+ this.compilers.set(cacheKey, parser);
736
+
737
+ return parser;
738
+ }
739
+
740
+ /**
741
+ * Compiles a row-emitter parser for streaming conversion.
742
+ */
743
+ compileRowEmitter(structure) {
744
+ const cacheKey = JSON.stringify(structure);
745
+
746
+ if (this.rowCompilers.has(cacheKey)) {
747
+ return this.rowCompilers.get(cacheKey);
748
+ }
749
+
750
+ let emitter;
751
+ switch (structure.recommendedEngine) {
752
+ case 'SIMPLE':
753
+ emitter = this._createSimpleRowEmitter(structure);
754
+ break;
755
+ case 'QUOTE_AWARE':
756
+ emitter = this._createQuoteAwareRowEmitter(structure);
757
+ break;
758
+ case 'STANDARD':
759
+ emitter = this._createQuoteAwareRowEmitter(structure);
760
+ break;
761
+ default:
762
+ emitter = this._createQuoteAwareRowEmitter(structure);
763
+ }
764
+
765
+ this.rowCompilers.set(cacheKey, emitter);
766
+ return emitter;
767
+ }
768
+
769
+ /**
770
+ * Iterates rows without allocating the full result set.
771
+ */
772
+ *iterateRows(csv: any, options = {}) {
773
+ const structure = this._getStructureForParse(csv, options);
774
+ const useEscapes = structure.hasBackslashes;
775
+
776
+ switch (structure.recommendedEngine) {
777
+ case 'SIMPLE':
778
+ if (useEscapes) {
779
+ yield* this._simpleEscapedRowsGenerator(csv, structure.delimiter);
780
+ } else {
781
+ yield* this._simpleRowsGenerator(csv, structure.delimiter);
782
+ }
783
+ break;
784
+ case 'QUOTE_AWARE':
785
+ if (useEscapes) {
786
+ yield* this._quoteAwareEscapedRowsGenerator(csv, structure.delimiter, structure.hasEscapedQuotes);
787
+ } else {
788
+ yield* this._quoteAwareRowsGenerator(csv, structure.delimiter, structure.hasEscapedQuotes);
789
+ }
790
+ break;
791
+ case 'STANDARD':
792
+ if (useEscapes) {
793
+ yield* this._quoteAwareEscapedRowsGenerator(csv, structure.delimiter, structure.hasEscapedQuotes);
794
+ } else {
795
+ yield* this._quoteAwareRowsGenerator(csv, structure.delimiter, structure.hasEscapedQuotes);
796
+ }
797
+ break;
798
+ default:
799
+ if (useEscapes) {
800
+ yield* this._quoteAwareEscapedRowsGenerator(csv, structure.delimiter, structure.hasEscapedQuotes);
801
+ } else {
802
+ yield* this._quoteAwareRowsGenerator(csv, structure.delimiter, structure.hasEscapedQuotes);
803
+ }
804
+ }
805
+ }
806
+
807
+ /**
808
+ * Парсит CSV с использованием оптимального парсера
809
+ */
810
+ parse(csv: any, options = {}) {
811
+ const structure = this._getStructureForParse(csv, options);
812
+ const parser = this.compileParser(structure);
813
+
814
+ return parser(csv);
815
+ }
816
+
817
+ /**
818
+ * Parses CSV and emits rows via a callback to reduce memory usage.
819
+ */
820
+ /* istanbul ignore next */
821
+ parseRows(csv: any, options = {}, onRow: any) {
822
+ for (const row of this.iterateRows(csv, options)) {
823
+ onRow(row);
824
+ }
825
+ }
826
+
827
+ /**
828
+ * Возвращает статистику использования парсеров
829
+ */
830
+ getStats() {
831
+ return {
832
+ ...this.stats,
833
+ totalParsers: this.compilers.size,
834
+ hitRate: this.stats.cacheHits / (this.stats.cacheHits + this.stats.cacheMisses) || 0
835
+ };
836
+ }
837
+
838
+ /**
839
+ * Сбрасывает статистику и кеш
840
+ */
841
+ reset() {
842
+ this.compilers.clear();
843
+ this.rowCompilers.clear();
844
+ this.stats = {
845
+ simpleParserCount: 0,
846
+ quoteAwareParserCount: 0,
847
+ standardParserCount: 0,
848
+ cacheHits: 0,
849
+ cacheMisses: 0
850
+ };
851
+ }
852
+ }
853
+
854
+ export default FastPathEngine;