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,92 @@
|
|
|
1
|
+
// Ядро jtcsv - только базовые функции JSON<->CSV
|
|
2
|
+
// Минимальный размер, максимальная производительность
|
|
3
|
+
|
|
4
|
+
import { jsonToCsv, preprocessData, deepUnwrap } from './json-to-csv-browser.js';
|
|
5
|
+
import { csvToJson, csvToJsonIterator, autoDetectDelimiter } from './csv-to-json-browser.js';
|
|
6
|
+
import {
|
|
7
|
+
downloadAsCsv,
|
|
8
|
+
parseCsvFile,
|
|
9
|
+
parseCsvFileStream,
|
|
10
|
+
jsonToCsvStream,
|
|
11
|
+
jsonToNdjsonStream,
|
|
12
|
+
csvToJsonStream
|
|
13
|
+
} from './browser-functions.js';
|
|
14
|
+
import {
|
|
15
|
+
ValidationError,
|
|
16
|
+
SecurityError,
|
|
17
|
+
FileSystemError,
|
|
18
|
+
ParsingError,
|
|
19
|
+
LimitError,
|
|
20
|
+
ConfigurationError,
|
|
21
|
+
ERROR_CODES
|
|
22
|
+
} from './errors-browser.js';
|
|
23
|
+
|
|
24
|
+
// Основной экспорт ядра
|
|
25
|
+
const jtcsvCore = {
|
|
26
|
+
// JSON to CSV функции
|
|
27
|
+
jsonToCsv,
|
|
28
|
+
preprocessData,
|
|
29
|
+
downloadAsCsv,
|
|
30
|
+
deepUnwrap,
|
|
31
|
+
|
|
32
|
+
// CSV to JSON функции
|
|
33
|
+
csvToJson,
|
|
34
|
+
csvToJsonIterator,
|
|
35
|
+
parseCsvFile,
|
|
36
|
+
parseCsvFileStream,
|
|
37
|
+
jsonToCsvStream,
|
|
38
|
+
jsonToNdjsonStream,
|
|
39
|
+
csvToJsonStream,
|
|
40
|
+
autoDetectDelimiter,
|
|
41
|
+
|
|
42
|
+
// Error classes
|
|
43
|
+
ValidationError,
|
|
44
|
+
SecurityError,
|
|
45
|
+
FileSystemError,
|
|
46
|
+
ParsingError,
|
|
47
|
+
LimitError,
|
|
48
|
+
ConfigurationError,
|
|
49
|
+
ERROR_CODES,
|
|
50
|
+
|
|
51
|
+
// Удобные алиасы
|
|
52
|
+
parse: csvToJson,
|
|
53
|
+
unparse: jsonToCsv,
|
|
54
|
+
|
|
55
|
+
// Версия
|
|
56
|
+
version: '3.0.0-core'
|
|
57
|
+
};
|
|
58
|
+
|
|
59
|
+
// Экспорт для разных сред
|
|
60
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
61
|
+
// Node.js CommonJS
|
|
62
|
+
module.exports = jtcsvCore;
|
|
63
|
+
} else if (typeof define === 'function' && define.amd) {
|
|
64
|
+
// AMD
|
|
65
|
+
define([], () => jtcsvCore);
|
|
66
|
+
} else if (typeof window !== 'undefined') {
|
|
67
|
+
// Браузер (глобальная переменная)
|
|
68
|
+
window.jtcsv = jtcsvCore;
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
export default jtcsvCore;
|
|
72
|
+
export {
|
|
73
|
+
jsonToCsv,
|
|
74
|
+
preprocessData,
|
|
75
|
+
downloadAsCsv,
|
|
76
|
+
deepUnwrap,
|
|
77
|
+
csvToJson,
|
|
78
|
+
csvToJsonIterator,
|
|
79
|
+
parseCsvFile,
|
|
80
|
+
parseCsvFileStream,
|
|
81
|
+
jsonToCsvStream,
|
|
82
|
+
jsonToNdjsonStream,
|
|
83
|
+
csvToJsonStream,
|
|
84
|
+
autoDetectDelimiter,
|
|
85
|
+
ValidationError,
|
|
86
|
+
SecurityError,
|
|
87
|
+
FileSystemError,
|
|
88
|
+
ParsingError,
|
|
89
|
+
LimitError,
|
|
90
|
+
ConfigurationError,
|
|
91
|
+
ERROR_CODES
|
|
92
|
+
};
|
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
// Ядро jtcsv - только базовые функции JSON<->CSV
|
|
2
|
+
// Минимальный размер, максимальная производительность
|
|
3
|
+
|
|
4
|
+
import * as jsonToCsvBrowser from './json-to-csv-browser';
|
|
5
|
+
import * as csvToJsonBrowser from './csv-to-json-browser';
|
|
6
|
+
import {
|
|
7
|
+
downloadAsCsv,
|
|
8
|
+
parseCsvFile,
|
|
9
|
+
parseCsvFileStream,
|
|
10
|
+
jsonToCsvStream,
|
|
11
|
+
jsonToNdjsonStream,
|
|
12
|
+
csvToJsonStream
|
|
13
|
+
} from './browser-functions';
|
|
14
|
+
import {
|
|
15
|
+
ValidationError,
|
|
16
|
+
SecurityError,
|
|
17
|
+
FileSystemError,
|
|
18
|
+
ParsingError,
|
|
19
|
+
LimitError,
|
|
20
|
+
ConfigurationError,
|
|
21
|
+
ERROR_CODES
|
|
22
|
+
} from './errors-browser';
|
|
23
|
+
|
|
24
|
+
import type { JsonToCsvOptions, CsvToJsonOptions } from '../types';
|
|
25
|
+
|
|
26
|
+
const { jsonToCsv, preprocessData, deepUnwrap } = jsonToCsvBrowser as any;
|
|
27
|
+
const { csvToJson, csvToJsonIterator, autoDetectDelimiter } = csvToJsonBrowser as any;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Асинхронная версия jsonToCsv
|
|
31
|
+
*/
|
|
32
|
+
async function jsonToCsvAsync(data: any, options: JsonToCsvOptions = {}): Promise<string> {
|
|
33
|
+
return jsonToCsv(data, options);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* Асинхронная версия csvToJson
|
|
38
|
+
*/
|
|
39
|
+
async function csvToJsonAsync(csv: string, options: CsvToJsonOptions = {}): Promise<any[]> {
|
|
40
|
+
return csvToJson(csv, options);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Асинхронная версия parseCsvFile
|
|
45
|
+
*/
|
|
46
|
+
async function parseCsvFileAsync(file: File, options: CsvToJsonOptions = {}): Promise<any[]> {
|
|
47
|
+
return parseCsvFile(file, options);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Асинхронная версия autoDetectDelimiter
|
|
52
|
+
*/
|
|
53
|
+
async function autoDetectDelimiterAsync(csv: string): Promise<string> {
|
|
54
|
+
return autoDetectDelimiter(csv);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Асинхронная версия downloadAsCsv
|
|
59
|
+
*/
|
|
60
|
+
async function downloadAsCsvAsync(
|
|
61
|
+
data: any,
|
|
62
|
+
filename: string = 'export.csv',
|
|
63
|
+
options: JsonToCsvOptions = {}
|
|
64
|
+
): Promise<void> {
|
|
65
|
+
return downloadAsCsv(data, filename, options);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Основной экспорт ядра
|
|
69
|
+
const jtcsvCore = {
|
|
70
|
+
// JSON to CSV функции
|
|
71
|
+
jsonToCsv,
|
|
72
|
+
preprocessData,
|
|
73
|
+
downloadAsCsv,
|
|
74
|
+
deepUnwrap,
|
|
75
|
+
|
|
76
|
+
// CSV to JSON функции
|
|
77
|
+
csvToJson,
|
|
78
|
+
csvToJsonIterator,
|
|
79
|
+
parseCsvFile,
|
|
80
|
+
parseCsvFileStream,
|
|
81
|
+
jsonToCsvStream,
|
|
82
|
+
jsonToNdjsonStream,
|
|
83
|
+
csvToJsonStream,
|
|
84
|
+
autoDetectDelimiter,
|
|
85
|
+
|
|
86
|
+
// Асинхронные функции
|
|
87
|
+
jsonToCsvAsync,
|
|
88
|
+
csvToJsonAsync,
|
|
89
|
+
parseCsvFileAsync,
|
|
90
|
+
autoDetectDelimiterAsync,
|
|
91
|
+
downloadAsCsvAsync,
|
|
92
|
+
|
|
93
|
+
// Error classes
|
|
94
|
+
ValidationError,
|
|
95
|
+
SecurityError,
|
|
96
|
+
FileSystemError,
|
|
97
|
+
ParsingError,
|
|
98
|
+
LimitError,
|
|
99
|
+
ConfigurationError,
|
|
100
|
+
ERROR_CODES,
|
|
101
|
+
|
|
102
|
+
// Удобные алиасы
|
|
103
|
+
parse: csvToJson,
|
|
104
|
+
unparse: jsonToCsv,
|
|
105
|
+
parseAsync: csvToJsonAsync,
|
|
106
|
+
unparseAsync: jsonToCsvAsync,
|
|
107
|
+
|
|
108
|
+
// Версия
|
|
109
|
+
version: '3.0.0-core'
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
// Экспорт для разных сред
|
|
113
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
114
|
+
// Node.js CommonJS
|
|
115
|
+
module.exports = jtcsvCore;
|
|
116
|
+
} else if (typeof define === 'function' && (define as any).amd) {
|
|
117
|
+
// AMD
|
|
118
|
+
(define as any)([], () => jtcsvCore);
|
|
119
|
+
} else if (typeof window !== 'undefined') {
|
|
120
|
+
// Браузер (глобальная переменная)
|
|
121
|
+
(window as any).jtcsv = jtcsvCore;
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
export default jtcsvCore;
|
|
125
|
+
export {
|
|
126
|
+
jsonToCsv,
|
|
127
|
+
preprocessData,
|
|
128
|
+
downloadAsCsv,
|
|
129
|
+
deepUnwrap,
|
|
130
|
+
csvToJson,
|
|
131
|
+
csvToJsonIterator,
|
|
132
|
+
parseCsvFile,
|
|
133
|
+
parseCsvFileStream,
|
|
134
|
+
jsonToCsvStream,
|
|
135
|
+
jsonToNdjsonStream,
|
|
136
|
+
csvToJsonStream,
|
|
137
|
+
autoDetectDelimiter,
|
|
138
|
+
// Асинхронные функции
|
|
139
|
+
jsonToCsvAsync,
|
|
140
|
+
csvToJsonAsync,
|
|
141
|
+
parseCsvFileAsync,
|
|
142
|
+
autoDetectDelimiterAsync,
|
|
143
|
+
downloadAsCsvAsync,
|
|
144
|
+
// Error classes
|
|
145
|
+
ValidationError,
|
|
146
|
+
SecurityError,
|
|
147
|
+
FileSystemError,
|
|
148
|
+
ParsingError,
|
|
149
|
+
LimitError,
|
|
150
|
+
ConfigurationError,
|
|
151
|
+
ERROR_CODES
|
|
152
|
+
};
|
|
@@ -191,10 +191,18 @@ function parseCsvValue(value, options) {
|
|
|
191
191
|
}
|
|
192
192
|
|
|
193
193
|
// Парсинг чисел
|
|
194
|
-
if (parseNumbers
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
194
|
+
if (parseNumbers) {
|
|
195
|
+
// Быстрая проверка числа: первый символ цифра, минус или точка
|
|
196
|
+
const trimmed = result.trim();
|
|
197
|
+
const firstChar = trimmed.charAt(0);
|
|
198
|
+
if ((firstChar >= '0' && firstChar <= '9') || firstChar === '-' || firstChar === '.') {
|
|
199
|
+
const num = parseFloat(trimmed);
|
|
200
|
+
if (!isNaN(num) && isFinite(num)) {
|
|
201
|
+
// Убедимся, что строка полностью соответствует числу (без лишних символов)
|
|
202
|
+
if (String(num) === trimmed || (trimmed.includes('.') && !isNaN(Number(trimmed)))) {
|
|
203
|
+
return num;
|
|
204
|
+
}
|
|
205
|
+
}
|
|
198
206
|
}
|
|
199
207
|
}
|
|
200
208
|
|
|
@@ -327,28 +335,42 @@ export function autoDetectDelimiter(csv, candidates = [';', ',', '\t', '|']) {
|
|
|
327
335
|
// Использование первой непустой строки для определения
|
|
328
336
|
const firstLine = lines[0];
|
|
329
337
|
|
|
338
|
+
// Быстрый подсчёт вхождений кандидатов за один проход
|
|
330
339
|
const counts = {};
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
const
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
340
|
+
const candidateSet = new Set(candidates);
|
|
341
|
+
for (let i = 0; i < firstLine.length; i++) {
|
|
342
|
+
const char = firstLine[i];
|
|
343
|
+
if (candidateSet.has(char)) {
|
|
344
|
+
counts[char] = (counts[char] || 0) + 1;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
// Убедимся, что все кандидаты присутствуют в counts (даже с нулём)
|
|
348
|
+
for (const delim of candidates) {
|
|
349
|
+
if (!(delim in counts)) {
|
|
350
|
+
counts[delim] = 0;
|
|
351
|
+
}
|
|
352
|
+
}
|
|
337
353
|
|
|
338
354
|
// Поиск разделителя с максимальным количеством
|
|
339
355
|
let maxCount = -1;
|
|
340
356
|
let detectedDelimiter = ';'; // значение по умолчанию
|
|
357
|
+
const maxDelimiters = [];
|
|
341
358
|
|
|
342
359
|
for (const [delim, count] of Object.entries(counts)) {
|
|
343
360
|
if (count > maxCount) {
|
|
344
361
|
maxCount = count;
|
|
345
|
-
|
|
362
|
+
maxDelimiters.length = 0;
|
|
363
|
+
maxDelimiters.push(delim);
|
|
364
|
+
} else if (count === maxCount) {
|
|
365
|
+
maxDelimiters.push(delim);
|
|
346
366
|
}
|
|
347
367
|
}
|
|
348
368
|
|
|
349
|
-
// Если разделитель не найден или
|
|
350
|
-
if (maxCount === 0) {
|
|
351
|
-
|
|
369
|
+
// Если разделитель не найден или есть ничья, возвращаем стандартный
|
|
370
|
+
if (maxCount === 0 || maxDelimiters.length > 1) {
|
|
371
|
+
detectedDelimiter = ';';
|
|
372
|
+
} else {
|
|
373
|
+
detectedDelimiter = maxDelimiters[0];
|
|
352
374
|
}
|
|
353
375
|
|
|
354
376
|
return detectedDelimiter;
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// Браузерная версия CSV to JSON конвертера
|
|
2
|
+
// Адаптирована для работы в браузере без Node.js API
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
ValidationError,
|
|
6
|
+
ParsingError,
|
|
7
|
+
LimitError,
|
|
8
|
+
ConfigurationError,
|
|
9
|
+
safeExecute
|
|
10
|
+
} from './errors-browser';
|
|
11
|
+
|
|
12
|
+
import type { CsvToJsonOptions } from '../types';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Валидация опций парсинга
|
|
16
|
+
* @private
|
|
17
|
+
*/
|
|
18
|
+
function validateCsvOptions(options: CsvToJsonOptions): boolean {
|
|
19
|
+
// Validate options
|
|
20
|
+
if (options && typeof options !== 'object') {
|
|
21
|
+
throw new ConfigurationError('Options must be an object');
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
// Validate delimiter
|
|
25
|
+
if (options?.delimiter && typeof options.delimiter !== 'string') {
|
|
26
|
+
throw new ConfigurationError('Delimiter must be a string');
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
if (options?.delimiter && options.delimiter.length !== 1) {
|
|
30
|
+
throw new ConfigurationError('Delimiter must be a single character');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Validate autoDetect
|
|
34
|
+
if (options?.autoDetect !== undefined && typeof options.autoDetect !== 'boolean') {
|
|
35
|
+
throw new ConfigurationError('autoDetect must be a boolean');
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Validate candidates
|
|
39
|
+
if (options?.candidates && !Array.isArray(options.candidates)) {
|
|
40
|
+
throw new ConfigurationError('candidates must be an array');
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Validate maxRows
|
|
44
|
+
if (options?.maxRows !== undefined && (typeof options.maxRows !== 'number' || options.maxRows <= 0)) {
|
|
45
|
+
throw new ConfigurationError('maxRows must be a positive number');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (options?.warnExtraFields !== undefined && typeof options.warnExtraFields !== 'boolean') {
|
|
49
|
+
throw new ConfigurationError('warnExtraFields must be a boolean');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
return true;
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/**
|
|
56
|
+
* Автоматическое определение разделителя
|
|
57
|
+
* @private
|
|
58
|
+
*/
|
|
59
|
+
function autoDetectDelimiter(text: string, candidates: string[] = [',', ';', '\t', '|']): string {
|
|
60
|
+
if (!text || typeof text !== 'string') {
|
|
61
|
+
return ',';
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const firstLine = text.split('\n')[0];
|
|
65
|
+
if (!firstLine) {
|
|
66
|
+
return ',';
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
let bestCandidate = ',';
|
|
70
|
+
let bestCount = 0;
|
|
71
|
+
|
|
72
|
+
for (const candidate of candidates) {
|
|
73
|
+
const count = (firstLine.match(new RegExp(candidate.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
|
|
74
|
+
if (count > bestCount) {
|
|
75
|
+
bestCount = count;
|
|
76
|
+
bestCandidate = candidate;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
return bestCandidate;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
/**
|
|
84
|
+
* Парсинг CSV строки в массив объектов
|
|
85
|
+
*
|
|
86
|
+
* @param csvText - CSV текст для парсинга
|
|
87
|
+
* @param options - Опции парсинга
|
|
88
|
+
* @returns Массив объектов
|
|
89
|
+
*/
|
|
90
|
+
export function csvToJson(csvText: string, options: CsvToJsonOptions = {}): any[] {
|
|
91
|
+
return safeExecute(() => {
|
|
92
|
+
validateCsvOptions(options);
|
|
93
|
+
|
|
94
|
+
if (typeof csvText !== 'string') {
|
|
95
|
+
throw new ValidationError('CSV text must be a string');
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
if (csvText.trim() === '') {
|
|
99
|
+
return [];
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
// Определение разделителя
|
|
103
|
+
const delimiter = options.delimiter ||
|
|
104
|
+
(options.autoDetect !== false ? autoDetectDelimiter(csvText, options.candidates) : ',');
|
|
105
|
+
|
|
106
|
+
// Разделение на строки
|
|
107
|
+
const lines = csvText.split('\n').filter(line => line.trim() !== '');
|
|
108
|
+
if (lines.length === 0) {
|
|
109
|
+
return [];
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Парсинг заголовков
|
|
113
|
+
const headers = lines[0].split(delimiter).map(h => h.trim());
|
|
114
|
+
|
|
115
|
+
// Ограничение количества строк
|
|
116
|
+
const maxRows = options.maxRows || Infinity;
|
|
117
|
+
const dataRows = lines.slice(1, Math.min(lines.length, maxRows + 1));
|
|
118
|
+
|
|
119
|
+
// Парсинг данных
|
|
120
|
+
const result = [];
|
|
121
|
+
|
|
122
|
+
for (let i = 0; i < dataRows.length; i++) {
|
|
123
|
+
const line = dataRows[i];
|
|
124
|
+
const values = line.split(delimiter);
|
|
125
|
+
const row: Record<string, any> = {};
|
|
126
|
+
|
|
127
|
+
for (let j = 0; j < headers.length; j++) {
|
|
128
|
+
const header = headers[j];
|
|
129
|
+
const value = j < values.length ? values[j].trim() : '';
|
|
130
|
+
|
|
131
|
+
// Попытка парсинга чисел
|
|
132
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) {
|
|
133
|
+
row[header] = parseFloat(value);
|
|
134
|
+
} else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
|
|
135
|
+
row[header] = value.toLowerCase() === 'true';
|
|
136
|
+
} else {
|
|
137
|
+
row[header] = value;
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
result.push(row);
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
return result;
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
/**
|
|
149
|
+
* Асинхронная версия csvToJson
|
|
150
|
+
*/
|
|
151
|
+
export async function csvToJsonAsync(csvText: string, options: CsvToJsonOptions = {}): Promise<any[]> {
|
|
152
|
+
return csvToJson(csvText, options);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/**
|
|
156
|
+
* Создает итератор для потокового парсинга CSV
|
|
157
|
+
*
|
|
158
|
+
* @param input - CSV текст, File или Blob
|
|
159
|
+
* @param options - Опции парсинга
|
|
160
|
+
* @returns AsyncGenerator
|
|
161
|
+
*/
|
|
162
|
+
export async function* csvToJsonIterator(input: string | File | Blob, options: CsvToJsonOptions = {}): AsyncGenerator<any> {
|
|
163
|
+
validateCsvOptions(options);
|
|
164
|
+
|
|
165
|
+
let csvText: string;
|
|
166
|
+
|
|
167
|
+
if (typeof input === 'string') {
|
|
168
|
+
csvText = input;
|
|
169
|
+
} else if (input instanceof File || input instanceof Blob) {
|
|
170
|
+
csvText = await input.text();
|
|
171
|
+
} else {
|
|
172
|
+
throw new ValidationError('Input must be string, File or Blob');
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
if (csvText.trim() === '') {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// Определение разделителя
|
|
180
|
+
const delimiter = options.delimiter ||
|
|
181
|
+
(options.autoDetect !== false ? autoDetectDelimiter(csvText, options.candidates) : ',');
|
|
182
|
+
|
|
183
|
+
// Разделение на строки
|
|
184
|
+
const lines = csvText.split('\n').filter(line => line.trim() !== '');
|
|
185
|
+
if (lines.length === 0) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
// Парсинг заголовков
|
|
190
|
+
const headers = lines[0].split(delimiter).map(h => h.trim());
|
|
191
|
+
|
|
192
|
+
// Ограничение количества строк
|
|
193
|
+
const maxRows = options.maxRows || Infinity;
|
|
194
|
+
const dataRows = lines.slice(1, Math.min(lines.length, maxRows + 1));
|
|
195
|
+
|
|
196
|
+
// Возврат данных по одной строке
|
|
197
|
+
for (let i = 0; i < dataRows.length; i++) {
|
|
198
|
+
const line = dataRows[i];
|
|
199
|
+
const values = line.split(delimiter);
|
|
200
|
+
const row: Record<string, any> = {};
|
|
201
|
+
|
|
202
|
+
for (let j = 0; j < headers.length; j++) {
|
|
203
|
+
const header = headers[j];
|
|
204
|
+
const value = j < values.length ? values[j].trim() : '';
|
|
205
|
+
|
|
206
|
+
// Попытка парсинга чисел
|
|
207
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) {
|
|
208
|
+
row[header] = parseFloat(value);
|
|
209
|
+
} else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
|
|
210
|
+
row[header] = value.toLowerCase() === 'true';
|
|
211
|
+
} else {
|
|
212
|
+
row[header] = value;
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
yield row;
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
/**
|
|
221
|
+
* Асинхронная версия csvToJsonIterator (псевдоним)
|
|
222
|
+
*/
|
|
223
|
+
export const csvToJsonIteratorAsync = csvToJsonIterator;
|
|
224
|
+
|
|
225
|
+
/**
|
|
226
|
+
* Парсинг CSV с обработкой ошибок
|
|
227
|
+
*
|
|
228
|
+
* @param csvText - CSV текст
|
|
229
|
+
* @param options - Опции парсинга
|
|
230
|
+
* @returns Результат парсинга или null при ошибке
|
|
231
|
+
*/
|
|
232
|
+
export function parseCsvSafe(csvText: string, options: CsvToJsonOptions = {}): any[] | null {
|
|
233
|
+
try {
|
|
234
|
+
return csvToJson(csvText, options);
|
|
235
|
+
} catch (error) {
|
|
236
|
+
console.error('CSV parsing error:', error);
|
|
237
|
+
return null;
|
|
238
|
+
}
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
/**
|
|
242
|
+
* Асинхронная версия parseCsvSafe
|
|
243
|
+
*/
|
|
244
|
+
export async function parseCsvSafeAsync(csvText: string, options: CsvToJsonOptions = {}): Promise<any[] | null> {
|
|
245
|
+
try {
|
|
246
|
+
return await csvToJsonAsync(csvText, options);
|
|
247
|
+
} catch (error) {
|
|
248
|
+
console.error('CSV parsing error:', error);
|
|
249
|
+
return null;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
// Экспорт для Node.js совместимости
|
|
254
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
255
|
+
module.exports = {
|
|
256
|
+
csvToJson,
|
|
257
|
+
csvToJsonAsync,
|
|
258
|
+
csvToJsonIterator,
|
|
259
|
+
csvToJsonIteratorAsync,
|
|
260
|
+
parseCsvSafe,
|
|
261
|
+
parseCsvSafeAsync,
|
|
262
|
+
autoDetectDelimiter
|
|
263
|
+
};
|
|
264
|
+
}
|