jtcsv 2.2.8 → 3.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (246) hide show
  1. package/README.md +204 -115
  2. package/bin/jtcsv.ts +2612 -0
  3. package/browser.d.ts +142 -0
  4. package/dist/benchmark.js +446 -0
  5. package/dist/benchmark.js.map +1 -0
  6. package/dist/bin/jtcsv.js +1940 -0
  7. package/dist/bin/jtcsv.js.map +1 -0
  8. package/dist/csv-to-json.js +1262 -0
  9. package/dist/csv-to-json.js.map +1 -0
  10. package/dist/errors.js +291 -0
  11. package/dist/errors.js.map +1 -0
  12. package/dist/eslint.config.js +147 -0
  13. package/dist/eslint.config.js.map +1 -0
  14. package/dist/index-core.js +95 -0
  15. package/dist/index-core.js.map +1 -0
  16. package/dist/index.js +93 -0
  17. package/dist/index.js.map +1 -0
  18. package/dist/json-save.js +229 -0
  19. package/dist/json-save.js.map +1 -0
  20. package/dist/json-to-csv.js +576 -0
  21. package/dist/json-to-csv.js.map +1 -0
  22. package/dist/jtcsv-core.cjs.js +1736 -0
  23. package/dist/jtcsv-core.cjs.js.map +1 -0
  24. package/dist/jtcsv-core.esm.js +1708 -0
  25. package/dist/jtcsv-core.esm.js.map +1 -0
  26. package/dist/jtcsv-core.umd.js +1742 -0
  27. package/dist/jtcsv-core.umd.js.map +1 -0
  28. package/dist/jtcsv-full.cjs.js +2241 -0
  29. package/dist/jtcsv-full.cjs.js.map +1 -0
  30. package/dist/jtcsv-full.esm.js +2209 -0
  31. package/dist/jtcsv-full.esm.js.map +1 -0
  32. package/dist/jtcsv-full.umd.js +2247 -0
  33. package/dist/jtcsv-full.umd.js.map +1 -0
  34. package/dist/jtcsv-workers.esm.js +768 -0
  35. package/dist/jtcsv-workers.esm.js.map +1 -0
  36. package/dist/jtcsv-workers.umd.js +782 -0
  37. package/dist/jtcsv-workers.umd.js.map +1 -0
  38. package/dist/jtcsv.cjs.js +1996 -2048
  39. package/dist/jtcsv.cjs.js.map +1 -1
  40. package/dist/jtcsv.esm.js +1992 -2048
  41. package/dist/jtcsv.esm.js.map +1 -1
  42. package/dist/jtcsv.umd.js +2157 -2209
  43. package/dist/jtcsv.umd.js.map +1 -1
  44. package/dist/plugins/express-middleware/index.js +350 -0
  45. package/dist/plugins/express-middleware/index.js.map +1 -0
  46. package/dist/plugins/fastify-plugin/index.js +315 -0
  47. package/dist/plugins/fastify-plugin/index.js.map +1 -0
  48. package/dist/plugins/hono/index.js +111 -0
  49. package/dist/plugins/hono/index.js.map +1 -0
  50. package/dist/plugins/nestjs/index.js +112 -0
  51. package/dist/plugins/nestjs/index.js.map +1 -0
  52. package/dist/plugins/nuxt/index.js +53 -0
  53. package/dist/plugins/nuxt/index.js.map +1 -0
  54. package/dist/plugins/remix/index.js +133 -0
  55. package/dist/plugins/remix/index.js.map +1 -0
  56. package/dist/plugins/sveltekit/index.js +155 -0
  57. package/dist/plugins/sveltekit/index.js.map +1 -0
  58. package/dist/plugins/trpc/index.js +136 -0
  59. package/dist/plugins/trpc/index.js.map +1 -0
  60. package/dist/run-demo.js +49 -0
  61. package/dist/run-demo.js.map +1 -0
  62. package/dist/src/browser/browser-functions.js +193 -0
  63. package/dist/src/browser/browser-functions.js.map +1 -0
  64. package/dist/src/browser/core.js +123 -0
  65. package/dist/src/browser/core.js.map +1 -0
  66. package/dist/src/browser/csv-to-json-browser.js +353 -0
  67. package/dist/src/browser/csv-to-json-browser.js.map +1 -0
  68. package/dist/src/browser/errors-browser.js +219 -0
  69. package/dist/src/browser/errors-browser.js.map +1 -0
  70. package/dist/src/browser/extensions/plugins.js +106 -0
  71. package/dist/src/browser/extensions/plugins.js.map +1 -0
  72. package/dist/src/browser/extensions/workers.js +66 -0
  73. package/dist/src/browser/extensions/workers.js.map +1 -0
  74. package/dist/src/browser/index.js +140 -0
  75. package/dist/src/browser/index.js.map +1 -0
  76. package/dist/src/browser/json-to-csv-browser.js +225 -0
  77. package/dist/src/browser/json-to-csv-browser.js.map +1 -0
  78. package/dist/src/browser/streams.js +340 -0
  79. package/dist/src/browser/streams.js.map +1 -0
  80. package/dist/src/browser/workers/csv-parser.worker.js +264 -0
  81. package/dist/src/browser/workers/csv-parser.worker.js.map +1 -0
  82. package/dist/src/browser/workers/worker-pool.js +338 -0
  83. package/dist/src/browser/workers/worker-pool.js.map +1 -0
  84. package/dist/src/core/delimiter-cache.js +196 -0
  85. package/dist/src/core/delimiter-cache.js.map +1 -0
  86. package/dist/src/core/node-optimizations.js +279 -0
  87. package/dist/src/core/node-optimizations.js.map +1 -0
  88. package/dist/src/core/plugin-system.js +399 -0
  89. package/dist/src/core/plugin-system.js.map +1 -0
  90. package/dist/src/core/transform-hooks.js +348 -0
  91. package/dist/src/core/transform-hooks.js.map +1 -0
  92. package/dist/src/engines/fast-path-engine-new.js +262 -0
  93. package/dist/src/engines/fast-path-engine-new.js.map +1 -0
  94. package/dist/src/engines/fast-path-engine.js +671 -0
  95. package/dist/src/engines/fast-path-engine.js.map +1 -0
  96. package/dist/src/errors.js +18 -0
  97. package/dist/src/errors.js.map +1 -0
  98. package/dist/src/formats/ndjson-parser.js +332 -0
  99. package/dist/src/formats/ndjson-parser.js.map +1 -0
  100. package/dist/src/formats/tsv-parser.js +230 -0
  101. package/dist/src/formats/tsv-parser.js.map +1 -0
  102. package/dist/src/index-with-plugins.js +259 -0
  103. package/dist/src/index-with-plugins.js.map +1 -0
  104. package/dist/src/types/index.js +3 -0
  105. package/dist/src/types/index.js.map +1 -0
  106. package/dist/src/utils/bom-utils.js +267 -0
  107. package/dist/src/utils/bom-utils.js.map +1 -0
  108. package/dist/src/utils/encoding-support.js +77 -0
  109. package/dist/src/utils/encoding-support.js.map +1 -0
  110. package/dist/src/utils/schema-validator.js +609 -0
  111. package/dist/src/utils/schema-validator.js.map +1 -0
  112. package/dist/src/utils/transform-loader.js +281 -0
  113. package/dist/src/utils/transform-loader.js.map +1 -0
  114. package/dist/src/utils/validators.js +40 -0
  115. package/dist/src/utils/validators.js.map +1 -0
  116. package/dist/src/utils/zod-adapter.js +144 -0
  117. package/dist/src/utils/zod-adapter.js.map +1 -0
  118. package/dist/src/web-server/index.js +648 -0
  119. package/dist/src/web-server/index.js.map +1 -0
  120. package/dist/src/workers/csv-multithreaded.js +211 -0
  121. package/dist/src/workers/csv-multithreaded.js.map +1 -0
  122. package/dist/src/workers/csv-parser.worker.js +179 -0
  123. package/dist/src/workers/csv-parser.worker.js.map +1 -0
  124. package/dist/src/workers/worker-pool.js +228 -0
  125. package/dist/src/workers/worker-pool.js.map +1 -0
  126. package/dist/stream-csv-to-json.js +665 -0
  127. package/dist/stream-csv-to-json.js.map +1 -0
  128. package/dist/stream-json-to-csv.js +389 -0
  129. package/dist/stream-json-to-csv.js.map +1 -0
  130. package/examples/advanced/conditional-transformations.ts +446 -0
  131. package/examples/advanced/csv-parser.worker.ts +89 -0
  132. package/examples/advanced/nested-objects-example.ts +306 -0
  133. package/examples/advanced/performance-optimization.ts +504 -0
  134. package/examples/advanced/run-demo-server.ts +116 -0
  135. package/examples/advanced/web-worker-usage.html +874 -0
  136. package/examples/async-multithreaded-example.ts +335 -0
  137. package/examples/cli-advanced-usage.md +290 -0
  138. package/examples/{cli-batch-processing.js → cli-batch-processing.ts} +38 -38
  139. package/examples/{cli-tool.js → cli-tool.ts} +5 -8
  140. package/examples/{error-handling.js → error-handling.ts} +356 -324
  141. package/examples/{express-api.js → express-api.ts} +161 -164
  142. package/examples/{large-dataset-example.js → large-dataset-example.ts} +201 -182
  143. package/examples/{ndjson-processing.js → ndjson-processing.ts} +456 -434
  144. package/examples/{plugin-excel-exporter.js → plugin-excel-exporter.ts} +6 -7
  145. package/examples/react-integration.tsx +637 -0
  146. package/examples/{schema-validation.js → schema-validation.ts} +2 -2
  147. package/examples/simple-usage.ts +194 -0
  148. package/examples/{streaming-example.js → streaming-example.ts} +12 -12
  149. package/index.d.ts +187 -18
  150. package/package.json +75 -81
  151. package/plugins.d.ts +37 -0
  152. package/schema.d.ts +103 -0
  153. package/src/browser/browser-functions.ts +402 -0
  154. package/src/browser/core.ts +152 -0
  155. package/src/browser/csv-to-json-browser.d.ts +3 -0
  156. package/src/browser/csv-to-json-browser.ts +494 -0
  157. package/src/browser/{errors-browser.js → errors-browser.ts} +305 -197
  158. package/src/browser/extensions/plugins.ts +93 -0
  159. package/src/browser/extensions/workers.ts +39 -0
  160. package/src/browser/globals.d.ts +5 -0
  161. package/src/browser/index.ts +192 -0
  162. package/src/browser/json-to-csv-browser.d.ts +3 -0
  163. package/src/browser/json-to-csv-browser.ts +338 -0
  164. package/src/browser/streams.ts +403 -0
  165. package/src/browser/workers/{csv-parser.worker.js → csv-parser.worker.ts} +3 -3
  166. package/src/browser/workers/{worker-pool.js → worker-pool.ts} +51 -30
  167. package/src/core/delimiter-cache.ts +320 -0
  168. package/src/core/{node-optimizations.js → node-optimizations.ts} +448 -407
  169. package/src/core/plugin-system.ts +588 -0
  170. package/src/core/transform-hooks.ts +566 -0
  171. package/src/engines/{fast-path-engine-new.js → fast-path-engine-new.ts} +11 -2
  172. package/src/engines/{fast-path-engine.js → fast-path-engine.ts} +79 -53
  173. package/src/errors.ts +1 -0
  174. package/src/formats/{ndjson-parser.js → ndjson-parser.ts} +24 -16
  175. package/src/formats/{tsv-parser.js → tsv-parser.ts} +18 -17
  176. package/src/{index-with-plugins.js → index-with-plugins.ts} +381 -357
  177. package/src/types/index.ts +275 -0
  178. package/src/utils/bom-utils.ts +373 -0
  179. package/src/utils/encoding-support.ts +155 -0
  180. package/src/utils/{schema-validator.js → schema-validator.ts} +814 -589
  181. package/src/utils/transform-loader.ts +389 -0
  182. package/src/utils/validators.ts +35 -0
  183. package/src/utils/zod-adapter.ts +280 -0
  184. package/src/web-server/{index.js → index.ts} +19 -19
  185. package/src/workers/csv-multithreaded.ts +310 -0
  186. package/src/workers/csv-parser.worker.ts +227 -0
  187. package/src/workers/worker-pool.ts +409 -0
  188. package/bin/jtcsv.js +0 -2462
  189. package/csv-to-json.js +0 -688
  190. package/errors.js +0 -208
  191. package/examples/simple-usage.js +0 -282
  192. package/index.js +0 -68
  193. package/json-save.js +0 -254
  194. package/json-to-csv.js +0 -526
  195. package/plugins/README.md +0 -91
  196. package/plugins/express-middleware/README.md +0 -64
  197. package/plugins/express-middleware/example.js +0 -136
  198. package/plugins/express-middleware/index.d.ts +0 -114
  199. package/plugins/express-middleware/index.js +0 -360
  200. package/plugins/express-middleware/package.json +0 -52
  201. package/plugins/fastify-plugin/index.js +0 -406
  202. package/plugins/fastify-plugin/package.json +0 -55
  203. package/plugins/hono/README.md +0 -28
  204. package/plugins/hono/index.d.ts +0 -12
  205. package/plugins/hono/index.js +0 -36
  206. package/plugins/hono/package.json +0 -35
  207. package/plugins/nestjs/README.md +0 -35
  208. package/plugins/nestjs/index.d.ts +0 -25
  209. package/plugins/nestjs/index.js +0 -77
  210. package/plugins/nestjs/package.json +0 -37
  211. package/plugins/nextjs-api/README.md +0 -57
  212. package/plugins/nextjs-api/examples/ConverterComponent.jsx +0 -386
  213. package/plugins/nextjs-api/examples/api-convert.js +0 -69
  214. package/plugins/nextjs-api/index.js +0 -387
  215. package/plugins/nextjs-api/package.json +0 -63
  216. package/plugins/nextjs-api/route.js +0 -371
  217. package/plugins/nuxt/README.md +0 -24
  218. package/plugins/nuxt/index.js +0 -21
  219. package/plugins/nuxt/package.json +0 -35
  220. package/plugins/nuxt/runtime/composables/useJtcsv.js +0 -6
  221. package/plugins/nuxt/runtime/plugin.js +0 -6
  222. package/plugins/remix/README.md +0 -26
  223. package/plugins/remix/index.d.ts +0 -16
  224. package/plugins/remix/index.js +0 -62
  225. package/plugins/remix/package.json +0 -35
  226. package/plugins/sveltekit/README.md +0 -28
  227. package/plugins/sveltekit/index.d.ts +0 -17
  228. package/plugins/sveltekit/index.js +0 -54
  229. package/plugins/sveltekit/package.json +0 -33
  230. package/plugins/trpc/README.md +0 -25
  231. package/plugins/trpc/index.d.ts +0 -7
  232. package/plugins/trpc/index.js +0 -32
  233. package/plugins/trpc/package.json +0 -34
  234. package/src/browser/browser-functions.js +0 -219
  235. package/src/browser/csv-to-json-browser.js +0 -700
  236. package/src/browser/index.js +0 -113
  237. package/src/browser/json-to-csv-browser.js +0 -309
  238. package/src/browser/streams.js +0 -393
  239. package/src/core/delimiter-cache.js +0 -186
  240. package/src/core/plugin-system.js +0 -476
  241. package/src/core/transform-hooks.js +0 -350
  242. package/src/errors.js +0 -26
  243. package/src/utils/transform-loader.js +0 -205
  244. package/stream-csv-to-json.js +0 -542
  245. package/stream-json-to-csv.js +0 -464
  246. /package/examples/{web-workers-advanced.js → web-workers-advanced.ts} +0 -0
