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,1379 @@
|
|
|
1
|
+
// Система ошибок для браузерной версии jtcsv
|
|
2
|
+
// Адаптирована для работы без Node.js специфичных API
|
|
3
|
+
/**
|
|
4
|
+
* Базовый класс ошибки jtcsv
|
|
5
|
+
*/
|
|
6
|
+
class JTCSVError extends Error {
|
|
7
|
+
constructor(message, code = 'JTCSV_ERROR', details = {}) {
|
|
8
|
+
super(message);
|
|
9
|
+
this.name = 'JTCSVError';
|
|
10
|
+
this.code = code;
|
|
11
|
+
this.details = details;
|
|
12
|
+
// Сохранение stack trace
|
|
13
|
+
if (Error.captureStackTrace) {
|
|
14
|
+
Error.captureStackTrace(this, JTCSVError);
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Ошибка валидации
|
|
20
|
+
*/
|
|
21
|
+
class ValidationError extends JTCSVError {
|
|
22
|
+
constructor(message, details = {}) {
|
|
23
|
+
super(message, 'VALIDATION_ERROR', details);
|
|
24
|
+
this.name = 'ValidationError';
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Ошибка безопасности
|
|
29
|
+
*/
|
|
30
|
+
class SecurityError extends JTCSVError {
|
|
31
|
+
constructor(message, details = {}) {
|
|
32
|
+
super(message, 'SECURITY_ERROR', details);
|
|
33
|
+
this.name = 'SecurityError';
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Ошибка файловой системы (адаптирована для браузера)
|
|
38
|
+
*/
|
|
39
|
+
class FileSystemError extends JTCSVError {
|
|
40
|
+
constructor(message, originalError, details = {}) {
|
|
41
|
+
super(message, 'FILE_SYSTEM_ERROR', { ...details, originalError });
|
|
42
|
+
this.name = 'FileSystemError';
|
|
43
|
+
if (originalError && originalError.code) {
|
|
44
|
+
this.code = originalError.code;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Ошибка парсинга
|
|
50
|
+
*/
|
|
51
|
+
class ParsingError extends JTCSVError {
|
|
52
|
+
constructor(message, lineNumber, details = {}) {
|
|
53
|
+
super(message, 'PARSING_ERROR', { ...details, lineNumber });
|
|
54
|
+
this.name = 'ParsingError';
|
|
55
|
+
this.lineNumber = lineNumber;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Ошибка превышения лимита
|
|
60
|
+
*/
|
|
61
|
+
class LimitError extends JTCSVError {
|
|
62
|
+
constructor(message, limit, actual, details = {}) {
|
|
63
|
+
super(message, 'LIMIT_ERROR', { ...details, limit, actual });
|
|
64
|
+
this.name = 'LimitError';
|
|
65
|
+
this.limit = limit;
|
|
66
|
+
this.actual = actual;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Ошибка конфигурации
|
|
71
|
+
*/
|
|
72
|
+
class ConfigurationError extends JTCSVError {
|
|
73
|
+
constructor(message, details = {}) {
|
|
74
|
+
super(message, 'CONFIGURATION_ERROR', details);
|
|
75
|
+
this.name = 'ConfigurationError';
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Коды ошибок
|
|
80
|
+
*/
|
|
81
|
+
const ERROR_CODES = {
|
|
82
|
+
JTCSV_ERROR: 'JTCSV_ERROR',
|
|
83
|
+
VALIDATION_ERROR: 'VALIDATION_ERROR',
|
|
84
|
+
SECURITY_ERROR: 'SECURITY_ERROR',
|
|
85
|
+
FILE_SYSTEM_ERROR: 'FILE_SYSTEM_ERROR',
|
|
86
|
+
PARSING_ERROR: 'PARSING_ERROR',
|
|
87
|
+
LIMIT_ERROR: 'LIMIT_ERROR',
|
|
88
|
+
CONFIGURATION_ERROR: 'CONFIGURATION_ERROR',
|
|
89
|
+
INVALID_INPUT: 'INVALID_INPUT',
|
|
90
|
+
SECURITY_VIOLATION: 'SECURITY_VIOLATION',
|
|
91
|
+
FILE_NOT_FOUND: 'FILE_NOT_FOUND',
|
|
92
|
+
PARSE_FAILED: 'PARSE_FAILED',
|
|
93
|
+
SIZE_LIMIT: 'SIZE_LIMIT',
|
|
94
|
+
INVALID_CONFIG: 'INVALID_CONFIG',
|
|
95
|
+
UNKNOWN_ERROR: 'UNKNOWN_ERROR'
|
|
96
|
+
};
|
|
97
|
+
/**
|
|
98
|
+
* Безопасное выполнение функции с обработкой ошибок
|
|
99
|
+
*
|
|
100
|
+
* @param fn - Функция для выполнения
|
|
101
|
+
* @param errorCode - Код ошибки по умолчанию
|
|
102
|
+
* @param errorDetails - Детали ошибки
|
|
103
|
+
* @returns Результат выполнения функции
|
|
104
|
+
*/
|
|
105
|
+
function safeExecute(fn, errorCode = 'UNKNOWN_ERROR', errorDetails = {}) {
|
|
106
|
+
try {
|
|
107
|
+
if (typeof fn === 'function') {
|
|
108
|
+
return fn();
|
|
109
|
+
}
|
|
110
|
+
throw new ValidationError('Function expected');
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
// Если ошибка уже является JTCSVError, перебросить её
|
|
114
|
+
if (error instanceof JTCSVError) {
|
|
115
|
+
throw error;
|
|
116
|
+
}
|
|
117
|
+
// Определить тип ошибки на основе сообщения или кода
|
|
118
|
+
let enhancedError;
|
|
119
|
+
const errorMessage = error.message || String(error);
|
|
120
|
+
if (errorMessage.includes('validation') || errorMessage.includes('Validation')) {
|
|
121
|
+
enhancedError = new ValidationError(errorMessage, { ...errorDetails, originalError: error });
|
|
122
|
+
}
|
|
123
|
+
else if (errorMessage.includes('security') || errorMessage.includes('Security')) {
|
|
124
|
+
enhancedError = new SecurityError(errorMessage, { ...errorDetails, originalError: error });
|
|
125
|
+
}
|
|
126
|
+
else if (errorMessage.includes('parsing') || errorMessage.includes('Parsing')) {
|
|
127
|
+
enhancedError = new ParsingError(errorMessage, undefined, { ...errorDetails, originalError: error });
|
|
128
|
+
}
|
|
129
|
+
else if (errorMessage.includes('limit') || errorMessage.includes('Limit')) {
|
|
130
|
+
enhancedError = new LimitError(errorMessage, null, null, { ...errorDetails, originalError: error });
|
|
131
|
+
}
|
|
132
|
+
else if (errorMessage.includes('configuration') || errorMessage.includes('Configuration')) {
|
|
133
|
+
enhancedError = new ConfigurationError(errorMessage, { ...errorDetails, originalError: error });
|
|
134
|
+
}
|
|
135
|
+
else if (errorMessage.includes('file') || errorMessage.includes('File')) {
|
|
136
|
+
enhancedError = new FileSystemError(errorMessage, error, errorDetails);
|
|
137
|
+
}
|
|
138
|
+
else {
|
|
139
|
+
// Общая ошибка
|
|
140
|
+
enhancedError = new JTCSVError(errorMessage, errorCode, { ...errorDetails, originalError: error });
|
|
141
|
+
}
|
|
142
|
+
// Сохранить оригинальный stack trace если возможно
|
|
143
|
+
if (error.stack) {
|
|
144
|
+
enhancedError.stack = error.stack;
|
|
145
|
+
}
|
|
146
|
+
throw enhancedError;
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
/**
|
|
150
|
+
* Асинхронная версия safeExecute
|
|
151
|
+
*/
|
|
152
|
+
async function safeExecuteAsync(fn, errorCode = 'UNKNOWN_ERROR', errorDetails = {}) {
|
|
153
|
+
try {
|
|
154
|
+
if (typeof fn === 'function') {
|
|
155
|
+
return await fn();
|
|
156
|
+
}
|
|
157
|
+
throw new ValidationError('Function expected');
|
|
158
|
+
}
|
|
159
|
+
catch (error) {
|
|
160
|
+
// Если ошибка уже является JTCSVError, перебросить её
|
|
161
|
+
if (error instanceof JTCSVError) {
|
|
162
|
+
throw error;
|
|
163
|
+
}
|
|
164
|
+
// Определить тип ошибки
|
|
165
|
+
let enhancedError;
|
|
166
|
+
const errorMessage = error.message || String(error);
|
|
167
|
+
if (errorMessage.includes('validation') || errorMessage.includes('Validation')) {
|
|
168
|
+
enhancedError = new ValidationError(errorMessage, { ...errorDetails, originalError: error });
|
|
169
|
+
}
|
|
170
|
+
else if (errorMessage.includes('security') || errorMessage.includes('Security')) {
|
|
171
|
+
enhancedError = new SecurityError(errorMessage, { ...errorDetails, originalError: error });
|
|
172
|
+
}
|
|
173
|
+
else if (errorMessage.includes('parsing') || errorMessage.includes('Parsing')) {
|
|
174
|
+
enhancedError = new ParsingError(errorMessage, undefined, { ...errorDetails, originalError: error });
|
|
175
|
+
}
|
|
176
|
+
else if (errorMessage.includes('limit') || errorMessage.includes('Limit')) {
|
|
177
|
+
enhancedError = new LimitError(errorMessage, null, null, { ...errorDetails, originalError: error });
|
|
178
|
+
}
|
|
179
|
+
else if (errorMessage.includes('configuration') || errorMessage.includes('Configuration')) {
|
|
180
|
+
enhancedError = new ConfigurationError(errorMessage, { ...errorDetails, originalError: error });
|
|
181
|
+
}
|
|
182
|
+
else if (errorMessage.includes('file') || errorMessage.includes('File')) {
|
|
183
|
+
enhancedError = new FileSystemError(errorMessage, error, errorDetails);
|
|
184
|
+
}
|
|
185
|
+
else {
|
|
186
|
+
enhancedError = new JTCSVError(errorMessage, errorCode, { ...errorDetails, originalError: error });
|
|
187
|
+
}
|
|
188
|
+
if (error.stack) {
|
|
189
|
+
enhancedError.stack = error.stack;
|
|
190
|
+
}
|
|
191
|
+
throw enhancedError;
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
/**
|
|
195
|
+
* Создать сообщение об ошибке
|
|
196
|
+
*/
|
|
197
|
+
function createErrorMessage(error, includeStack = false) {
|
|
198
|
+
let message = error.message || 'Unknown error';
|
|
199
|
+
if (error instanceof JTCSVError) {
|
|
200
|
+
message = `[${error.code}] ${message}`;
|
|
201
|
+
if (error instanceof ParsingError && error.lineNumber) {
|
|
202
|
+
message += ` (line ${error.lineNumber})`;
|
|
203
|
+
}
|
|
204
|
+
if (error instanceof LimitError && error.limit && error.actual) {
|
|
205
|
+
message += ` (limit: ${error.limit}, actual: ${error.actual})`;
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
if (includeStack && error.stack) {
|
|
209
|
+
message += `\n${error.stack}`;
|
|
210
|
+
}
|
|
211
|
+
return message;
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Обработка ошибки
|
|
215
|
+
*/
|
|
216
|
+
function handleError(error, options = {}) {
|
|
217
|
+
const { log = true, throw: shouldThrow = false, format = true } = options;
|
|
218
|
+
const message = format ? createErrorMessage(error) : error.message;
|
|
219
|
+
if (log) {
|
|
220
|
+
console.error(`[jtcsv] ${message}`);
|
|
221
|
+
if (error instanceof JTCSVError && error.details) {
|
|
222
|
+
console.error('Error details:', error.details);
|
|
223
|
+
}
|
|
224
|
+
}
|
|
225
|
+
if (shouldThrow) {
|
|
226
|
+
throw error;
|
|
227
|
+
}
|
|
228
|
+
return message;
|
|
229
|
+
}
|
|
230
|
+
// Экспорт для Node.js совместимости
|
|
231
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
232
|
+
module.exports = {
|
|
233
|
+
JTCSVError,
|
|
234
|
+
ValidationError,
|
|
235
|
+
SecurityError,
|
|
236
|
+
FileSystemError,
|
|
237
|
+
ParsingError,
|
|
238
|
+
LimitError,
|
|
239
|
+
ConfigurationError,
|
|
240
|
+
ERROR_CODES,
|
|
241
|
+
safeExecute,
|
|
242
|
+
safeExecuteAsync,
|
|
243
|
+
createErrorMessage,
|
|
244
|
+
handleError
|
|
245
|
+
};
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
// Браузерная версия JSON to CSV конвертера
|
|
249
|
+
// Адаптирована для работы в браузере без Node.js API
|
|
250
|
+
/**
|
|
251
|
+
* Валидация входных данных и опций
|
|
252
|
+
* @private
|
|
253
|
+
*/
|
|
254
|
+
function validateInput(data, options) {
|
|
255
|
+
// Validate data
|
|
256
|
+
if (!Array.isArray(data)) {
|
|
257
|
+
throw new ValidationError('Input data must be an array');
|
|
258
|
+
}
|
|
259
|
+
// Validate options
|
|
260
|
+
if (options && typeof options !== 'object') {
|
|
261
|
+
throw new ConfigurationError('Options must be an object');
|
|
262
|
+
}
|
|
263
|
+
// Validate delimiter
|
|
264
|
+
if (options?.delimiter && typeof options.delimiter !== 'string') {
|
|
265
|
+
throw new ConfigurationError('Delimiter must be a string');
|
|
266
|
+
}
|
|
267
|
+
if (options?.delimiter && options.delimiter.length !== 1) {
|
|
268
|
+
throw new ConfigurationError('Delimiter must be a single character');
|
|
269
|
+
}
|
|
270
|
+
// Validate renameMap
|
|
271
|
+
if (options?.renameMap && typeof options.renameMap !== 'object') {
|
|
272
|
+
throw new ConfigurationError('renameMap must be an object');
|
|
273
|
+
}
|
|
274
|
+
// Validate maxRecords
|
|
275
|
+
if (options && options.maxRecords !== undefined) {
|
|
276
|
+
if (typeof options.maxRecords !== 'number' || options.maxRecords <= 0) {
|
|
277
|
+
throw new ConfigurationError('maxRecords must be a positive number');
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
// Validate preventCsvInjection
|
|
281
|
+
if (options?.preventCsvInjection !== undefined && typeof options.preventCsvInjection !== 'boolean') {
|
|
282
|
+
throw new ConfigurationError('preventCsvInjection must be a boolean');
|
|
283
|
+
}
|
|
284
|
+
// Validate rfc4180Compliant
|
|
285
|
+
if (options?.rfc4180Compliant !== undefined && typeof options.rfc4180Compliant !== 'boolean') {
|
|
286
|
+
throw new ConfigurationError('rfc4180Compliant must be a boolean');
|
|
287
|
+
}
|
|
288
|
+
return true;
|
|
289
|
+
}
|
|
290
|
+
/**
|
|
291
|
+
* Экранирование CSV значений для предотвращения инъекций
|
|
292
|
+
* @private
|
|
293
|
+
*/
|
|
294
|
+
function escapeCsvValue(value, preventInjection = true) {
|
|
295
|
+
if (value === null || value === undefined) {
|
|
296
|
+
return '';
|
|
297
|
+
}
|
|
298
|
+
const str = String(value);
|
|
299
|
+
// Экранирование формул для предотвращения CSV инъекций
|
|
300
|
+
if (preventInjection && /^[=+\-@]/.test(str)) {
|
|
301
|
+
return "'" + str;
|
|
302
|
+
}
|
|
303
|
+
// Экранирование кавычек и переносов строк
|
|
304
|
+
if (str.includes('"') || str.includes('\n') || str.includes('\r') || str.includes(',')) {
|
|
305
|
+
return '"' + str.replace(/"/g, '""') + '"';
|
|
306
|
+
}
|
|
307
|
+
return str;
|
|
308
|
+
}
|
|
309
|
+
/**
|
|
310
|
+
* Извлечение всех уникальных ключей из массива объектов
|
|
311
|
+
* @private
|
|
312
|
+
*/
|
|
313
|
+
function extractAllKeys(data) {
|
|
314
|
+
const keys = new Set();
|
|
315
|
+
for (const item of data) {
|
|
316
|
+
if (item && typeof item === 'object') {
|
|
317
|
+
Object.keys(item).forEach(key => keys.add(key));
|
|
318
|
+
}
|
|
319
|
+
}
|
|
320
|
+
return Array.from(keys);
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Конвертация массива объектов в CSV строку
|
|
324
|
+
*
|
|
325
|
+
* @param data - Массив объектов для конвертации
|
|
326
|
+
* @param options - Опции конвертации
|
|
327
|
+
* @returns CSV строка
|
|
328
|
+
*/
|
|
329
|
+
function jsonToCsv$1(data, options = {}) {
|
|
330
|
+
return safeExecute(() => {
|
|
331
|
+
validateInput(data, options);
|
|
332
|
+
if (data.length === 0) {
|
|
333
|
+
return '';
|
|
334
|
+
}
|
|
335
|
+
// Настройки по умолчанию
|
|
336
|
+
const delimiter = options.delimiter || ';';
|
|
337
|
+
const includeHeaders = options.includeHeaders !== false;
|
|
338
|
+
const maxRecords = options.maxRecords || data.length;
|
|
339
|
+
const preventInjection = options.preventCsvInjection !== false;
|
|
340
|
+
const rfc4180Compliant = options.rfc4180Compliant !== false;
|
|
341
|
+
// Ограничение количества записей
|
|
342
|
+
const limitedData = data.slice(0, maxRecords);
|
|
343
|
+
// Извлечение всех ключей
|
|
344
|
+
const allKeys = extractAllKeys(limitedData);
|
|
345
|
+
// Применение renameMap если есть
|
|
346
|
+
const renameMap = options.renameMap || {};
|
|
347
|
+
const finalKeys = allKeys.map(key => renameMap[key] || key);
|
|
348
|
+
// Создание CSV строки
|
|
349
|
+
const lines = [];
|
|
350
|
+
// Заголовки
|
|
351
|
+
if (includeHeaders) {
|
|
352
|
+
const headerLine = finalKeys.map(key => escapeCsvValue(key, preventInjection)).join(delimiter);
|
|
353
|
+
lines.push(headerLine);
|
|
354
|
+
}
|
|
355
|
+
// Данные
|
|
356
|
+
for (const item of limitedData) {
|
|
357
|
+
const rowValues = allKeys.map(key => {
|
|
358
|
+
const value = item?.[key];
|
|
359
|
+
return escapeCsvValue(value, preventInjection);
|
|
360
|
+
});
|
|
361
|
+
lines.push(rowValues.join(delimiter));
|
|
362
|
+
}
|
|
363
|
+
// RFC 4180 compliance: CRLF line endings
|
|
364
|
+
if (rfc4180Compliant) {
|
|
365
|
+
return lines.join('\r\n');
|
|
366
|
+
}
|
|
367
|
+
return lines.join('\n');
|
|
368
|
+
});
|
|
369
|
+
}
|
|
370
|
+
/**
|
|
371
|
+
* Асинхронная версия jsonToCsv
|
|
372
|
+
*/
|
|
373
|
+
async function jsonToCsvAsync$1(data, options = {}) {
|
|
374
|
+
return jsonToCsv$1(data, options);
|
|
375
|
+
}
|
|
376
|
+
/**
|
|
377
|
+
* Создает итератор для потоковой конвертации JSON в CSV
|
|
378
|
+
*
|
|
379
|
+
* @param data - Массив объектов или async итератор
|
|
380
|
+
* @param options - Опции конвертации
|
|
381
|
+
* @returns AsyncIterator с CSV чанками
|
|
382
|
+
*/
|
|
383
|
+
async function* jsonToCsvIterator(data, options = {}) {
|
|
384
|
+
validateInput(Array.isArray(data) ? data : [], options);
|
|
385
|
+
const delimiter = options.delimiter || ';';
|
|
386
|
+
const includeHeaders = options.includeHeaders !== false;
|
|
387
|
+
const preventInjection = options.preventCsvInjection !== false;
|
|
388
|
+
const rfc4180Compliant = options.rfc4180Compliant !== false;
|
|
389
|
+
let allKeys = [];
|
|
390
|
+
let renameMap = {};
|
|
391
|
+
// Если данные - массив, обрабатываем как массив
|
|
392
|
+
if (Array.isArray(data)) {
|
|
393
|
+
if (data.length === 0) {
|
|
394
|
+
return;
|
|
395
|
+
}
|
|
396
|
+
allKeys = extractAllKeys(data);
|
|
397
|
+
renameMap = options.renameMap || {};
|
|
398
|
+
const finalKeys = allKeys.map(key => renameMap[key] || key);
|
|
399
|
+
// Заголовки
|
|
400
|
+
if (includeHeaders) {
|
|
401
|
+
const headerLine = finalKeys.map(key => escapeCsvValue(key, preventInjection)).join(delimiter);
|
|
402
|
+
yield headerLine + (rfc4180Compliant ? '\r\n' : '\n');
|
|
403
|
+
}
|
|
404
|
+
// Данные
|
|
405
|
+
for (const item of data) {
|
|
406
|
+
const rowValues = allKeys.map(key => {
|
|
407
|
+
const value = item?.[key];
|
|
408
|
+
return escapeCsvValue(value, preventInjection);
|
|
409
|
+
});
|
|
410
|
+
yield rowValues.join(delimiter) + (rfc4180Compliant ? '\r\n' : '\n');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
413
|
+
else {
|
|
414
|
+
// Для async итератора нужна другая логика
|
|
415
|
+
throw new ValidationError('Async iterators not yet implemented in browser version');
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Асинхронная версия jsonToCsvIterator (псевдоним)
|
|
420
|
+
*/
|
|
421
|
+
const jsonToCsvIteratorAsync = jsonToCsvIterator;
|
|
422
|
+
/**
|
|
423
|
+
* Безопасная конвертация с обработкой ошибок
|
|
424
|
+
*
|
|
425
|
+
* @param data - Массив объектов
|
|
426
|
+
* @param options - Опции конвертации
|
|
427
|
+
* @returns CSV строка или null при ошибке
|
|
428
|
+
*/
|
|
429
|
+
function jsonToCsvSafe(data, options = {}) {
|
|
430
|
+
try {
|
|
431
|
+
return jsonToCsv$1(data, options);
|
|
432
|
+
}
|
|
433
|
+
catch (error) {
|
|
434
|
+
console.error('JSON to CSV conversion error:', error);
|
|
435
|
+
return null;
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Асинхронная версия jsonToCsvSafe
|
|
440
|
+
*/
|
|
441
|
+
async function jsonToCsvSafeAsync(data, options = {}) {
|
|
442
|
+
try {
|
|
443
|
+
return await jsonToCsvAsync$1(data, options);
|
|
444
|
+
}
|
|
445
|
+
catch (error) {
|
|
446
|
+
console.error('JSON to CSV conversion error:', error);
|
|
447
|
+
return null;
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
// Экспорт для Node.js совместимости
|
|
451
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
452
|
+
module.exports = {
|
|
453
|
+
jsonToCsv: jsonToCsv$1,
|
|
454
|
+
jsonToCsvAsync: jsonToCsvAsync$1,
|
|
455
|
+
jsonToCsvIterator,
|
|
456
|
+
jsonToCsvIteratorAsync,
|
|
457
|
+
jsonToCsvSafe,
|
|
458
|
+
jsonToCsvSafeAsync
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
var jsonToCsvBrowser = /*#__PURE__*/Object.freeze({
|
|
463
|
+
__proto__: null,
|
|
464
|
+
jsonToCsv: jsonToCsv$1,
|
|
465
|
+
jsonToCsvAsync: jsonToCsvAsync$1,
|
|
466
|
+
jsonToCsvIterator: jsonToCsvIterator,
|
|
467
|
+
jsonToCsvIteratorAsync: jsonToCsvIteratorAsync,
|
|
468
|
+
jsonToCsvSafe: jsonToCsvSafe,
|
|
469
|
+
jsonToCsvSafeAsync: jsonToCsvSafeAsync
|
|
470
|
+
});
|
|
471
|
+
|
|
472
|
+
// Браузерная версия CSV to JSON конвертера
|
|
473
|
+
// Адаптирована для работы в браузере без Node.js API
|
|
474
|
+
/**
|
|
475
|
+
* Валидация опций парсинга
|
|
476
|
+
* @private
|
|
477
|
+
*/
|
|
478
|
+
function validateCsvOptions(options) {
|
|
479
|
+
// Validate options
|
|
480
|
+
if (options && typeof options !== 'object') {
|
|
481
|
+
throw new ConfigurationError('Options must be an object');
|
|
482
|
+
}
|
|
483
|
+
// Validate delimiter
|
|
484
|
+
if (options?.delimiter && typeof options.delimiter !== 'string') {
|
|
485
|
+
throw new ConfigurationError('Delimiter must be a string');
|
|
486
|
+
}
|
|
487
|
+
if (options?.delimiter && options.delimiter.length !== 1) {
|
|
488
|
+
throw new ConfigurationError('Delimiter must be a single character');
|
|
489
|
+
}
|
|
490
|
+
// Validate autoDetect
|
|
491
|
+
if (options?.autoDetect !== undefined && typeof options.autoDetect !== 'boolean') {
|
|
492
|
+
throw new ConfigurationError('autoDetect must be a boolean');
|
|
493
|
+
}
|
|
494
|
+
// Validate candidates
|
|
495
|
+
if (options?.candidates && !Array.isArray(options.candidates)) {
|
|
496
|
+
throw new ConfigurationError('candidates must be an array');
|
|
497
|
+
}
|
|
498
|
+
// Validate maxRows
|
|
499
|
+
if (options?.maxRows !== undefined && (typeof options.maxRows !== 'number' || options.maxRows <= 0)) {
|
|
500
|
+
throw new ConfigurationError('maxRows must be a positive number');
|
|
501
|
+
}
|
|
502
|
+
if (options?.warnExtraFields !== undefined && typeof options.warnExtraFields !== 'boolean') {
|
|
503
|
+
throw new ConfigurationError('warnExtraFields must be a boolean');
|
|
504
|
+
}
|
|
505
|
+
return true;
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Автоматическое определение разделителя
|
|
509
|
+
* @private
|
|
510
|
+
*/
|
|
511
|
+
function autoDetectDelimiter$1(text, candidates = [',', ';', '\t', '|']) {
|
|
512
|
+
if (!text || typeof text !== 'string') {
|
|
513
|
+
return ',';
|
|
514
|
+
}
|
|
515
|
+
const firstLine = text.split('\n')[0];
|
|
516
|
+
if (!firstLine) {
|
|
517
|
+
return ',';
|
|
518
|
+
}
|
|
519
|
+
let bestCandidate = ',';
|
|
520
|
+
let bestCount = 0;
|
|
521
|
+
for (const candidate of candidates) {
|
|
522
|
+
const count = (firstLine.match(new RegExp(candidate.replace(/[.*+?^${}()|[\]\\]/g, '\\$&'), 'g')) || []).length;
|
|
523
|
+
if (count > bestCount) {
|
|
524
|
+
bestCount = count;
|
|
525
|
+
bestCandidate = candidate;
|
|
526
|
+
}
|
|
527
|
+
}
|
|
528
|
+
return bestCandidate;
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Парсинг CSV строки в массив объектов
|
|
532
|
+
*
|
|
533
|
+
* @param csvText - CSV текст для парсинга
|
|
534
|
+
* @param options - Опции парсинга
|
|
535
|
+
* @returns Массив объектов
|
|
536
|
+
*/
|
|
537
|
+
function csvToJson$1(csvText, options = {}) {
|
|
538
|
+
return safeExecute(() => {
|
|
539
|
+
validateCsvOptions(options);
|
|
540
|
+
if (typeof csvText !== 'string') {
|
|
541
|
+
throw new ValidationError('CSV text must be a string');
|
|
542
|
+
}
|
|
543
|
+
if (csvText.trim() === '') {
|
|
544
|
+
return [];
|
|
545
|
+
}
|
|
546
|
+
// Определение разделителя
|
|
547
|
+
const delimiter = options.delimiter ||
|
|
548
|
+
(options.autoDetect !== false ? autoDetectDelimiter$1(csvText, options.candidates) : ',');
|
|
549
|
+
// Разделение на строки
|
|
550
|
+
const lines = csvText.split('\n').filter(line => line.trim() !== '');
|
|
551
|
+
if (lines.length === 0) {
|
|
552
|
+
return [];
|
|
553
|
+
}
|
|
554
|
+
// Парсинг заголовков
|
|
555
|
+
const headers = lines[0].split(delimiter).map(h => h.trim());
|
|
556
|
+
// Ограничение количества строк
|
|
557
|
+
const maxRows = options.maxRows || Infinity;
|
|
558
|
+
const dataRows = lines.slice(1, Math.min(lines.length, maxRows + 1));
|
|
559
|
+
// Парсинг данных
|
|
560
|
+
const result = [];
|
|
561
|
+
for (let i = 0; i < dataRows.length; i++) {
|
|
562
|
+
const line = dataRows[i];
|
|
563
|
+
const values = line.split(delimiter);
|
|
564
|
+
const row = {};
|
|
565
|
+
for (let j = 0; j < headers.length; j++) {
|
|
566
|
+
const header = headers[j];
|
|
567
|
+
const value = j < values.length ? values[j].trim() : '';
|
|
568
|
+
// Попытка парсинга чисел
|
|
569
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) {
|
|
570
|
+
row[header] = parseFloat(value);
|
|
571
|
+
}
|
|
572
|
+
else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
|
|
573
|
+
row[header] = value.toLowerCase() === 'true';
|
|
574
|
+
}
|
|
575
|
+
else {
|
|
576
|
+
row[header] = value;
|
|
577
|
+
}
|
|
578
|
+
}
|
|
579
|
+
result.push(row);
|
|
580
|
+
}
|
|
581
|
+
return result;
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
/**
|
|
585
|
+
* Асинхронная версия csvToJson
|
|
586
|
+
*/
|
|
587
|
+
async function csvToJsonAsync$1(csvText, options = {}) {
|
|
588
|
+
return csvToJson$1(csvText, options);
|
|
589
|
+
}
|
|
590
|
+
/**
|
|
591
|
+
* Создает итератор для потокового парсинга CSV
|
|
592
|
+
*
|
|
593
|
+
* @param input - CSV текст, File или Blob
|
|
594
|
+
* @param options - Опции парсинга
|
|
595
|
+
* @returns AsyncGenerator
|
|
596
|
+
*/
|
|
597
|
+
async function* csvToJsonIterator$1(input, options = {}) {
|
|
598
|
+
validateCsvOptions(options);
|
|
599
|
+
let csvText;
|
|
600
|
+
if (typeof input === 'string') {
|
|
601
|
+
csvText = input;
|
|
602
|
+
}
|
|
603
|
+
else if (input instanceof File || input instanceof Blob) {
|
|
604
|
+
csvText = await input.text();
|
|
605
|
+
}
|
|
606
|
+
else {
|
|
607
|
+
throw new ValidationError('Input must be string, File or Blob');
|
|
608
|
+
}
|
|
609
|
+
if (csvText.trim() === '') {
|
|
610
|
+
return;
|
|
611
|
+
}
|
|
612
|
+
// Определение разделителя
|
|
613
|
+
const delimiter = options.delimiter ||
|
|
614
|
+
(options.autoDetect !== false ? autoDetectDelimiter$1(csvText, options.candidates) : ',');
|
|
615
|
+
// Разделение на строки
|
|
616
|
+
const lines = csvText.split('\n').filter(line => line.trim() !== '');
|
|
617
|
+
if (lines.length === 0) {
|
|
618
|
+
return;
|
|
619
|
+
}
|
|
620
|
+
// Парсинг заголовков
|
|
621
|
+
const headers = lines[0].split(delimiter).map(h => h.trim());
|
|
622
|
+
// Ограничение количества строк
|
|
623
|
+
const maxRows = options.maxRows || Infinity;
|
|
624
|
+
const dataRows = lines.slice(1, Math.min(lines.length, maxRows + 1));
|
|
625
|
+
// Возврат данных по одной строке
|
|
626
|
+
for (let i = 0; i < dataRows.length; i++) {
|
|
627
|
+
const line = dataRows[i];
|
|
628
|
+
const values = line.split(delimiter);
|
|
629
|
+
const row = {};
|
|
630
|
+
for (let j = 0; j < headers.length; j++) {
|
|
631
|
+
const header = headers[j];
|
|
632
|
+
const value = j < values.length ? values[j].trim() : '';
|
|
633
|
+
// Попытка парсинга чисел
|
|
634
|
+
if (/^-?\d+(\.\d+)?$/.test(value)) {
|
|
635
|
+
row[header] = parseFloat(value);
|
|
636
|
+
}
|
|
637
|
+
else if (value.toLowerCase() === 'true' || value.toLowerCase() === 'false') {
|
|
638
|
+
row[header] = value.toLowerCase() === 'true';
|
|
639
|
+
}
|
|
640
|
+
else {
|
|
641
|
+
row[header] = value;
|
|
642
|
+
}
|
|
643
|
+
}
|
|
644
|
+
yield row;
|
|
645
|
+
}
|
|
646
|
+
}
|
|
647
|
+
/**
|
|
648
|
+
* Асинхронная версия csvToJsonIterator (псевдоним)
|
|
649
|
+
*/
|
|
650
|
+
const csvToJsonIteratorAsync = csvToJsonIterator$1;
|
|
651
|
+
/**
|
|
652
|
+
* Парсинг CSV с обработкой ошибок
|
|
653
|
+
*
|
|
654
|
+
* @param csvText - CSV текст
|
|
655
|
+
* @param options - Опции парсинга
|
|
656
|
+
* @returns Результат парсинга или null при ошибке
|
|
657
|
+
*/
|
|
658
|
+
function parseCsvSafe(csvText, options = {}) {
|
|
659
|
+
try {
|
|
660
|
+
return csvToJson$1(csvText, options);
|
|
661
|
+
}
|
|
662
|
+
catch (error) {
|
|
663
|
+
console.error('CSV parsing error:', error);
|
|
664
|
+
return null;
|
|
665
|
+
}
|
|
666
|
+
}
|
|
667
|
+
/**
|
|
668
|
+
* Асинхронная версия parseCsvSafe
|
|
669
|
+
*/
|
|
670
|
+
async function parseCsvSafeAsync(csvText, options = {}) {
|
|
671
|
+
try {
|
|
672
|
+
return await csvToJsonAsync$1(csvText, options);
|
|
673
|
+
}
|
|
674
|
+
catch (error) {
|
|
675
|
+
console.error('CSV parsing error:', error);
|
|
676
|
+
return null;
|
|
677
|
+
}
|
|
678
|
+
}
|
|
679
|
+
// Экспорт для Node.js совместимости
|
|
680
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
681
|
+
module.exports = {
|
|
682
|
+
csvToJson: csvToJson$1,
|
|
683
|
+
csvToJsonAsync: csvToJsonAsync$1,
|
|
684
|
+
csvToJsonIterator: csvToJsonIterator$1,
|
|
685
|
+
csvToJsonIteratorAsync,
|
|
686
|
+
parseCsvSafe,
|
|
687
|
+
parseCsvSafeAsync,
|
|
688
|
+
autoDetectDelimiter: autoDetectDelimiter$1
|
|
689
|
+
};
|
|
690
|
+
}
|
|
691
|
+
|
|
692
|
+
var csvToJsonBrowser = /*#__PURE__*/Object.freeze({
|
|
693
|
+
__proto__: null,
|
|
694
|
+
csvToJson: csvToJson$1,
|
|
695
|
+
csvToJsonAsync: csvToJsonAsync$1,
|
|
696
|
+
csvToJsonIterator: csvToJsonIterator$1,
|
|
697
|
+
csvToJsonIteratorAsync: csvToJsonIteratorAsync,
|
|
698
|
+
parseCsvSafe: parseCsvSafe,
|
|
699
|
+
parseCsvSafeAsync: parseCsvSafeAsync
|
|
700
|
+
});
|
|
701
|
+
|
|
702
|
+
function isReadableStream(value) {
|
|
703
|
+
return value && typeof value.getReader === 'function';
|
|
704
|
+
}
|
|
705
|
+
function isAsyncIterable(value) {
|
|
706
|
+
return value && typeof value[Symbol.asyncIterator] === 'function';
|
|
707
|
+
}
|
|
708
|
+
function isIterable(value) {
|
|
709
|
+
return value && typeof value[Symbol.iterator] === 'function';
|
|
710
|
+
}
|
|
711
|
+
function createReadableStreamFromIterator(iterator) {
|
|
712
|
+
return new ReadableStream({
|
|
713
|
+
async pull(controller) {
|
|
714
|
+
try {
|
|
715
|
+
const { value, done } = await iterator.next();
|
|
716
|
+
if (done) {
|
|
717
|
+
controller.close();
|
|
718
|
+
return;
|
|
719
|
+
}
|
|
720
|
+
controller.enqueue(value);
|
|
721
|
+
}
|
|
722
|
+
catch (error) {
|
|
723
|
+
controller.error(error);
|
|
724
|
+
}
|
|
725
|
+
},
|
|
726
|
+
cancel() {
|
|
727
|
+
if (iterator.return) {
|
|
728
|
+
iterator.return();
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
});
|
|
732
|
+
}
|
|
733
|
+
function detectInputFormat(input, options) {
|
|
734
|
+
if (options && options.inputFormat) {
|
|
735
|
+
return options.inputFormat;
|
|
736
|
+
}
|
|
737
|
+
if (typeof input === 'string') {
|
|
738
|
+
const trimmed = input.trim();
|
|
739
|
+
if (trimmed === '') {
|
|
740
|
+
return 'unknown';
|
|
741
|
+
}
|
|
742
|
+
// Проверка на NDJSON (каждая строка - валидный JSON)
|
|
743
|
+
if (trimmed.includes('\n')) {
|
|
744
|
+
const lines = trimmed.split('\n').filter(line => line.trim() !== '');
|
|
745
|
+
if (lines.length > 0) {
|
|
746
|
+
try {
|
|
747
|
+
JSON.parse(lines[0]);
|
|
748
|
+
return 'ndjson';
|
|
749
|
+
}
|
|
750
|
+
catch {
|
|
751
|
+
// Не NDJSON
|
|
752
|
+
}
|
|
753
|
+
}
|
|
754
|
+
}
|
|
755
|
+
// Проверка на JSON
|
|
756
|
+
try {
|
|
757
|
+
const parsed = JSON.parse(trimmed);
|
|
758
|
+
if (Array.isArray(parsed) || (parsed && typeof parsed === 'object')) {
|
|
759
|
+
return 'json';
|
|
760
|
+
}
|
|
761
|
+
}
|
|
762
|
+
catch {
|
|
763
|
+
// Не JSON
|
|
764
|
+
}
|
|
765
|
+
// Проверка на CSV
|
|
766
|
+
if (trimmed.includes(',') || trimmed.includes(';') || trimmed.includes('\t')) {
|
|
767
|
+
return 'csv';
|
|
768
|
+
}
|
|
769
|
+
}
|
|
770
|
+
return 'unknown';
|
|
771
|
+
}
|
|
772
|
+
async function* jsonToCsvChunkIterator(input, options = {}) {
|
|
773
|
+
const format = detectInputFormat(input, options);
|
|
774
|
+
if (format === 'csv') {
|
|
775
|
+
throw new ValidationError('Input appears to be CSV, not JSON');
|
|
776
|
+
}
|
|
777
|
+
// Вспомогательная функция для создания асинхронного итератора
|
|
778
|
+
function toAsyncIterator(iterable) {
|
|
779
|
+
if (isAsyncIterable(iterable)) {
|
|
780
|
+
return iterable[Symbol.asyncIterator]();
|
|
781
|
+
}
|
|
782
|
+
if (isIterable(iterable)) {
|
|
783
|
+
const syncIterator = iterable[Symbol.iterator]();
|
|
784
|
+
return {
|
|
785
|
+
next: () => Promise.resolve(syncIterator.next()),
|
|
786
|
+
return: syncIterator.return ? () => Promise.resolve(syncIterator.return()) : undefined,
|
|
787
|
+
throw: syncIterator.throw ? (error) => Promise.resolve(syncIterator.throw(error)) : undefined
|
|
788
|
+
};
|
|
789
|
+
}
|
|
790
|
+
throw new ValidationError('Input is not iterable');
|
|
791
|
+
}
|
|
792
|
+
let iterator;
|
|
793
|
+
if (isAsyncIterable(input) || isIterable(input)) {
|
|
794
|
+
iterator = toAsyncIterator(input);
|
|
795
|
+
}
|
|
796
|
+
else if (typeof input === 'string') {
|
|
797
|
+
const parsed = JSON.parse(input);
|
|
798
|
+
if (Array.isArray(parsed)) {
|
|
799
|
+
iterator = toAsyncIterator(parsed);
|
|
800
|
+
}
|
|
801
|
+
else {
|
|
802
|
+
iterator = toAsyncIterator([parsed]);
|
|
803
|
+
}
|
|
804
|
+
}
|
|
805
|
+
else if (Array.isArray(input)) {
|
|
806
|
+
iterator = toAsyncIterator(input);
|
|
807
|
+
}
|
|
808
|
+
else {
|
|
809
|
+
iterator = toAsyncIterator([input]);
|
|
810
|
+
}
|
|
811
|
+
const delimiter = options.delimiter || ';';
|
|
812
|
+
const includeHeaders = options.includeHeaders !== false;
|
|
813
|
+
const preventInjection = options.preventCsvInjection !== false;
|
|
814
|
+
let isFirstChunk = true;
|
|
815
|
+
let headers = [];
|
|
816
|
+
while (true) {
|
|
817
|
+
const { value, done } = await iterator.next();
|
|
818
|
+
if (done)
|
|
819
|
+
break;
|
|
820
|
+
const item = value;
|
|
821
|
+
if (isFirstChunk) {
|
|
822
|
+
// Извлечение заголовков из первого элемента
|
|
823
|
+
headers = Object.keys(item);
|
|
824
|
+
if (includeHeaders) {
|
|
825
|
+
const headerLine = headers.map(header => {
|
|
826
|
+
const escaped = header.includes('"') ? `"${header.replace(/"/g, '""')}"` : header;
|
|
827
|
+
return preventInjection && /^[=+\-@]/.test(escaped) ? `'${escaped}` : escaped;
|
|
828
|
+
}).join(delimiter);
|
|
829
|
+
yield headerLine + '\n';
|
|
830
|
+
}
|
|
831
|
+
isFirstChunk = false;
|
|
832
|
+
}
|
|
833
|
+
const row = headers.map(header => {
|
|
834
|
+
const value = item[header];
|
|
835
|
+
const strValue = value === null || value === undefined ? '' : String(value);
|
|
836
|
+
if (strValue.includes('"') || strValue.includes('\n') || strValue.includes('\r') || strValue.includes(delimiter)) {
|
|
837
|
+
return `"${strValue.replace(/"/g, '""')}"`;
|
|
838
|
+
}
|
|
839
|
+
if (preventInjection && /^[=+\-@]/.test(strValue)) {
|
|
840
|
+
return `'${strValue}`;
|
|
841
|
+
}
|
|
842
|
+
return strValue;
|
|
843
|
+
}).join(delimiter);
|
|
844
|
+
yield row + '\n';
|
|
845
|
+
}
|
|
846
|
+
}
|
|
847
|
+
async function* jsonToNdjsonChunkIterator(input, options = {}) {
|
|
848
|
+
const format = detectInputFormat(input, options);
|
|
849
|
+
// Вспомогательная функция для создания асинхронного итератора
|
|
850
|
+
function toAsyncIterator(iterable) {
|
|
851
|
+
if (isAsyncIterable(iterable)) {
|
|
852
|
+
return iterable[Symbol.asyncIterator]();
|
|
853
|
+
}
|
|
854
|
+
if (isIterable(iterable)) {
|
|
855
|
+
const syncIterator = iterable[Symbol.iterator]();
|
|
856
|
+
return {
|
|
857
|
+
next: () => Promise.resolve(syncIterator.next()),
|
|
858
|
+
return: syncIterator.return ? () => Promise.resolve(syncIterator.return()) : undefined,
|
|
859
|
+
throw: syncIterator.throw ? (error) => Promise.resolve(syncIterator.throw(error)) : undefined
|
|
860
|
+
};
|
|
861
|
+
}
|
|
862
|
+
throw new ValidationError('Input is not iterable');
|
|
863
|
+
}
|
|
864
|
+
let iterator;
|
|
865
|
+
if (isAsyncIterable(input) || isIterable(input)) {
|
|
866
|
+
iterator = toAsyncIterator(input);
|
|
867
|
+
}
|
|
868
|
+
else if (typeof input === 'string') {
|
|
869
|
+
if (format === 'ndjson') {
|
|
870
|
+
const lines = input.split('\n').filter(line => line.trim() !== '');
|
|
871
|
+
iterator = toAsyncIterator(lines);
|
|
872
|
+
}
|
|
873
|
+
else {
|
|
874
|
+
const parsed = JSON.parse(input);
|
|
875
|
+
if (Array.isArray(parsed)) {
|
|
876
|
+
iterator = toAsyncIterator(parsed);
|
|
877
|
+
}
|
|
878
|
+
else {
|
|
879
|
+
iterator = toAsyncIterator([parsed]);
|
|
880
|
+
}
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
else if (Array.isArray(input)) {
|
|
884
|
+
iterator = toAsyncIterator(input);
|
|
885
|
+
}
|
|
886
|
+
else {
|
|
887
|
+
iterator = toAsyncIterator([input]);
|
|
888
|
+
}
|
|
889
|
+
while (true) {
|
|
890
|
+
const { value, done } = await iterator.next();
|
|
891
|
+
if (done)
|
|
892
|
+
break;
|
|
893
|
+
let jsonStr;
|
|
894
|
+
if (typeof value === 'string') {
|
|
895
|
+
try {
|
|
896
|
+
// Проверяем, является ли строка валидным JSON
|
|
897
|
+
JSON.parse(value);
|
|
898
|
+
jsonStr = value;
|
|
899
|
+
}
|
|
900
|
+
catch {
|
|
901
|
+
// Если нет, сериализуем как JSON
|
|
902
|
+
jsonStr = JSON.stringify(value);
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
else {
|
|
906
|
+
jsonStr = JSON.stringify(value);
|
|
907
|
+
}
|
|
908
|
+
yield jsonStr + '\n';
|
|
909
|
+
}
|
|
910
|
+
}
|
|
911
|
+
async function* csvToJsonChunkIterator(input, options = {}) {
|
|
912
|
+
if (typeof input === 'string') {
|
|
913
|
+
// Используем csvToJsonIterator из csv-to-json-browser
|
|
914
|
+
yield* csvToJsonIterator$1(input, options);
|
|
915
|
+
}
|
|
916
|
+
else if (input instanceof File || input instanceof Blob) {
|
|
917
|
+
const text = await input.text();
|
|
918
|
+
yield* csvToJsonIterator$1(text, options);
|
|
919
|
+
}
|
|
920
|
+
else if (isReadableStream(input)) {
|
|
921
|
+
const reader = input.getReader();
|
|
922
|
+
const decoder = new TextDecoder();
|
|
923
|
+
let buffer = '';
|
|
924
|
+
try {
|
|
925
|
+
while (true) {
|
|
926
|
+
const { value, done } = await reader.read();
|
|
927
|
+
if (done)
|
|
928
|
+
break;
|
|
929
|
+
buffer += decoder.decode(value, { stream: true });
|
|
930
|
+
// Обработка буфера по строкам
|
|
931
|
+
const lines = buffer.split('\n');
|
|
932
|
+
buffer = lines.pop() || '';
|
|
933
|
+
// TODO: Реализовать парсинг CSV из чанков
|
|
934
|
+
// Пока просто возвращаем сырые строки
|
|
935
|
+
for (const line of lines) {
|
|
936
|
+
if (line.trim()) {
|
|
937
|
+
yield { raw: line };
|
|
938
|
+
}
|
|
939
|
+
}
|
|
940
|
+
}
|
|
941
|
+
// Обработка остатка буфера
|
|
942
|
+
if (buffer.trim()) {
|
|
943
|
+
yield { raw: buffer };
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
finally {
|
|
947
|
+
reader.releaseLock();
|
|
948
|
+
}
|
|
949
|
+
}
|
|
950
|
+
else {
|
|
951
|
+
throw new ValidationError('Unsupported input type for CSV streaming');
|
|
952
|
+
}
|
|
953
|
+
}
|
|
954
|
+
function jsonToCsvStream$1(input, options = {}) {
|
|
955
|
+
const iterator = jsonToCsvChunkIterator(input, options);
|
|
956
|
+
return createReadableStreamFromIterator(iterator);
|
|
957
|
+
}
|
|
958
|
+
function jsonToNdjsonStream$1(input, options = {}) {
|
|
959
|
+
const iterator = jsonToNdjsonChunkIterator(input, options);
|
|
960
|
+
return createReadableStreamFromIterator(iterator);
|
|
961
|
+
}
|
|
962
|
+
function csvToJsonStream$1(input, options = {}) {
|
|
963
|
+
const iterator = csvToJsonChunkIterator(input, options);
|
|
964
|
+
return createReadableStreamFromIterator(iterator);
|
|
965
|
+
}
|
|
966
|
+
/**
|
|
967
|
+
* Асинхронная версия jsonToCsvStream
|
|
968
|
+
*/
|
|
969
|
+
async function jsonToCsvStreamAsync(input, options = {}) {
|
|
970
|
+
return jsonToCsvStream$1(input, options);
|
|
971
|
+
}
|
|
972
|
+
/**
|
|
973
|
+
* Асинхронная версия jsonToNdjsonStream
|
|
974
|
+
*/
|
|
975
|
+
async function jsonToNdjsonStreamAsync(input, options = {}) {
|
|
976
|
+
return jsonToNdjsonStream$1(input, options);
|
|
977
|
+
}
|
|
978
|
+
/**
|
|
979
|
+
* Асинхронная версия csvToJsonStream
|
|
980
|
+
*/
|
|
981
|
+
async function csvToJsonStreamAsync(input, options = {}) {
|
|
982
|
+
return csvToJsonStream$1(input, options);
|
|
983
|
+
}
|
|
984
|
+
// Экспорт для Node.js совместимости
|
|
985
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
986
|
+
module.exports = {
|
|
987
|
+
jsonToCsvStream: jsonToCsvStream$1,
|
|
988
|
+
jsonToCsvStreamAsync,
|
|
989
|
+
jsonToNdjsonStream: jsonToNdjsonStream$1,
|
|
990
|
+
jsonToNdjsonStreamAsync,
|
|
991
|
+
csvToJsonStream: csvToJsonStream$1,
|
|
992
|
+
csvToJsonStreamAsync,
|
|
993
|
+
createReadableStreamFromIterator
|
|
994
|
+
};
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
// Браузерные специфичные функции для jtcsv
|
|
998
|
+
// Функции, которые работают только в браузере
|
|
999
|
+
/**
|
|
1000
|
+
* Скачивает JSON данные как CSV файл
|
|
1001
|
+
*
|
|
1002
|
+
* @param data - Массив объектов для конвертации
|
|
1003
|
+
* @param filename - Имя файла для скачивания (по умолчанию 'data.csv')
|
|
1004
|
+
* @param options - Опции для jsonToCsv
|
|
1005
|
+
*
|
|
1006
|
+
* @example
|
|
1007
|
+
* const data = [
|
|
1008
|
+
* { id: 1, name: 'John' },
|
|
1009
|
+
* { id: 2, name: 'Jane' }
|
|
1010
|
+
* ];
|
|
1011
|
+
* downloadAsCsv(data, 'users.csv', { delimiter: ',' });
|
|
1012
|
+
*/
|
|
1013
|
+
function downloadAsCsv(data, filename = 'data.csv', options = {}) {
|
|
1014
|
+
// Проверка что мы в браузере
|
|
1015
|
+
if (typeof window === 'undefined') {
|
|
1016
|
+
throw new ValidationError('downloadAsCsv() работает только в браузере. Используйте saveAsCsv() в Node.js');
|
|
1017
|
+
}
|
|
1018
|
+
// Валидация имени файла
|
|
1019
|
+
if (typeof filename !== 'string' || filename.trim() === '') {
|
|
1020
|
+
throw new ValidationError('Filename must be a non-empty string');
|
|
1021
|
+
}
|
|
1022
|
+
// Добавление расширения .csv если его нет
|
|
1023
|
+
if (!filename.toLowerCase().endsWith('.csv')) {
|
|
1024
|
+
filename += '.csv';
|
|
1025
|
+
}
|
|
1026
|
+
// Конвертация в CSV
|
|
1027
|
+
const csv = jsonToCsv$1(data, options);
|
|
1028
|
+
// Создание Blob
|
|
1029
|
+
const blob = new Blob([csv], {
|
|
1030
|
+
type: 'text/csv;charset=utf-8;'
|
|
1031
|
+
});
|
|
1032
|
+
// Создание ссылки для скачивания
|
|
1033
|
+
const link = document.createElement('a');
|
|
1034
|
+
const url = URL.createObjectURL(blob);
|
|
1035
|
+
link.setAttribute('href', url);
|
|
1036
|
+
link.setAttribute('download', filename);
|
|
1037
|
+
link.style.visibility = 'hidden';
|
|
1038
|
+
document.body.appendChild(link);
|
|
1039
|
+
link.click();
|
|
1040
|
+
document.body.removeChild(link);
|
|
1041
|
+
// Освобождение URL
|
|
1042
|
+
setTimeout(() => URL.revokeObjectURL(url), 100);
|
|
1043
|
+
}
|
|
1044
|
+
/**
|
|
1045
|
+
* Асинхронная версия downloadAsCsv
|
|
1046
|
+
*/
|
|
1047
|
+
async function downloadAsCsvAsync$1(data, filename = 'data.csv', options = {}) {
|
|
1048
|
+
return downloadAsCsv(data, filename, options);
|
|
1049
|
+
}
|
|
1050
|
+
/**
|
|
1051
|
+
* Парсит CSV файл из input[type="file"]
|
|
1052
|
+
*
|
|
1053
|
+
* @param file - File объект из input
|
|
1054
|
+
* @param options - Опции для csvToJson
|
|
1055
|
+
* @returns Promise с распарсенными данными
|
|
1056
|
+
*/
|
|
1057
|
+
async function parseCsvFile(file, options = {}) {
|
|
1058
|
+
if (!(file instanceof File)) {
|
|
1059
|
+
throw new ValidationError('parseCsvFile() ожидает объект File');
|
|
1060
|
+
}
|
|
1061
|
+
// Чтение файла как текст
|
|
1062
|
+
const text = await file.text();
|
|
1063
|
+
// Парсинг CSV
|
|
1064
|
+
return csvToJson$1(text, options);
|
|
1065
|
+
}
|
|
1066
|
+
/**
|
|
1067
|
+
* Парсит CSV файл потоково
|
|
1068
|
+
*
|
|
1069
|
+
* @param file - File объект
|
|
1070
|
+
* @param options - Опции для потокового парсинга
|
|
1071
|
+
* @returns AsyncIterator с данными
|
|
1072
|
+
*/
|
|
1073
|
+
function parseCsvFileStream(file, options = {}) {
|
|
1074
|
+
if (!(file instanceof File)) {
|
|
1075
|
+
throw new ValidationError('parseCsvFileStream() ожидает объект File');
|
|
1076
|
+
}
|
|
1077
|
+
// Используем csvToJsonIterator из импортированного модуля
|
|
1078
|
+
return csvToJsonIterator$1(file, options);
|
|
1079
|
+
}
|
|
1080
|
+
/**
|
|
1081
|
+
* Создает поток для конвертации JSON в CSV
|
|
1082
|
+
*
|
|
1083
|
+
* @param options - Опции для jsonToCsv
|
|
1084
|
+
* @returns ReadableStream
|
|
1085
|
+
*/
|
|
1086
|
+
function jsonToCsvStream(options = {}) {
|
|
1087
|
+
return jsonToCsvStream$1(options);
|
|
1088
|
+
}
|
|
1089
|
+
/**
|
|
1090
|
+
* Создает поток для конвертации JSON в NDJSON
|
|
1091
|
+
*
|
|
1092
|
+
* @param options - Опции для конвертации
|
|
1093
|
+
* @returns ReadableStream
|
|
1094
|
+
*/
|
|
1095
|
+
function jsonToNdjsonStream(options = {}) {
|
|
1096
|
+
return jsonToNdjsonStream$1(options);
|
|
1097
|
+
}
|
|
1098
|
+
/**
|
|
1099
|
+
* Создает поток для парсинга CSV в JSON
|
|
1100
|
+
*
|
|
1101
|
+
* @param options - Опции для csvToJson
|
|
1102
|
+
* @returns ReadableStream
|
|
1103
|
+
*/
|
|
1104
|
+
function csvToJsonStream(options = {}) {
|
|
1105
|
+
return csvToJsonStream$1(options);
|
|
1106
|
+
}
|
|
1107
|
+
/**
|
|
1108
|
+
* Загружает CSV файл по URL
|
|
1109
|
+
*
|
|
1110
|
+
* @param url - URL CSV файла
|
|
1111
|
+
* @param options - Опции для csvToJson
|
|
1112
|
+
* @returns Promise с распарсенными данными
|
|
1113
|
+
*/
|
|
1114
|
+
async function loadCsvFromUrl(url, options = {}) {
|
|
1115
|
+
if (typeof window === 'undefined') {
|
|
1116
|
+
throw new ValidationError('loadCsvFromUrl() работает только в браузере');
|
|
1117
|
+
}
|
|
1118
|
+
const response = await fetch(url);
|
|
1119
|
+
if (!response.ok) {
|
|
1120
|
+
throw new ValidationError(`Failed to load CSV from URL: ${response.status} ${response.statusText}`);
|
|
1121
|
+
}
|
|
1122
|
+
const text = await response.text();
|
|
1123
|
+
return csvToJson$1(text, options);
|
|
1124
|
+
}
|
|
1125
|
+
/**
|
|
1126
|
+
* Асинхронная версия loadCsvFromUrl
|
|
1127
|
+
*/
|
|
1128
|
+
async function loadCsvFromUrlAsync(url, options = {}) {
|
|
1129
|
+
return loadCsvFromUrl(url, options);
|
|
1130
|
+
}
|
|
1131
|
+
/**
|
|
1132
|
+
* Экспортирует данные в CSV и открывает в новой вкладке
|
|
1133
|
+
*
|
|
1134
|
+
* @param data - Данные для экспорта
|
|
1135
|
+
* @param options - Опции для jsonToCsv
|
|
1136
|
+
*/
|
|
1137
|
+
function openCsvInNewTab(data, options = {}) {
|
|
1138
|
+
if (typeof window === 'undefined') {
|
|
1139
|
+
throw new ValidationError('openCsvInNewTab() работает только в браузере');
|
|
1140
|
+
}
|
|
1141
|
+
const csv = jsonToCsv$1(data, options);
|
|
1142
|
+
const blob = new Blob([csv], { type: 'text/csv;charset=utf-8' });
|
|
1143
|
+
const url = URL.createObjectURL(blob);
|
|
1144
|
+
window.open(url, '_blank');
|
|
1145
|
+
// Освобождение URL через некоторое время
|
|
1146
|
+
setTimeout(() => URL.revokeObjectURL(url), 1000);
|
|
1147
|
+
}
|
|
1148
|
+
/**
|
|
1149
|
+
* Асинхронная версия openCsvInNewTab
|
|
1150
|
+
*/
|
|
1151
|
+
async function openCsvInNewTabAsync(data, options = {}) {
|
|
1152
|
+
return openCsvInNewTab(data, options);
|
|
1153
|
+
}
|
|
1154
|
+
/**
|
|
1155
|
+
* Копирует CSV в буфер обмена
|
|
1156
|
+
*
|
|
1157
|
+
* @param data - Данные для копирования
|
|
1158
|
+
* @param options - Опции для jsonToCsv
|
|
1159
|
+
* @returns Promise с результатом копирования
|
|
1160
|
+
*/
|
|
1161
|
+
async function copyCsvToClipboard(data, options = {}) {
|
|
1162
|
+
if (typeof window === 'undefined' || !navigator.clipboard) {
|
|
1163
|
+
throw new ValidationError('copyCsvToClipboard() требует поддержки Clipboard API');
|
|
1164
|
+
}
|
|
1165
|
+
const csv = jsonToCsv$1(data, options);
|
|
1166
|
+
try {
|
|
1167
|
+
await navigator.clipboard.writeText(csv);
|
|
1168
|
+
return true;
|
|
1169
|
+
}
|
|
1170
|
+
catch (error) {
|
|
1171
|
+
console.error('Failed to copy to clipboard:', error);
|
|
1172
|
+
return false;
|
|
1173
|
+
}
|
|
1174
|
+
}
|
|
1175
|
+
/**
|
|
1176
|
+
* Сохраняет CSV в localStorage
|
|
1177
|
+
*
|
|
1178
|
+
* @param key - Ключ для сохранения
|
|
1179
|
+
* @param data - Данные для сохранения
|
|
1180
|
+
* @param options - Опции для jsonToCsv
|
|
1181
|
+
*/
|
|
1182
|
+
function saveCsvToLocalStorage(key, data, options = {}) {
|
|
1183
|
+
if (typeof window === 'undefined' || !localStorage) {
|
|
1184
|
+
throw new ValidationError('saveCsvToLocalStorage() требует localStorage');
|
|
1185
|
+
}
|
|
1186
|
+
const csv = jsonToCsv$1(data, options);
|
|
1187
|
+
localStorage.setItem(key, csv);
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Загружает CSV из localStorage
|
|
1191
|
+
*
|
|
1192
|
+
* @param key - Ключ для загрузки
|
|
1193
|
+
* @param options - Опции для csvToJson
|
|
1194
|
+
* @returns Распарсенные данные или null
|
|
1195
|
+
*/
|
|
1196
|
+
function loadCsvFromLocalStorage(key, options = {}) {
|
|
1197
|
+
if (typeof window === 'undefined' || !localStorage) {
|
|
1198
|
+
throw new ValidationError('loadCsvFromLocalStorage() требует localStorage');
|
|
1199
|
+
}
|
|
1200
|
+
const csv = localStorage.getItem(key);
|
|
1201
|
+
if (!csv) {
|
|
1202
|
+
return null;
|
|
1203
|
+
}
|
|
1204
|
+
return csvToJson$1(csv, options);
|
|
1205
|
+
}
|
|
1206
|
+
/**
|
|
1207
|
+
* Асинхронная версия loadCsvFromLocalStorage
|
|
1208
|
+
*/
|
|
1209
|
+
async function loadCsvFromLocalStorageAsync(key, options = {}) {
|
|
1210
|
+
return loadCsvFromLocalStorage(key, options);
|
|
1211
|
+
}
|
|
1212
|
+
/**
|
|
1213
|
+
* Создает CSV файл из JSON данных (альтернатива downloadAsCsv)
|
|
1214
|
+
* Возвращает Blob вместо автоматического скачивания
|
|
1215
|
+
*
|
|
1216
|
+
* @param data - Массив объектов
|
|
1217
|
+
* @param options - Опции для jsonToCsv
|
|
1218
|
+
* @returns CSV Blob
|
|
1219
|
+
*/
|
|
1220
|
+
function createCsvBlob(data, options = {}) {
|
|
1221
|
+
const csv = jsonToCsv$1(data, options);
|
|
1222
|
+
return new Blob([csv], {
|
|
1223
|
+
type: 'text/csv;charset=utf-8;'
|
|
1224
|
+
});
|
|
1225
|
+
}
|
|
1226
|
+
/**
|
|
1227
|
+
* Асинхронная версия createCsvBlob
|
|
1228
|
+
*/
|
|
1229
|
+
async function createCsvBlobAsync(data, options = {}) {
|
|
1230
|
+
return createCsvBlob(data, options);
|
|
1231
|
+
}
|
|
1232
|
+
/**
|
|
1233
|
+
* Парсит CSV строку из Blob
|
|
1234
|
+
*
|
|
1235
|
+
* @param blob - CSV Blob
|
|
1236
|
+
* @param options - Опции для csvToJson
|
|
1237
|
+
* @returns Promise с JSON данными
|
|
1238
|
+
*/
|
|
1239
|
+
async function parseCsvBlob(blob, options = {}) {
|
|
1240
|
+
if (!(blob instanceof Blob)) {
|
|
1241
|
+
throw new ValidationError('Input must be a Blob object');
|
|
1242
|
+
}
|
|
1243
|
+
return new Promise((resolve, reject) => {
|
|
1244
|
+
const reader = new FileReader();
|
|
1245
|
+
reader.onload = function (event) {
|
|
1246
|
+
try {
|
|
1247
|
+
const csvText = event.target?.result;
|
|
1248
|
+
const json = csvToJson$1(csvText, options);
|
|
1249
|
+
resolve(json);
|
|
1250
|
+
}
|
|
1251
|
+
catch (error) {
|
|
1252
|
+
reject(error);
|
|
1253
|
+
}
|
|
1254
|
+
};
|
|
1255
|
+
reader.onerror = function () {
|
|
1256
|
+
reject(new ValidationError('Ошибка чтения Blob'));
|
|
1257
|
+
};
|
|
1258
|
+
reader.readAsText(blob, 'UTF-8');
|
|
1259
|
+
});
|
|
1260
|
+
}
|
|
1261
|
+
/**
|
|
1262
|
+
* Асинхронная версия parseCsvBlob
|
|
1263
|
+
*/
|
|
1264
|
+
async function parseCsvBlobAsync(blob, options = {}) {
|
|
1265
|
+
return parseCsvBlob(blob, options);
|
|
1266
|
+
}
|
|
1267
|
+
// Экспорт для Node.js совместимости
|
|
1268
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1269
|
+
module.exports = {
|
|
1270
|
+
downloadAsCsv,
|
|
1271
|
+
downloadAsCsvAsync: downloadAsCsvAsync$1,
|
|
1272
|
+
parseCsvFile,
|
|
1273
|
+
parseCsvFileStream,
|
|
1274
|
+
createCsvBlob,
|
|
1275
|
+
createCsvBlobAsync,
|
|
1276
|
+
parseCsvBlob,
|
|
1277
|
+
parseCsvBlobAsync,
|
|
1278
|
+
jsonToCsvStream,
|
|
1279
|
+
jsonToNdjsonStream,
|
|
1280
|
+
csvToJsonStream,
|
|
1281
|
+
loadCsvFromUrl,
|
|
1282
|
+
loadCsvFromUrlAsync,
|
|
1283
|
+
openCsvInNewTab,
|
|
1284
|
+
openCsvInNewTabAsync,
|
|
1285
|
+
copyCsvToClipboard,
|
|
1286
|
+
saveCsvToLocalStorage,
|
|
1287
|
+
loadCsvFromLocalStorage,
|
|
1288
|
+
loadCsvFromLocalStorageAsync
|
|
1289
|
+
};
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// Ядро jtcsv - только базовые функции JSON<->CSV
|
|
1293
|
+
// Минимальный размер, максимальная производительность
|
|
1294
|
+
const { jsonToCsv, preprocessData, deepUnwrap } = jsonToCsvBrowser;
|
|
1295
|
+
const { csvToJson, csvToJsonIterator, autoDetectDelimiter } = csvToJsonBrowser;
|
|
1296
|
+
/**
|
|
1297
|
+
* Асинхронная версия jsonToCsv
|
|
1298
|
+
*/
|
|
1299
|
+
async function jsonToCsvAsync(data, options = {}) {
|
|
1300
|
+
return jsonToCsv(data, options);
|
|
1301
|
+
}
|
|
1302
|
+
/**
|
|
1303
|
+
* Асинхронная версия csvToJson
|
|
1304
|
+
*/
|
|
1305
|
+
async function csvToJsonAsync(csv, options = {}) {
|
|
1306
|
+
return csvToJson(csv, options);
|
|
1307
|
+
}
|
|
1308
|
+
/**
|
|
1309
|
+
* Асинхронная версия parseCsvFile
|
|
1310
|
+
*/
|
|
1311
|
+
async function parseCsvFileAsync(file, options = {}) {
|
|
1312
|
+
return parseCsvFile(file, options);
|
|
1313
|
+
}
|
|
1314
|
+
/**
|
|
1315
|
+
* Асинхронная версия autoDetectDelimiter
|
|
1316
|
+
*/
|
|
1317
|
+
async function autoDetectDelimiterAsync(csv) {
|
|
1318
|
+
return autoDetectDelimiter(csv);
|
|
1319
|
+
}
|
|
1320
|
+
/**
|
|
1321
|
+
* Асинхронная версия downloadAsCsv
|
|
1322
|
+
*/
|
|
1323
|
+
async function downloadAsCsvAsync(data, filename = 'export.csv', options = {}) {
|
|
1324
|
+
return downloadAsCsv(data, filename, options);
|
|
1325
|
+
}
|
|
1326
|
+
// Основной экспорт ядра
|
|
1327
|
+
const jtcsvCore = {
|
|
1328
|
+
// JSON to CSV функции
|
|
1329
|
+
jsonToCsv,
|
|
1330
|
+
preprocessData,
|
|
1331
|
+
downloadAsCsv,
|
|
1332
|
+
deepUnwrap,
|
|
1333
|
+
// CSV to JSON функции
|
|
1334
|
+
csvToJson,
|
|
1335
|
+
csvToJsonIterator,
|
|
1336
|
+
parseCsvFile,
|
|
1337
|
+
parseCsvFileStream,
|
|
1338
|
+
jsonToCsvStream,
|
|
1339
|
+
jsonToNdjsonStream,
|
|
1340
|
+
csvToJsonStream,
|
|
1341
|
+
autoDetectDelimiter,
|
|
1342
|
+
// Асинхронные функции
|
|
1343
|
+
jsonToCsvAsync,
|
|
1344
|
+
csvToJsonAsync,
|
|
1345
|
+
parseCsvFileAsync,
|
|
1346
|
+
autoDetectDelimiterAsync,
|
|
1347
|
+
downloadAsCsvAsync,
|
|
1348
|
+
// Error classes
|
|
1349
|
+
ValidationError,
|
|
1350
|
+
SecurityError,
|
|
1351
|
+
FileSystemError,
|
|
1352
|
+
ParsingError,
|
|
1353
|
+
LimitError,
|
|
1354
|
+
ConfigurationError,
|
|
1355
|
+
ERROR_CODES,
|
|
1356
|
+
// Удобные алиасы
|
|
1357
|
+
parse: csvToJson,
|
|
1358
|
+
unparse: jsonToCsv,
|
|
1359
|
+
parseAsync: csvToJsonAsync,
|
|
1360
|
+
unparseAsync: jsonToCsvAsync,
|
|
1361
|
+
// Версия
|
|
1362
|
+
version: '3.0.0-core'
|
|
1363
|
+
};
|
|
1364
|
+
// Экспорт для разных сред
|
|
1365
|
+
if (typeof module !== 'undefined' && module.exports) {
|
|
1366
|
+
// Node.js CommonJS
|
|
1367
|
+
module.exports = jtcsvCore;
|
|
1368
|
+
}
|
|
1369
|
+
else if (typeof define === 'function' && define.amd) {
|
|
1370
|
+
// AMD
|
|
1371
|
+
define([], () => jtcsvCore);
|
|
1372
|
+
}
|
|
1373
|
+
else if (typeof window !== 'undefined') {
|
|
1374
|
+
// Браузер (глобальная переменная)
|
|
1375
|
+
window.jtcsv = jtcsvCore;
|
|
1376
|
+
}
|
|
1377
|
+
|
|
1378
|
+
export { ConfigurationError, ERROR_CODES, FileSystemError, LimitError, ParsingError, SecurityError, ValidationError, autoDetectDelimiter, autoDetectDelimiterAsync, csvToJson, csvToJsonAsync, csvToJsonIterator, csvToJsonStream, deepUnwrap, jtcsvCore as default, downloadAsCsv, downloadAsCsvAsync, jsonToCsv, jsonToCsvAsync, jsonToCsvStream, jsonToNdjsonStream, parseCsvFile, parseCsvFileAsync, parseCsvFileStream, preprocessData };
|
|
1379
|
+
//# sourceMappingURL=jtcsv-core.esm.js.map
|