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.
- package/README.md +31 -1
- package/bin/jtcsv.js +891 -821
- package/bin/jtcsv.ts +2534 -0
- package/csv-to-json.js +168 -145
- package/dist/jtcsv-core.cjs.js +1407 -0
- package/dist/jtcsv-core.cjs.js.map +1 -0
- package/dist/jtcsv-core.esm.js +1379 -0
- package/dist/jtcsv-core.esm.js.map +1 -0
- package/dist/jtcsv-core.umd.js +1413 -0
- package/dist/jtcsv-core.umd.js.map +1 -0
- package/dist/jtcsv-full.cjs.js +1912 -0
- package/dist/jtcsv-full.cjs.js.map +1 -0
- package/dist/jtcsv-full.esm.js +1880 -0
- package/dist/jtcsv-full.esm.js.map +1 -0
- package/dist/jtcsv-full.umd.js +1918 -0
- package/dist/jtcsv-full.umd.js.map +1 -0
- package/dist/jtcsv-workers.esm.js +759 -0
- package/dist/jtcsv-workers.esm.js.map +1 -0
- package/dist/jtcsv-workers.umd.js +773 -0
- package/dist/jtcsv-workers.umd.js.map +1 -0
- package/dist/jtcsv.cjs.js +61 -19
- package/dist/jtcsv.cjs.js.map +1 -1
- package/dist/jtcsv.esm.js +61 -19
- package/dist/jtcsv.esm.js.map +1 -1
- package/dist/jtcsv.umd.js +61 -19
- package/dist/jtcsv.umd.js.map +1 -1
- package/errors.js +188 -2
- package/examples/advanced/conditional-transformations.js +446 -0
- package/examples/advanced/conditional-transformations.ts +446 -0
- package/examples/advanced/csv-parser.worker.js +89 -0
- package/examples/advanced/csv-parser.worker.ts +89 -0
- package/examples/advanced/nested-objects-example.js +306 -0
- package/examples/advanced/nested-objects-example.ts +306 -0
- package/examples/advanced/performance-optimization.js +504 -0
- package/examples/advanced/performance-optimization.ts +504 -0
- package/examples/advanced/run-demo-server.js +116 -0
- package/examples/advanced/run-demo-server.ts +116 -0
- package/examples/advanced/web-worker-usage.html +874 -0
- package/examples/async-multithreaded-example.ts +335 -0
- package/examples/cli-advanced-usage.md +288 -0
- package/examples/cli-batch-processing.ts +38 -0
- package/examples/cli-tool.js +0 -3
- package/examples/cli-tool.ts +183 -0
- package/examples/error-handling.js +21 -7
- package/examples/error-handling.ts +356 -0
- package/examples/express-api.js +0 -3
- package/examples/express-api.ts +164 -0
- package/examples/large-dataset-example.js +0 -3
- package/examples/large-dataset-example.ts +204 -0
- package/examples/ndjson-processing.js +1 -1
- package/examples/ndjson-processing.ts +456 -0
- package/examples/plugin-excel-exporter.js +3 -4
- package/examples/plugin-excel-exporter.ts +406 -0
- package/examples/react-integration.tsx +637 -0
- package/examples/schema-validation.ts +640 -0
- package/examples/simple-usage.js +254 -254
- package/examples/simple-usage.ts +194 -0
- package/examples/streaming-example.js +4 -5
- package/examples/streaming-example.ts +419 -0
- package/examples/web-workers-advanced.ts +28 -0
- package/index.d.ts +1 -3
- package/index.js +15 -1
- package/json-save.js +9 -3
- package/json-to-csv.js +168 -21
- package/package.json +69 -10
- package/plugins/express-middleware/README.md +21 -2
- package/plugins/express-middleware/example.js +3 -4
- package/plugins/express-middleware/example.ts +135 -0
- package/plugins/express-middleware/index.d.ts +1 -1
- package/plugins/express-middleware/index.js +270 -118
- package/plugins/express-middleware/index.ts +557 -0
- package/plugins/fastify-plugin/index.js +2 -4
- package/plugins/fastify-plugin/index.ts +443 -0
- package/plugins/hono/index.ts +226 -0
- package/plugins/nestjs/index.ts +201 -0
- package/plugins/nextjs-api/examples/ConverterComponent.tsx +386 -0
- package/plugins/nextjs-api/examples/api-convert.js +0 -2
- package/plugins/nextjs-api/examples/api-convert.ts +67 -0
- package/plugins/nextjs-api/index.tsx +339 -0
- package/plugins/nextjs-api/route.js +2 -3
- package/plugins/nextjs-api/route.ts +370 -0
- package/plugins/nuxt/index.ts +94 -0
- package/plugins/nuxt/runtime/composables/useJtcsv.ts +100 -0
- package/plugins/nuxt/runtime/plugin.ts +71 -0
- package/plugins/remix/index.js +1 -1
- package/plugins/remix/index.ts +260 -0
- package/plugins/sveltekit/index.js +1 -1
- package/plugins/sveltekit/index.ts +301 -0
- package/plugins/trpc/index.ts +267 -0
- package/src/browser/browser-functions.ts +402 -0
- package/src/browser/core.js +92 -0
- package/src/browser/core.ts +152 -0
- package/src/browser/csv-to-json-browser.d.ts +3 -0
- package/src/browser/csv-to-json-browser.js +36 -14
- package/src/browser/csv-to-json-browser.ts +264 -0
- package/src/browser/errors-browser.ts +303 -0
- package/src/browser/extensions/plugins.js +92 -0
- package/src/browser/extensions/plugins.ts +93 -0
- package/src/browser/extensions/workers.js +39 -0
- package/src/browser/extensions/workers.ts +39 -0
- package/src/browser/globals.d.ts +5 -0
- package/src/browser/index.ts +192 -0
- package/src/browser/json-to-csv-browser.d.ts +3 -0
- package/src/browser/json-to-csv-browser.js +13 -3
- package/src/browser/json-to-csv-browser.ts +262 -0
- package/src/browser/streams.js +12 -2
- package/src/browser/streams.ts +336 -0
- package/src/browser/workers/csv-parser.worker.ts +377 -0
- package/src/browser/workers/worker-pool.ts +548 -0
- package/src/core/delimiter-cache.js +22 -8
- package/src/core/delimiter-cache.ts +310 -0
- package/src/core/node-optimizations.ts +449 -0
- package/src/core/plugin-system.js +29 -11
- package/src/core/plugin-system.ts +400 -0
- package/src/core/transform-hooks.ts +558 -0
- package/src/engines/fast-path-engine-new.ts +347 -0
- package/src/engines/fast-path-engine.ts +854 -0
- package/src/errors.ts +72 -0
- package/src/formats/ndjson-parser.ts +469 -0
- package/src/formats/tsv-parser.ts +334 -0
- package/src/index-with-plugins.js +16 -9
- package/src/index-with-plugins.ts +395 -0
- package/src/types/index.ts +255 -0
- package/src/utils/bom-utils.js +259 -0
- package/src/utils/bom-utils.ts +373 -0
- package/src/utils/encoding-support.js +124 -0
- package/src/utils/encoding-support.ts +155 -0
- package/src/utils/schema-validator.js +19 -19
- package/src/utils/schema-validator.ts +819 -0
- package/src/utils/transform-loader.js +1 -1
- package/src/utils/transform-loader.ts +389 -0
- package/src/utils/zod-adapter.js +170 -0
- package/src/utils/zod-adapter.ts +280 -0
- package/src/web-server/index.js +10 -10
- package/src/web-server/index.ts +683 -0
- package/src/workers/csv-multithreaded.ts +310 -0
- package/src/workers/csv-parser.worker.ts +227 -0
- package/src/workers/worker-pool.ts +409 -0
- package/stream-csv-to-json.js +26 -8
- package/stream-json-to-csv.js +1 -0
|
@@ -0,0 +1,347 @@
|
|
|
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
|
+
stats: {
|
|
12
|
+
simpleParserCount: number;
|
|
13
|
+
quoteAwareParserCount: number;
|
|
14
|
+
standardParserCount: number;
|
|
15
|
+
cacheHits: number;
|
|
16
|
+
cacheMisses: number;
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
constructor() {
|
|
20
|
+
this.compilers = new Map();
|
|
21
|
+
this.stats = {
|
|
22
|
+
simpleParserCount: 0,
|
|
23
|
+
quoteAwareParserCount: 0,
|
|
24
|
+
standardParserCount: 0,
|
|
25
|
+
cacheHits: 0,
|
|
26
|
+
cacheMisses: 0
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Анализирует структуру CSV и определяет оптимальный парсер
|
|
32
|
+
*/
|
|
33
|
+
analyzeStructure(sample, options: any = {}) {
|
|
34
|
+
const delimiter = options.delimiter || this._detectDelimiter(sample);
|
|
35
|
+
const lines = sample.split('\n').slice(0, 10);
|
|
36
|
+
|
|
37
|
+
let hasQuotes = false;
|
|
38
|
+
let hasNewlinesInFields = false;
|
|
39
|
+
let hasEscapedQuotes = false;
|
|
40
|
+
let maxFields = 0;
|
|
41
|
+
let totalFields = 0;
|
|
42
|
+
|
|
43
|
+
for (const line of lines) {
|
|
44
|
+
if (line.includes('"')) {
|
|
45
|
+
hasQuotes = true;
|
|
46
|
+
if (line.includes('""')) {
|
|
47
|
+
hasEscapedQuotes = true;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
const quoteCount = (line.match(/"/g) || []).length;
|
|
52
|
+
if (quoteCount % 2 !== 0) {
|
|
53
|
+
hasNewlinesInFields = true;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
const fieldCount = line.split(delimiter).length;
|
|
57
|
+
totalFields += fieldCount;
|
|
58
|
+
if (fieldCount > maxFields) {
|
|
59
|
+
maxFields = fieldCount;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
const avgFieldsPerLine = totalFields / lines.length;
|
|
64
|
+
const fieldConsistency = maxFields === avgFieldsPerLine;
|
|
65
|
+
|
|
66
|
+
return {
|
|
67
|
+
delimiter,
|
|
68
|
+
hasQuotes,
|
|
69
|
+
hasEscapedQuotes,
|
|
70
|
+
hasNewlinesInFields,
|
|
71
|
+
fieldConsistency,
|
|
72
|
+
avgFieldsPerLine,
|
|
73
|
+
maxFields,
|
|
74
|
+
recommendedEngine: this._selectEngine(hasQuotes, hasNewlinesInFields, fieldConsistency)
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Автоматически определяет разделитель
|
|
80
|
+
*/
|
|
81
|
+
_detectDelimiter(sample) {
|
|
82
|
+
const candidates = [',', ';', '\t', '|'];
|
|
83
|
+
const firstLine = sample.split('\n')[0];
|
|
84
|
+
|
|
85
|
+
let bestDelimiter = ',';
|
|
86
|
+
let bestScore = 0;
|
|
87
|
+
|
|
88
|
+
for (const delimiter of candidates) {
|
|
89
|
+
const fields = firstLine.split(delimiter);
|
|
90
|
+
const score = fields.length;
|
|
91
|
+
|
|
92
|
+
const avgLength = fields.reduce((sum, field) => sum + field.length, 0) / fields.length;
|
|
93
|
+
const variance = fields.reduce((sum, field) => sum + Math.pow(field.length - avgLength, 2), 0) / fields.length;
|
|
94
|
+
|
|
95
|
+
const finalScore = score / (variance + 1);
|
|
96
|
+
|
|
97
|
+
if (finalScore > bestScore) {
|
|
98
|
+
bestScore = finalScore;
|
|
99
|
+
bestDelimiter = delimiter;
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
return bestDelimiter;
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Выбирает оптимальный движок парсинга
|
|
108
|
+
*/
|
|
109
|
+
_selectEngine(hasQuotes, hasNewlinesInFields, fieldConsistency) {
|
|
110
|
+
if (!hasQuotes && !hasNewlinesInFields && fieldConsistency) {
|
|
111
|
+
return 'SIMPLE';
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
if (hasQuotes && !hasNewlinesInFields) {
|
|
115
|
+
return 'QUOTE_AWARE';
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
return 'STANDARD';
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
/**
|
|
122
|
+
* Компилирует специализированный парсер для конкретной структуры
|
|
123
|
+
*/
|
|
124
|
+
compileParser(structure) {
|
|
125
|
+
const cacheKey = JSON.stringify(structure);
|
|
126
|
+
|
|
127
|
+
if (this.compilers.has(cacheKey)) {
|
|
128
|
+
this.stats.cacheHits++;
|
|
129
|
+
return this.compilers.get(cacheKey);
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
this.stats.cacheMisses++;
|
|
133
|
+
let parser;
|
|
134
|
+
|
|
135
|
+
switch (structure.recommendedEngine) {
|
|
136
|
+
case 'SIMPLE':
|
|
137
|
+
parser = this._createSimpleParser(structure);
|
|
138
|
+
this.stats.simpleParserCount++;
|
|
139
|
+
break;
|
|
140
|
+
case 'QUOTE_AWARE':
|
|
141
|
+
parser = this._createQuoteAwareParser(structure);
|
|
142
|
+
this.stats.quoteAwareParserCount++;
|
|
143
|
+
break;
|
|
144
|
+
default:
|
|
145
|
+
parser = this._createStandardParser(structure);
|
|
146
|
+
this.stats.standardParserCount++;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
this.compilers.set(cacheKey, parser);
|
|
150
|
+
return parser;
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
/**
|
|
154
|
+
* Regex-based парсер для простых CSV (без кавычек)
|
|
155
|
+
*/
|
|
156
|
+
_createSimpleParser(structure) {
|
|
157
|
+
const { delimiter } = structure;
|
|
158
|
+
|
|
159
|
+
return (csv) => {
|
|
160
|
+
const rows = [];
|
|
161
|
+
const lines = csv.split('\n');
|
|
162
|
+
|
|
163
|
+
for (const line of lines) {
|
|
164
|
+
if (line.trim() === '') {
|
|
165
|
+
continue;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
const fields = line.split(delimiter).map(field => field.trim());
|
|
169
|
+
if (fields.length > 0 && fields.some(field => field !== '')) {
|
|
170
|
+
rows.push(fields);
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
return rows;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* State machine парсер для CSV с кавычками (RFC 4180)
|
|
180
|
+
*/
|
|
181
|
+
_createQuoteAwareParser(structure) {
|
|
182
|
+
const { delimiter, hasEscapedQuotes } = structure;
|
|
183
|
+
|
|
184
|
+
return (csv) => {
|
|
185
|
+
const rows = [];
|
|
186
|
+
let currentRow = [];
|
|
187
|
+
let currentField = '';
|
|
188
|
+
let insideQuotes = false;
|
|
189
|
+
let i = 0;
|
|
190
|
+
|
|
191
|
+
while (i < csv.length) {
|
|
192
|
+
const char = csv[i];
|
|
193
|
+
const nextChar = csv[i + 1];
|
|
194
|
+
|
|
195
|
+
if (char === '"') {
|
|
196
|
+
if (insideQuotes) {
|
|
197
|
+
if (nextChar === '"' && hasEscapedQuotes) {
|
|
198
|
+
currentField += '"';
|
|
199
|
+
i += 2;
|
|
200
|
+
} else {
|
|
201
|
+
insideQuotes = false;
|
|
202
|
+
i++;
|
|
203
|
+
}
|
|
204
|
+
} else {
|
|
205
|
+
insideQuotes = true;
|
|
206
|
+
i++;
|
|
207
|
+
}
|
|
208
|
+
} else if (char === delimiter && !insideQuotes) {
|
|
209
|
+
currentRow.push(currentField);
|
|
210
|
+
currentField = '';
|
|
211
|
+
i++;
|
|
212
|
+
} else if ((char === '\n' || (char === '\r' && nextChar === '\n')) && !insideQuotes) {
|
|
213
|
+
currentRow.push(currentField);
|
|
214
|
+
if (currentRow.length > 0 && currentRow.some(field => field !== '')) {
|
|
215
|
+
rows.push(currentRow);
|
|
216
|
+
}
|
|
217
|
+
currentRow = [];
|
|
218
|
+
currentField = '';
|
|
219
|
+
i += (char === '\r' && nextChar === '\n') ? 2 : 1;
|
|
220
|
+
} else {
|
|
221
|
+
currentField += char;
|
|
222
|
+
i++;
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
if (currentField !== '' || currentRow.length > 0) {
|
|
227
|
+
currentRow.push(currentField);
|
|
228
|
+
if (currentRow.length > 0 && currentRow.some(field => field !== '')) {
|
|
229
|
+
rows.push(currentRow);
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
return rows;
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
/**
|
|
238
|
+
* Стандартный парсер (fallback)
|
|
239
|
+
*/
|
|
240
|
+
_createStandardParser(structure) {
|
|
241
|
+
const { delimiter } = structure;
|
|
242
|
+
|
|
243
|
+
return (csv) => {
|
|
244
|
+
const rows = [];
|
|
245
|
+
const lines = csv.split('\n');
|
|
246
|
+
let insideQuotes = false;
|
|
247
|
+
let currentLine = '';
|
|
248
|
+
|
|
249
|
+
for (const line of lines) {
|
|
250
|
+
const quoteCount = (line.match(/"/g) || []).length;
|
|
251
|
+
|
|
252
|
+
if (insideQuotes) {
|
|
253
|
+
currentLine += '\n' + line;
|
|
254
|
+
if (quoteCount % 2 !== 0) {
|
|
255
|
+
insideQuotes = false;
|
|
256
|
+
rows.push(this._parseLineWithQuotes(currentLine, delimiter));
|
|
257
|
+
currentLine = '';
|
|
258
|
+
}
|
|
259
|
+
} else {
|
|
260
|
+
if (quoteCount % 2 !== 0) {
|
|
261
|
+
insideQuotes = true;
|
|
262
|
+
currentLine = line;
|
|
263
|
+
} else {
|
|
264
|
+
rows.push(this._parseLineWithQuotes(line, delimiter));
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
return rows;
|
|
270
|
+
};
|
|
271
|
+
}
|
|
272
|
+
|
|
273
|
+
/**
|
|
274
|
+
* Парсит строку с учетом кавычек
|
|
275
|
+
*/
|
|
276
|
+
_parseLineWithQuotes(line, delimiter) {
|
|
277
|
+
const fields = [];
|
|
278
|
+
let currentField = '';
|
|
279
|
+
let insideQuotes = false;
|
|
280
|
+
let i = 0;
|
|
281
|
+
|
|
282
|
+
while (i < line.length) {
|
|
283
|
+
const char = line[i];
|
|
284
|
+
const nextChar = line[i + 1];
|
|
285
|
+
|
|
286
|
+
if (char === '"') {
|
|
287
|
+
if (insideQuotes && nextChar === '"') {
|
|
288
|
+
currentField += '"';
|
|
289
|
+
i += 2;
|
|
290
|
+
} else {
|
|
291
|
+
insideQuotes = !insideQuotes;
|
|
292
|
+
i++;
|
|
293
|
+
}
|
|
294
|
+
} else if (char === delimiter && !insideQuotes) {
|
|
295
|
+
fields.push(currentField);
|
|
296
|
+
currentField = '';
|
|
297
|
+
i++;
|
|
298
|
+
} else {
|
|
299
|
+
currentField += char;
|
|
300
|
+
i++;
|
|
301
|
+
}
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
fields.push(currentField);
|
|
305
|
+
return fields;
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
/**
|
|
309
|
+
* Парсит CSV с использованием оптимального парсера
|
|
310
|
+
*/
|
|
311
|
+
parse(csv, options = {}) {
|
|
312
|
+
const sampleSize = Math.min(1000, csv.length);
|
|
313
|
+
const sample = csv.substring(0, sampleSize);
|
|
314
|
+
|
|
315
|
+
const structure = this.analyzeStructure(sample, options);
|
|
316
|
+
const parser = this.compileParser(structure);
|
|
317
|
+
|
|
318
|
+
return parser(csv);
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
/**
|
|
322
|
+
* Возвращает статистику использования парсеров
|
|
323
|
+
*/
|
|
324
|
+
getStats() {
|
|
325
|
+
return {
|
|
326
|
+
...this.stats,
|
|
327
|
+
totalParsers: this.compilers.size,
|
|
328
|
+
hitRate: this.stats.cacheHits / (this.stats.cacheHits + this.stats.cacheMisses) || 0
|
|
329
|
+
};
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
/**
|
|
333
|
+
* Сбрасывает статистику и кеш
|
|
334
|
+
*/
|
|
335
|
+
reset() {
|
|
336
|
+
this.compilers.clear();
|
|
337
|
+
this.stats = {
|
|
338
|
+
simpleParserCount: 0,
|
|
339
|
+
quoteAwareParserCount: 0,
|
|
340
|
+
standardParserCount: 0,
|
|
341
|
+
cacheHits: 0,
|
|
342
|
+
cacheMisses: 0
|
|
343
|
+
};
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
export default FastPathEngine;
|