@@ -1,700 +0,0 @@
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.js';
11
-
12
- /**
13
- * Валидация опций парсинга
14
- * @private
15
- */
16
- function validateCsvOptions(options) {
17
- // Validate options
18
- if (options && typeof options !== 'object') {
19
- throw new ConfigurationError('Options must be an object');
20
- }
21
-
22
- // Validate delimiter
23
- if (options?.delimiter && typeof options.delimiter !== 'string') {
24
- throw new ConfigurationError('Delimiter must be a string');
25
- }
26
-
27
- if (options?.delimiter && options.delimiter.length !== 1) {
28
- throw new ConfigurationError('Delimiter must be a single character');
29
- }
30
-
31
- // Validate autoDetect
32
- if (options?.autoDetect !== undefined && typeof options.autoDetect !== 'boolean') {
33
- throw new ConfigurationError('autoDetect must be a boolean');
34
- }
35
-
36
- // Validate candidates
37
- if (options?.candidates && !Array.isArray(options.candidates)) {
38
- throw new ConfigurationError('candidates must be an array');
39
- }
40
-
41
- // Validate maxRows
42
- if (options?.maxRows !== undefined && (typeof options.maxRows !== 'number' || options.maxRows <= 0)) {
43
- throw new ConfigurationError('maxRows must be a positive number');
44
- }
45
-
46
- if (options?.warnExtraFields !== undefined && typeof options.warnExtraFields !== 'boolean') {
47
- throw new ConfigurationError('warnExtraFields must be a boolean');
48
- }
49
-
50
- return true;
51
- }
52
-
53
- /**
54
- * Валидация CSV ввода и опций
55
- * @private
56
- */
57
- function validateCsvInput(csv, options) {
58
- // Validate CSV input
59
- if (typeof csv !== 'string') {
60
- throw new ValidationError('Input must be a CSV string');
61
- }
62
-
63
- return validateCsvOptions(options);
64
- }
65
-
66
- /**
67
- * Парсинг одной строки CSV с правильным экранированием
68
- * @private
69
- */
70
- function parseCsvLine(line, lineNumber, delimiter) {
71
- const fields = [];
72
- let currentField = '';
73
- let insideQuotes = false;
74
- let escapeNext = false;
75
-
76
- for (let i = 0; i < line.length; i++) {
77
- const char = line[i];
78
-
79
- if (escapeNext) {
80
- currentField += char;
81
- escapeNext = false;
82
- continue;
83
- }
84
-
85
- if (char === '\\') {
86
- if (i + 1 === line.length) {
87
- // Обратный слеш в конце строки
88
- currentField += char;
89
- } else if (line[i + 1] === '\\') {
90
- // Двойной обратный слеш
91
- currentField += char;
92
- i++; // Пропустить следующий слеш
93
- } else {
94
- // Экранирование следующего символа
95
- escapeNext = true;
96
- }
97
- continue;
98
- }
99
-
100
- if (char === '"') {
101
- if (insideQuotes) {
102
- if (i + 1 < line.length && line[i + 1] === '"') {
103
- // Экранированная кавычка внутри кавычек
104
- currentField += '"';
105
- i++; // Пропустить следующую кавычку
106
-
107
- // Проверка конца поля
108
- let isEndOfField = false;
109
- let j = i + 1;
110
- while (j < line.length && (line[j] === ' ' || line[j] === '\t')) {
111
- j++;
112
- }
113
- if (j === line.length || line[j] === delimiter) {
114
- isEndOfField = true;
115
- }
116
-
117
- if (isEndOfField) {
118
- insideQuotes = false;
119
- }
120
- } else {
121
- // Проверка конца поля
122
- let isEndOfField = false;
123
- let j = i + 1;
124
- while (j < line.length && (line[j] === ' ' || line[j] === '\t')) {
125
- j++;
126
- }
127
- if (j === line.length || line[j] === delimiter) {
128
- isEndOfField = true;
129
- }
130
-
131
- if (isEndOfField) {
132
- insideQuotes = false;
133
- } else {
134
- currentField += '"';
135
- }
136
- }
137
- } else {
138
- // Начало поля в кавычках
139
- insideQuotes = true;
140
- }
141
- continue;
142
- }
143
-
144
- if (!insideQuotes && char === delimiter) {
145
- // Конец поля
146
- fields.push(currentField);
147
- currentField = '';
148
- continue;
149
- }
150
-
151
- currentField += char;
152
- }
153
-
154
- // Обработка незавершенного экранирования
155
- if (escapeNext) {
156
- currentField += '\\';
157
- }
158
-
159
- // Добавление последнего поля
160
- fields.push(currentField);
161
-
162
- // Проверка незакрытых кавычек
163
- if (insideQuotes) {
164
- throw new ParsingError('Unclosed quotes in CSV', lineNumber);
165
- }
166
-
167
- // Валидация количества полей
168
- if (fields.length === 0) {
169
- throw new ParsingError('No fields found', lineNumber);
170
- }
171
-
172
- return fields;
173
- }
174
-
175
- /**
176
- * Парсинг значения на основе опций
177
- * @private
178
- */
179
- function parseCsvValue(value, options) {
180
- const { trim = true, parseNumbers = false, parseBooleans = false } = options;
181
-
182
- let result = value;
183
-
184
- if (trim) {
185
- result = result.trim();
186
- }
187
-
188
- // Удаление защиты формул Excel
189
- if (result.startsWith("'")) {
190
- result = result.substring(1);
191
- }
192
-
193
- // Парсинг чисел
194
- if (parseNumbers && /^-?\d+(\.\d+)?$/.test(result)) {
195
- const num = parseFloat(result);
196
- if (!isNaN(num)) {
197
- return num;
198
- }
199
- }
200
-
201
- // Парсинг булевых значений
202
- if (parseBooleans) {
203
- const lowerValue = result.toLowerCase();
204
- if (lowerValue === 'true') {
205
- return true;
206
- }
207
- if (lowerValue === 'false') {
208
- return false;
209
- }
210
- }
211
-
212
- // Пустые строки как null
213
- if (result === '') {
214
- return null;
215
- }
216
-
217
- return result;
218
- }
219
-
220
- function isSimpleCsv(csv) {
221
- return csv.indexOf('"') === -1 && csv.indexOf('\\') === -1;
222
- }
223
-
224
- function parseSimpleCsv(csv, delimiter, options) {
225
- const {
226
- hasHeaders = true,
227
- renameMap = {},
228
- trim = true,
229
- parseNumbers = false,
230
- parseBooleans = false,
231
- maxRows
232
- } = options;
233
-
234
- const result = [];
235
- let headers = null;
236
- let fieldStart = 0;
237
- let currentRow = [];
238
- let rowHasData = false;
239
- let rowCount = 0;
240
-
241
- const finalizeRow = (fields) => {
242
- if (fields.length === 1 && fields[0].trim() === '') {
243
- return;
244
- }
245
-
246
- if (!headers) {
247
- if (hasHeaders) {
248
- headers = fields.map(header => {
249
- const trimmed = trim ? header.trim() : header;
250
- return renameMap[trimmed] || trimmed;
251
- });
252
- return;
253
- }
254
-
255
- headers = fields.map((_, index) => `column${index + 1}`);
256
- }
257
-
258
- rowCount++;
259
- if (maxRows && rowCount > maxRows) {
260
- throw new LimitError(
261
- `CSV size exceeds maximum limit of ${maxRows} rows`,
262
- maxRows,
263
- rowCount
264
- );
265
- }
266
-
267
- const row = {};
268
- const fieldCount = Math.min(fields.length, headers.length);
269
- for (let i = 0; i < fieldCount; i++) {
270
- row[headers[i]] = parseCsvValue(fields[i], { trim, parseNumbers, parseBooleans });
271
- }
272
-
273
- result.push(row);
274
- };
275
-
276
- let i = 0;
277
- while (i <= csv.length) {
278
- const char = i < csv.length ? csv[i] : '\n';
279
-
280
- if (char !== '\r' && char !== '\n' && char !== ' ' && char !== '\t') {
281
- rowHasData = true;
282
- }
283
-
284
- if (char === delimiter || char === '\n' || char === '\r' || i === csv.length) {
285
- const field = csv.slice(fieldStart, i);
286
- currentRow.push(field);
287
-
288
- if (char === '\n' || char === '\r' || i === csv.length) {
289
- if (rowHasData || currentRow.length > 1) {
290
- finalizeRow(currentRow);
291
- }
292
- currentRow = [];
293
- rowHasData = false;
294
- }
295
-
296
- if (char === '\r' && csv[i + 1] === '\n') {
297
- i++;
298
- }
299
-
300
- fieldStart = i + 1;
301
- }
302
-
303
- i++;
304
- }
305
-
306
- return result;
307
- }
308
-
309
- /**
310
- * Автоматическое определение разделителя CSV
311
- *
312
- * @param {string} csv - CSV строка
313
- * @param {Array} [candidates=[';', ',', '\t', '|']] - Кандидаты на разделитель
314
- * @returns {string} Определенный разделитель
315
- */
316
- export function autoDetectDelimiter(csv, candidates = [';', ',', '\t', '|']) {
317
- if (!csv || typeof csv !== 'string') {
318
- return ';'; // значение по умолчанию
319
- }
320
-
321
- const lines = csv.split('\n').filter(line => line.trim().length > 0);
322
-
323
- if (lines.length === 0) {
324
- return ';'; // значение по умолчанию
325
- }
326
-
327
- // Использование первой непустой строки для определения
328
- const firstLine = lines[0];
329
-
330
- const counts = {};
331
- candidates.forEach(delim => {
332
- const escapedDelim = delim.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
333
- const regex = new RegExp(escapedDelim, 'g');
334
- const matches = firstLine.match(regex);
335
- counts[delim] = matches ? matches.length : 0;
336
- });
337
-
338
- // Поиск разделителя с максимальным количеством
339
- let maxCount = -1;
340
- let detectedDelimiter = ';'; // значение по умолчанию
341
-
342
- for (const [delim, count] of Object.entries(counts)) {
343
- if (count > maxCount) {
344
- maxCount = count;
345
- detectedDelimiter = delim;
346
- }
347
- }
348
-
349
- // Если разделитель не найден или ничья
350
- if (maxCount === 0) {
351
- return ';'; // значение по умолчанию
352
- }
353
-
354
- return detectedDelimiter;
355
- }
356
-
357
- /**
358
- * Конвертирует CSV строку в JSON массив
359
- *
360
- * @param {string} csv - CSV строка для конвертации
361
- * @param {Object} [options] - Опции конфигурации
362
- * @param {string} [options.delimiter] - CSV разделитель (по умолчанию: автоопределение)
363
- * @param {boolean} [options.autoDetect=true] - Автоопределение разделителя
364
- * @param {Array} [options.candidates=[';', ',', '\t', '|']] - Кандидаты для автоопределения
365
- * @param {boolean} [options.hasHeaders=true] - Есть ли заголовки в CSV
366
- * @param {Object} [options.renameMap={}] - Маппинг переименования заголовков
367
- * @param {boolean} [options.trim=true] - Обрезать пробелы
368
- * @param {boolean} [options.parseNumbers=false] - Парсить числовые значения
369
- * @param {boolean} [options.parseBooleans=false] - Парсить булевы значения
370
- * @param {number} [options.maxRows] - Максимальное количество строк
371
- * @returns {Array<Object>} JSON массив
372
- */
373
- export function csvToJson(csv, options = {}) {
374
- return safeExecute(() => {
375
- // Валидация ввода
376
- validateCsvInput(csv, options);
377
-
378
- const opts = options && typeof options === 'object' ? options : {};
379
-
380
- const {
381
- delimiter,
382
- autoDetect = true,
383
- candidates = [';', ',', '\t', '|'],
384
- hasHeaders = true,
385
- renameMap = {},
386
- trim = true,
387
- parseNumbers = false,
388
- parseBooleans = false,
389
- maxRows,
390
- warnExtraFields = true
391
- } = opts;
392
-
393
- // Определение разделителя
394
- let finalDelimiter = delimiter;
395
- if (!finalDelimiter && autoDetect) {
396
- finalDelimiter = autoDetectDelimiter(csv, candidates);
397
- }
398
- finalDelimiter = finalDelimiter || ';'; // fallback
399
-
400
- // Обработка пустого CSV
401
- if (csv.trim() === '') {
402
- return [];
403
- }
404
-
405
- if (isSimpleCsv(csv)) {
406
- return parseSimpleCsv(csv, finalDelimiter, {
407
- hasHeaders,
408
- renameMap,
409
- trim,
410
- parseNumbers,
411
- parseBooleans,
412
- maxRows
413
- });
414
- }
415
-
416
- // Парсинг CSV с обработкой кавычек и переносов строк
417
- const lines = [];
418
- let currentLine = '';
419
- let insideQuotes = false;
420
-
421
- for (let i = 0; i < csv.length; i++) {
422
- const char = csv[i];
423
-
424
- if (char === '"') {
425
- if (insideQuotes && i + 1 < csv.length && csv[i + 1] === '"') {
426
- // Экранированная кавычка внутри кавычек
427
- currentLine += '"';
428
- i++; // Пропустить следующую кавычку
429
- } else {
430
- // Переключение режима кавычек
431
- insideQuotes = !insideQuotes;
432
- }
433
- currentLine += char;
434
- continue;
435
- }
436
-
437
- if (char === '\n' && !insideQuotes) {
438
- // Конец строки (вне кавычек)
439
- lines.push(currentLine);
440
- currentLine = '';
441
- continue;
442
- }
443
-
444
- if (char === '\r') {
445
- // Игнорировать carriage return
446
- continue;
447
- }
448
-
449
- currentLine += char;
450
- }
451
-
452
- // Добавление последней строки
453
- if (currentLine !== '' || insideQuotes) {
454
- lines.push(currentLine);
455
- }
456
-
457
- if (lines.length === 0) {
458
- return [];
459
- }
460
-
461
- // Предупреждение для больших наборов данных
462
- if (lines.length > 1000000 && !maxRows && process.env.NODE_ENV !== 'production') {
463
- console.warn(
464
- '⚠️ Warning: Processing >1M records in memory may be slow.\n' +
465
- '💡 Consider using Web Workers for better performance with large files.\n' +
466
- '📊 Current size: ' + lines.length.toLocaleString() + ' rows'
467
- );
468
- }
469
-
470
- // Применение ограничения по строкам
471
- if (maxRows && lines.length > maxRows) {
472
- throw new LimitError(
473
- `CSV size exceeds maximum limit of ${maxRows} rows`,
474
- maxRows,
475
- lines.length
476
- );
477
- }
478
-
479
- let headers = [];
480
- let startIndex = 0;
481
-
482
- // Парсинг заголовков если есть
483
- if (hasHeaders && lines.length > 0) {
484
- try {
485
- headers = parseCsvLine(lines[0], 1, finalDelimiter).map(header => {
486
- const trimmed = trim ? header.trim() : header;
487
- return renameMap[trimmed] || trimmed;
488
- });
489
- startIndex = 1;
490
- } catch (error) {
491
- if (error instanceof ParsingError) {
492
- throw new ParsingError(`Failed to parse headers: ${error.message}`, 1);
493
- }
494
- throw error;
495
- }
496
- } else {
497
- // Генерация числовых заголовков из первой строки
498
- try {
499
- const firstLineFields = parseCsvLine(lines[0], 1, finalDelimiter);
500
- headers = firstLineFields.map((_, index) => `column${index + 1}`);
501
- } catch (error) {
502
- if (error instanceof ParsingError) {
503
- throw new ParsingError(`Failed to parse first line: ${error.message}`, 1);
504
- }
505
- throw error;
506
- }
507
- }
508
-
509
- // Парсинг строк данных
510
- const result = [];
511
-
512
- for (let i = startIndex; i < lines.length; i++) {
513
- const line = lines[i];
514
-
515
- // Пропуск пустых строк
516
- if (line.trim() === '') {
517
- continue;
518
- }
519
-
520
- try {
521
- const fields = parseCsvLine(line, i + 1, finalDelimiter);
522
-
523
- // Обработка несоответствия количества полей
524
- const row = {};
525
- const fieldCount = Math.min(fields.length, headers.length);
526
-
527
- for (let j = 0; j < fieldCount; j++) {
528
- row[headers[j]] = parseCsvValue(fields[j], { trim, parseNumbers, parseBooleans });
529
- }
530
-
531
- // Предупреждение о лишних полях
532
- const isDev = typeof process !== 'undefined' && process.env && process.env.NODE_ENV === 'development';
533
- if (fields.length > headers.length && warnExtraFields && isDev) {
534
- console.warn(`[jtcsv] Line ${i + 1}: ${fields.length - headers.length} extra fields ignored`);
535
- }
536
-
537
- result.push(row);
538
- } catch (error) {
539
- if (error instanceof ParsingError) {
540
- throw new ParsingError(`Line ${i + 1}: ${error.message}`, i + 1);
541
- }
542
- throw error;
543
- }
544
- }
545
-
546
- return result;
547
- }, 'PARSE_FAILED', { function: 'csvToJson' });
548
- }
549
-
550
- export async function* csvToJsonIterator(input, options = {}) {
551
- const opts = options && typeof options === 'object' ? options : {};
552
- validateCsvOptions(opts);
553
-
554
- if (typeof input === 'string') {
555
- const rows = csvToJson(input, options);
556
- for (const row of rows) {
557
- yield row;
558
- }
559
- return;
560
- }
561
-
562
- const {
563
- delimiter,
564
- autoDetect = true,
565
- candidates = [';', ',', '\t', '|'],
566
- hasHeaders = true,
567
- renameMap = {},
568
- trim = true,
569
- parseNumbers = false,
570
- parseBooleans = false,
571
- maxRows
572
- } = opts;
573
-
574
- const stream = (input instanceof Blob && input.stream) ? input.stream() : input;
575
- if (!stream || typeof stream.getReader !== 'function') {
576
- throw new ValidationError('Input must be a CSV string, Blob/File, or ReadableStream');
577
- }
578
-
579
- const reader = stream.getReader();
580
- const decoder = new TextDecoder('utf-8');
581
- let buffer = '';
582
- let insideQuotes = false;
583
- let headers = null;
584
- let rowCount = 0;
585
- let lineNumber = 0;
586
- let finalDelimiter = delimiter;
587
- let delimiterResolved = Boolean(finalDelimiter);
588
-
589
- const processFields = (fields) => {
590
- if (fields.length === 1 && fields[0].trim() === '') {
591
- return null;
592
- }
593
-
594
- rowCount++;
595
- if (maxRows && rowCount > maxRows) {
596
- throw new LimitError(
597
- `CSV size exceeds maximum limit of ${maxRows} rows`,
598
- maxRows,
599
- rowCount
600
- );
601
- }
602
-
603
- const row = {};
604
- const fieldCount = Math.min(fields.length, headers.length);
605
- for (let j = 0; j < fieldCount; j++) {
606
- row[headers[j]] = parseCsvValue(fields[j], { trim, parseNumbers, parseBooleans });
607
- }
608
- return row;
609
- };
610
-
611
- const processLine = (line) => {
612
- lineNumber++;
613
- let cleanLine = line;
614
- if (cleanLine.endsWith('\r')) {
615
- cleanLine = cleanLine.slice(0, -1);
616
- }
617
-
618
- if (!delimiterResolved) {
619
- if (!finalDelimiter && autoDetect) {
620
- finalDelimiter = autoDetectDelimiter(cleanLine, candidates);
621
- }
622
- finalDelimiter = finalDelimiter || ';';
623
- delimiterResolved = true;
624
- }
625
-
626
- if (cleanLine.trim() === '') {
627
- return null;
628
- }
629
-
630
- if (!headers) {
631
- if (hasHeaders) {
632
- headers = parseCsvLine(cleanLine, lineNumber, finalDelimiter).map(header => {
633
- const trimmed = trim ? header.trim() : header;
634
- return renameMap[trimmed] || trimmed;
635
- });
636
- return null;
637
- }
638
-
639
- const fields = parseCsvLine(cleanLine, lineNumber, finalDelimiter);
640
- headers = fields.map((_, index) => `column${index + 1}`);
641
- return processFields(fields);
642
- }
643
-
644
- const fields = parseCsvLine(cleanLine, lineNumber, finalDelimiter);
645
- return processFields(fields);
646
- };
647
-
648
- while (true) {
649
- const { value, done } = await reader.read();
650
- if (done) {
651
- break;
652
- }
653
-
654
- buffer += decoder.decode(value, { stream: true });
655
-
656
- let start = 0;
657
- for (let i = 0; i < buffer.length; i++) {
658
- const char = buffer[i];
659
- if (char === '"') {
660
- if (insideQuotes && buffer[i + 1] === '"') {
661
- i++;
662
- continue;
663
- }
664
- insideQuotes = !insideQuotes;
665
- continue;
666
- }
667
-
668
- if (char === '\n' && !insideQuotes) {
669
- const line = buffer.slice(start, i);
670
- start = i + 1;
671
- const row = processLine(line);
672
- if (row) {
673
- yield row;
674
- }
675
- }
676
- }
677
-
678
- buffer = buffer.slice(start);
679
- }
680
-
681
- if (buffer.length > 0) {
682
- const row = processLine(buffer);
683
- if (row) {
684
- yield row;
685
- }
686
- }
687
-
688
- if (insideQuotes) {
689
- throw new ParsingError('Unclosed quotes in CSV', lineNumber);
690
- }
691
- }
692
-
693
- // Экспорт для Node.js совместимости
694
- if (typeof module !== 'undefined' && module.exports) {
695
- module.exports = {
696
- csvToJson,
697
- autoDetectDelimiter,
698
- csvToJsonIterator
699
- };
700
- }