web-csv-toolbox 0.1.0 → 0.3.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 +117 -64
- package/lib/index.d.ts +496 -23
- package/lib/index.js +33 -216
- package/lib/index.umd.js +1 -0
- package/package.json +7 -4
package/lib/index.js
CHANGED
|
@@ -1,33 +1,11 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* FiledDelimiter is a symbol for field delimiter of CSV.
|
|
3
|
-
*/
|
|
4
1
|
const FieldDelimiter = Symbol.for("web-streams-csv.FieldDelimiter");
|
|
5
|
-
/**
|
|
6
|
-
* RecordDelimiter is a symbol for record delimiter of CSV.
|
|
7
|
-
*/
|
|
8
2
|
const RecordDelimiter = Symbol.for("web-streams-csv.RecordDelimiter");
|
|
9
|
-
/**
|
|
10
|
-
* Field is a symbol for field of CSV.
|
|
11
|
-
*/
|
|
12
3
|
const Field = Symbol.for("web-streams-csv.Field");
|
|
13
|
-
|
|
14
4
|
const CR = "\r";
|
|
15
5
|
const CRLF = "\r\n";
|
|
16
6
|
const LF = "\n";
|
|
17
|
-
/**
|
|
18
|
-
* COMMA is a symbol for comma(,).
|
|
19
|
-
*/
|
|
20
7
|
const COMMA = ",";
|
|
21
|
-
/**
|
|
22
|
-
* DOUBLE_QUATE is a symbol for double quate(").
|
|
23
|
-
*/
|
|
24
8
|
const DOUBLE_QUATE = '"';
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Assert that the options are valid.
|
|
28
|
-
*
|
|
29
|
-
* @param options The options to assert.
|
|
30
|
-
*/
|
|
31
9
|
function assertCommonOptions(options) {
|
|
32
10
|
if (typeof options.quotation === "string" && options.quotation.length === 0) {
|
|
33
11
|
throw new Error("quotation must not be empty");
|
|
@@ -50,42 +28,9 @@ function assertCommonOptions(options) {
|
|
|
50
28
|
);
|
|
51
29
|
}
|
|
52
30
|
}
|
|
53
|
-
|
|
54
|
-
/**
|
|
55
|
-
* Escape a string for use in a regular expression.
|
|
56
|
-
*
|
|
57
|
-
* @see {@link https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_expressions#escaping Regular expressions#Escaping | MDN}
|
|
58
|
-
* @param v string to escape
|
|
59
|
-
* @returns escaped string
|
|
60
|
-
*/
|
|
61
31
|
function escapeRegExp(v) {
|
|
62
32
|
return v.replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
|
|
63
33
|
}
|
|
64
|
-
|
|
65
|
-
/**
|
|
66
|
-
* A transform stream that converts a stream of tokens into a stream of rows.
|
|
67
|
-
*
|
|
68
|
-
* @example Parse a CSV with headers by data
|
|
69
|
-
* ```ts
|
|
70
|
-
* new ReadableStream({
|
|
71
|
-
* start(controller) {
|
|
72
|
-
* controller.enqueue("name,age\r\n");
|
|
73
|
-
* controller.enqueue("Alice,20\r\n");
|
|
74
|
-
* controller.close();
|
|
75
|
-
* }
|
|
76
|
-
* })
|
|
77
|
-
* .pipeThrough(new LexerTransformer())
|
|
78
|
-
* .pipeTo(new WritableStream({ write(token) { console.log(token); }}));
|
|
79
|
-
* // { type: Field, value: "name" }
|
|
80
|
-
* // { type: FieldDelimiter, value: "," }
|
|
81
|
-
* // { type: Field, value: "age" }
|
|
82
|
-
* // { type: RecordDelimiter, value: "\r\n" }
|
|
83
|
-
* // { type: Field, value: "Alice" }
|
|
84
|
-
* // { type: FieldDelimiter, value: "," }
|
|
85
|
-
* // { type: Field, value: "20" }
|
|
86
|
-
* // { type: RecordDelimiter, value: "\r\n" }
|
|
87
|
-
* ```
|
|
88
|
-
*/
|
|
89
34
|
class LexerTransformer extends TransformStream {
|
|
90
35
|
#demiliter;
|
|
91
36
|
#demiliterLength;
|
|
@@ -161,49 +106,38 @@ class LexerTransformer extends TransformStream {
|
|
|
161
106
|
if (this.#buffer.length === 0) {
|
|
162
107
|
return null;
|
|
163
108
|
}
|
|
164
|
-
// Check for CRLF
|
|
165
109
|
if (this.#buffer.startsWith(CRLF)) {
|
|
166
110
|
this.#buffer = this.#buffer.slice(2);
|
|
167
111
|
return { type: RecordDelimiter, value: CRLF };
|
|
168
112
|
}
|
|
169
|
-
// Check for LF
|
|
170
113
|
if (this.#buffer.startsWith(LF)) {
|
|
171
114
|
this.#buffer = this.#buffer.slice(1);
|
|
172
115
|
return { type: RecordDelimiter, value: LF };
|
|
173
116
|
}
|
|
174
|
-
// Check for Delimiter
|
|
175
117
|
if (this.#buffer.startsWith(this.#demiliter)) {
|
|
176
118
|
this.#buffer = this.#buffer.slice(this.#demiliterLength);
|
|
177
119
|
return { type: FieldDelimiter, value: this.#demiliter };
|
|
178
120
|
}
|
|
179
|
-
// Check for Quoted String
|
|
180
121
|
if (this.#buffer.startsWith(this.#quotation)) {
|
|
181
|
-
// If we're flushing and the buffer doesn't end with a quote, then return null
|
|
182
|
-
// because we're not done with the quoted string
|
|
183
122
|
if (flush === false && this.#buffer.endsWith(this.#quotation)) {
|
|
184
123
|
return null;
|
|
185
124
|
}
|
|
186
125
|
return this.extractQuotedString();
|
|
187
126
|
}
|
|
188
|
-
// Check for Unquoted String
|
|
189
127
|
const match = this.#matcher.exec(this.#buffer);
|
|
190
128
|
if (match) {
|
|
191
|
-
// If we're flushing and the match doesn't consume the entire buffer,
|
|
192
|
-
// then return null
|
|
193
129
|
if (flush === false && match[0].length === this.#buffer.length) {
|
|
194
130
|
return null;
|
|
195
131
|
}
|
|
196
132
|
this.#buffer = this.#buffer.slice(match[0].length);
|
|
197
133
|
return { type: Field, value: match[0] };
|
|
198
134
|
}
|
|
199
|
-
// Otherwise, return null
|
|
200
135
|
return null;
|
|
201
136
|
}
|
|
202
137
|
extractQuotedString() {
|
|
203
|
-
let end = this.#quotationLength;
|
|
138
|
+
let end = this.#quotationLength;
|
|
204
139
|
let value = "";
|
|
205
140
|
while (end < this.#buffer.length) {
|
|
206
|
-
// Escaped quote
|
|
207
141
|
if (
|
|
208
142
|
this.#buffer.slice(end, end + this.#quotationLength) ===
|
|
209
143
|
this.quotation &&
|
|
@@ -216,7 +150,6 @@ class LexerTransformer extends TransformStream {
|
|
|
216
150
|
end += this.#quotationLength * 2;
|
|
217
151
|
continue;
|
|
218
152
|
}
|
|
219
|
-
// Closing quote
|
|
220
153
|
if (
|
|
221
154
|
this.#buffer.slice(end, end + this.#quotationLength) === this.quotation
|
|
222
155
|
) {
|
|
@@ -226,52 +159,9 @@ class LexerTransformer extends TransformStream {
|
|
|
226
159
|
value += this.#buffer[end];
|
|
227
160
|
end++;
|
|
228
161
|
}
|
|
229
|
-
// If we get here, we've reached the end of the buffer
|
|
230
162
|
return null;
|
|
231
163
|
}
|
|
232
164
|
}
|
|
233
|
-
|
|
234
|
-
/**
|
|
235
|
-
* A transform stream that converts a stream of tokens into a stream of rows.
|
|
236
|
-
* @template Header The type of the header row.
|
|
237
|
-
* @param options The options for the parser.
|
|
238
|
-
*
|
|
239
|
-
* @example Parse a CSV with headers by data
|
|
240
|
-
* ```ts
|
|
241
|
-
* new ReadableStream({
|
|
242
|
-
* start(controller) {
|
|
243
|
-
* controller.enqueue("name,age\r\n");
|
|
244
|
-
* controller.enqueue("Alice,20\r\n");
|
|
245
|
-
* controller.enqueue("Bob,25\r\n");
|
|
246
|
-
* controller.enqueue("Charlie,30\r\n");
|
|
247
|
-
* controller.close();
|
|
248
|
-
* })
|
|
249
|
-
* .pipeThrough(new LexerTransformer())
|
|
250
|
-
* .pipeThrough(new RecordAssemblerTransformar())
|
|
251
|
-
* .pipeTo(new WritableStream({ write(row) { console.log(row); }}));
|
|
252
|
-
* // { name: "Alice", age: "20" }
|
|
253
|
-
* // { name: "Bob", age: "25" }
|
|
254
|
-
* // { name: "Charlie", age: "30" }
|
|
255
|
-
* ```
|
|
256
|
-
*
|
|
257
|
-
* @example Parse a CSV with headers by options
|
|
258
|
-
* ```ts
|
|
259
|
-
* new ReadableStream({
|
|
260
|
-
* start(controller) {
|
|
261
|
-
* controller.enqueue("Alice,20\r\n");
|
|
262
|
-
* controller.enqueue("Bob,25\r\n");
|
|
263
|
-
* controller.enqueue("Charlie,30\r\n");
|
|
264
|
-
* controller.close();
|
|
265
|
-
* }
|
|
266
|
-
* })
|
|
267
|
-
* .pipeThrough(new LexerTransformer())
|
|
268
|
-
* .pipeThrough(new RecordAssemblerTransformar({ header: ["name", "age"] }))
|
|
269
|
-
* .pipeTo(new WritableStream({ write(row) { console.log(row); }}));
|
|
270
|
-
* // { name: "Alice", age: "20" }
|
|
271
|
-
* // { name: "Bob", age: "25" }
|
|
272
|
-
* // { name: "Charlie", age: "30" }
|
|
273
|
-
* ```
|
|
274
|
-
*/
|
|
275
165
|
class RecordAssemblerTransformar extends TransformStream {
|
|
276
166
|
#fieldIndex = 0;
|
|
277
167
|
#row = [];
|
|
@@ -301,7 +191,6 @@ class RecordAssemblerTransformar extends TransformStream {
|
|
|
301
191
|
controller.enqueue(record);
|
|
302
192
|
}
|
|
303
193
|
}
|
|
304
|
-
// Reset the row fields buffer.
|
|
305
194
|
this.#fieldIndex = 0;
|
|
306
195
|
this.#row = new Array(this.#header?.length);
|
|
307
196
|
this.#darty = false;
|
|
@@ -310,7 +199,6 @@ class RecordAssemblerTransformar extends TransformStream {
|
|
|
310
199
|
},
|
|
311
200
|
flush: (controller) => {
|
|
312
201
|
if (this.#fieldIndex !== 0 && this.#header !== undefined) {
|
|
313
|
-
// console.log('B', this.#row)
|
|
314
202
|
if (this.#darty) {
|
|
315
203
|
const record = Object.fromEntries(
|
|
316
204
|
this.#header
|
|
@@ -336,7 +224,6 @@ class RecordAssemblerTransformar extends TransformStream {
|
|
|
336
224
|
}
|
|
337
225
|
}
|
|
338
226
|
}
|
|
339
|
-
|
|
340
227
|
class SingleValueReadableStream extends ReadableStream {
|
|
341
228
|
constructor(value) {
|
|
342
229
|
super({
|
|
@@ -347,7 +234,6 @@ class SingleValueReadableStream extends ReadableStream {
|
|
|
347
234
|
});
|
|
348
235
|
}
|
|
349
236
|
}
|
|
350
|
-
|
|
351
237
|
async function toArray(...args) {
|
|
352
238
|
const rows = [];
|
|
353
239
|
for await (const row of this(...args)) {
|
|
@@ -355,13 +241,6 @@ async function toArray(...args) {
|
|
|
355
241
|
}
|
|
356
242
|
return rows;
|
|
357
243
|
}
|
|
358
|
-
|
|
359
|
-
/**
|
|
360
|
-
* Parse CSV string to records.
|
|
361
|
-
*
|
|
362
|
-
* @param stream CSV string stream to parse
|
|
363
|
-
* @param options Parsing options. See {@link ParseOptions}.
|
|
364
|
-
*/
|
|
365
244
|
async function* parseStringStream(stream, options) {
|
|
366
245
|
let controller;
|
|
367
246
|
const readable = new ReadableStream({
|
|
@@ -387,49 +266,28 @@ async function* parseStringStream(stream, options) {
|
|
|
387
266
|
reader.releaseLock();
|
|
388
267
|
}
|
|
389
268
|
}
|
|
390
|
-
(
|
|
391
|
-
parseStringStream
|
|
392
|
-
);
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
/**
|
|
396
|
-
* Parse CSV string to records.
|
|
397
|
-
*
|
|
398
|
-
* @param csv CSV string to parse
|
|
399
|
-
* @param options Parsing options. See {@link ParseOptions}.
|
|
400
|
-
*/
|
|
401
|
-
async function* streamingParse(csv, options) {
|
|
269
|
+
((parseStringStream) => {
|
|
270
|
+
parseStringStream.toArray = toArray;
|
|
271
|
+
})(parseStringStream || (parseStringStream = {}));
|
|
272
|
+
async function* parseString(csv, options) {
|
|
402
273
|
yield* parseStringStream(new SingleValueReadableStream(csv), options);
|
|
403
274
|
}
|
|
404
|
-
(
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
/**
|
|
408
|
-
* Parse CSV to records.
|
|
409
|
-
* This function is for parsing a binary stream.
|
|
410
|
-
*
|
|
411
|
-
* @remarks
|
|
412
|
-
* If you want to parse a string, use {@link streamingParse}.
|
|
413
|
-
* @param stream CSV string to parse
|
|
414
|
-
* @param options Parsing options. See {@link ParseBinaryOptions}.
|
|
415
|
-
*/
|
|
275
|
+
((parseString) => {
|
|
276
|
+
parseString.toArray = toArray;
|
|
277
|
+
})(parseString || (parseString = {}));
|
|
416
278
|
async function* parseBinaryStream(stream, options) {
|
|
417
279
|
const { charset, fatal, ignoreBOM, decomposition } = options ?? {};
|
|
418
280
|
yield* parseStringStream(
|
|
419
281
|
[
|
|
420
|
-
// NOTE: if decompression is undefined, it will be ignored.
|
|
421
282
|
...(decomposition ? [new DecompressionStream(decomposition)] : []),
|
|
422
|
-
// NOTE: if charset is undefined, it will be decoded as utf-8.
|
|
423
283
|
new TextDecoderStream(charset, { fatal, ignoreBOM }),
|
|
424
284
|
].reduce((stream, transformer) => stream.pipeThrough(transformer), stream),
|
|
425
285
|
options,
|
|
426
286
|
);
|
|
427
287
|
}
|
|
428
|
-
(
|
|
429
|
-
parseBinaryStream
|
|
430
|
-
);
|
|
431
|
-
parseBinaryStream.toArray = toArray;
|
|
432
|
-
|
|
288
|
+
((parseBinaryStream) => {
|
|
289
|
+
parseBinaryStream.toArray = toArray;
|
|
290
|
+
})(parseBinaryStream || (parseBinaryStream = {}));
|
|
433
291
|
function parseMime(contentType) {
|
|
434
292
|
const [type, ...parameters] = contentType.split(";");
|
|
435
293
|
const result = {
|
|
@@ -442,7 +300,6 @@ function parseMime(contentType) {
|
|
|
442
300
|
}
|
|
443
301
|
return result;
|
|
444
302
|
}
|
|
445
|
-
|
|
446
303
|
function parseResponse(response, options) {
|
|
447
304
|
const { headers } = response;
|
|
448
305
|
const contentType = headers.get("content-type") ?? "text/csv";
|
|
@@ -452,8 +309,6 @@ function parseResponse(response, options) {
|
|
|
452
309
|
}
|
|
453
310
|
const decomposition = headers.get("content-encoding") ?? undefined;
|
|
454
311
|
const charset = mime.parameters.charset ?? "utf-8";
|
|
455
|
-
// TODO: Support header=present and header=absent
|
|
456
|
-
// const header = mime.parameters.header ?? "present";
|
|
457
312
|
if (response.body === null) {
|
|
458
313
|
throw new Error("Response body is null");
|
|
459
314
|
}
|
|
@@ -463,75 +318,35 @@ function parseResponse(response, options) {
|
|
|
463
318
|
...options,
|
|
464
319
|
});
|
|
465
320
|
}
|
|
466
|
-
(
|
|
467
|
-
parseResponse.toArray = toArray;
|
|
468
|
-
|
|
469
|
-
/**
|
|
470
|
-
* Parse CSV Stream to records.
|
|
471
|
-
* string and Uint8Array are supported.
|
|
472
|
-
*
|
|
473
|
-
* @remarks
|
|
474
|
-
* {@link parseStringStream} and {@link parseBinaryStream} are used internally.
|
|
475
|
-
* If you known the type of the stream, it performs better to use them directly.
|
|
476
|
-
*
|
|
477
|
-
* If you want to parse a string, use {@link parseStringStream}.
|
|
478
|
-
* If you want to parse a Uint8Array, use {@link parseBinaryStream}.
|
|
479
|
-
*
|
|
480
|
-
* @param csv CSV string to parse
|
|
481
|
-
* @param options Parsing options. See {@link ParserOptions}.
|
|
482
|
-
*/
|
|
321
|
+
((parseResponse) => {
|
|
322
|
+
parseResponse.toArray = toArray;
|
|
323
|
+
})(parseResponse || (parseResponse = {}));
|
|
483
324
|
async function* parseStream(stream, options) {
|
|
484
325
|
const [branch1, branch2] = stream.tee();
|
|
485
326
|
const reader1 = branch1.getReader();
|
|
486
327
|
const { value: firstChunk } = await reader1.read();
|
|
487
328
|
reader1.releaseLock();
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
case firstChunk instanceof Uint8Array:
|
|
493
|
-
yield* parseBinaryStream(branch2, options);
|
|
494
|
-
break;
|
|
329
|
+
if (typeof firstChunk === "string") {
|
|
330
|
+
yield* parseStringStream(branch2, options);
|
|
331
|
+
} else if (firstChunk instanceof Uint8Array) {
|
|
332
|
+
yield* parseBinaryStream(branch2, options);
|
|
495
333
|
}
|
|
496
334
|
}
|
|
497
|
-
(
|
|
498
|
-
parseStream.toArray = toArray;
|
|
499
|
-
|
|
500
|
-
/**
|
|
501
|
-
* Parse CSV to records.
|
|
502
|
-
*
|
|
503
|
-
* {@link String}, {@link Uint8Array}, ReadableStream<string | Uint8Array> and Response are supported.
|
|
504
|
-
*
|
|
505
|
-
* @remarks
|
|
506
|
-
* {@link streamingParse}, {@link parseBinaryStream},
|
|
507
|
-
* {@link parseStringStream} and {@link parseResponse} are used internally.
|
|
508
|
-
* If you known the type of the stream, it performs better to use them directly.
|
|
509
|
-
*
|
|
510
|
-
* If you want to parse a string, use {@link streamingParse}.
|
|
511
|
-
* If you want to parse a Uint8Array, use {@link parseStream}.
|
|
512
|
-
* If you want to parse a ReadableStream<string>, use {@link parseStringStream}.
|
|
513
|
-
* If you want to parse a ReadableStream<Uint8Array>, use {@link parseBinaryStream}.
|
|
514
|
-
* If you want to parse a Response, use {@link parseResponse}.
|
|
515
|
-
*
|
|
516
|
-
* @param csv CSV string to parse
|
|
517
|
-
* @param options Parsing options. See {@link ParseOptions}.
|
|
518
|
-
*/
|
|
335
|
+
((parseStream) => {
|
|
336
|
+
parseStream.toArray = toArray;
|
|
337
|
+
})(parseStream || (parseStream = {}));
|
|
519
338
|
async function* parse(csv, options) {
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
break;
|
|
527
|
-
case csv instanceof Response:
|
|
528
|
-
yield* parseResponse(csv, options);
|
|
529
|
-
break;
|
|
339
|
+
if (typeof csv === "string") {
|
|
340
|
+
yield* parseString(csv, options);
|
|
341
|
+
} else if (csv instanceof ReadableStream) {
|
|
342
|
+
yield* parseStream(csv, options);
|
|
343
|
+
} else if (csv instanceof Response) {
|
|
344
|
+
yield* parseResponse(csv, options);
|
|
530
345
|
}
|
|
531
346
|
}
|
|
532
|
-
(
|
|
533
|
-
parse.toArray = toArray;
|
|
534
|
-
|
|
347
|
+
((parse) => {
|
|
348
|
+
parse.toArray = toArray;
|
|
349
|
+
})(parse || (parse = {}));
|
|
535
350
|
export {
|
|
536
351
|
Field,
|
|
537
352
|
FieldDelimiter,
|
|
@@ -540,6 +355,8 @@ export {
|
|
|
540
355
|
RecordDelimiter,
|
|
541
356
|
parse,
|
|
542
357
|
parseBinaryStream,
|
|
358
|
+
parseResponse,
|
|
359
|
+
parseStream,
|
|
360
|
+
parseString,
|
|
543
361
|
parseStringStream,
|
|
544
|
-
streamingParse,
|
|
545
362
|
};
|
package/lib/index.umd.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
!function(e,t){"object"==typeof exports&&"undefined"!=typeof module?t(exports):"function"==typeof define&&define.amd?define(["exports"],t):t((e="undefined"!=typeof globalThis?globalThis:e||self).CSV={})}(this,(function(e){"use strict";const t=Symbol.for("web-streams-csv.FieldDelimiter"),i=Symbol.for("web-streams-csv.RecordDelimiter"),r=Symbol.for("web-streams-csv.Field"),n="\n";function s(e){return e.replace(/[.*+?^${}()|[\]\\]/g,"\\$&")}class o extends TransformStream{#e;#t;#i;#r;#n;#s="";get demiliter(){return this.#e}get quotation(){return this.#i}constructor({demiliter:e=",",quotation:t='"'}={}){!function(e){if("string"==typeof e.quotation&&0===e.quotation.length)throw new Error("quotation must not be empty");if("string"==typeof e.demiliter&&0===e.demiliter.length)throw new Error("demiliter must not be empty");if(e.quotation.includes(n)||e.quotation.includes("\r"))throw new Error("quotation must not include CR or LF");if(e.demiliter.includes(n)||e.demiliter.includes("\r"))throw new Error("demiliter must not include CR or LF");if(e.demiliter.includes(e.quotation)||e.quotation.includes(e.demiliter))throw new Error("demiliter and quotation must not include each other as a substring")}({demiliter:e,quotation:t}),super({transform:(e,t)=>{if(0!==e.length){this.#s+=e;for(const e of this.#o({flush:!1}))t.enqueue(e)}},flush:e=>{for(const t of this.#o({flush:!0}))e.enqueue(t)}}),this.#e=e,this.#t=e.length,this.#i=t,this.#r=t.length;const i=s(e),r=s(t);this.#n=new RegExp(`^(?:(?!${r})(?!${i})(?![\\r\\n]))([\\S\\s\\uFEFF\\xA0]+?)(?=${r}|${i}|\\r|\\n|$)`)}*#o({flush:e}){let n=null;for(let s;s=this.#a({flush:e});)switch(s.type){case r:n?n.value+=s.value:n=s;break;case t:case i:n&&(yield n,n=null),yield s}n&&(yield n)}#a({flush:e=!1}={}){if(0===this.#s.length)return null;if(this.#s.startsWith("\r\n"))return this.#s=this.#s.slice(2),{type:i,value:"\r\n"};if(this.#s.startsWith(n))return this.#s=this.#s.slice(1),{type:i,value:n};if(this.#s.startsWith(this.#e))return this.#s=this.#s.slice(this.#t),{type:t,value:this.#e};if(this.#s.startsWith(this.#i))return!1===e&&this.#s.endsWith(this.#i)?null:this.extractQuotedString();const s=this.#n.exec(this.#s);return s?!1===e&&s[0].length===this.#s.length?null:(this.#s=this.#s.slice(s[0].length),{type:r,value:s[0]}):null}extractQuotedString(){let e=this.#r,t="";for(;e<this.#s.length;)if(this.#s.slice(e,e+this.#r)!==this.quotation||this.#s.slice(e+this.#r,e+2*this.#r)!==this.quotation){if(this.#s.slice(e,e+this.#r)===this.quotation)return this.#s=this.#s.slice(e+this.#r),{type:r,value:t};t+=this.#s[e],e++}else t+=this.quotation,e+=2*this.#r;return null}}class a extends TransformStream{#u=0;#h=[];#l;#f=!1;constructor(e={}){super({transform:(e,n)=>{switch(e.type){case r:this.#f=!0,this.#h[this.#u]=e.value;break;case t:this.#u++;break;case i:if(void 0===this.#l)this.#d(this.#h);else if(this.#f){const e=Object.fromEntries(this.#l.filter((e=>e)).map(((e,t)=>[e,this.#h.at(t)])));n.enqueue(e)}this.#u=0,this.#h=new Array(this.#l?.length),this.#f=!1}},flush:e=>{if(0!==this.#u&&void 0!==this.#l&&this.#f){const t=Object.fromEntries(this.#l.filter((e=>e)).map(((e,t)=>[e,this.#h.at(t)])));e.enqueue(t)}}}),void 0!==e.header&&Array.isArray(e.header)&&this.#d(e.header)}#d(e){if(this.#l=e,0===this.#l.length)throw new Error("The header must not be empty.");if(new Set(this.#l).size!==this.#l.length)throw new Error("The header must not contain duplicate fields.")}}class u extends ReadableStream{constructor(e){super({start(t){t.enqueue(e),t.close()}})}}async function h(...e){const t=[];for await(const i of this(...e))t.push(i);return t}async function*l(e,t){let i;const r=new ReadableStream({start:e=>i=e});await e.pipeThrough(new o(t)).pipeThrough(new a(t)).pipeTo(new WritableStream({write:e=>i.enqueue(e),close:()=>i.close()}));const n=r.getReader();try{for(;;){const{value:e,done:t}=await n.read();if(t)break;yield e}}finally{n.releaseLock()}}async function*f(e,t){yield*l(new u(e),t)}async function*d(e,t){const{charset:i,fatal:r,ignoreBOM:n,decomposition:s}=t??{};yield*l([...s?[new DecompressionStream(s)]:[],new TextDecoderStream(i,{fatal:r,ignoreBOM:n})].reduce(((e,t)=>e.pipeThrough(t)),e),t)}function c(e,t){const{headers:i}=e,r=i.get("content-type")??"text/csv",n=function(e){const[t,...i]=e.split(";"),r={type:t.trim(),parameters:{}};for(const e of i){const[t,i]=e.split("=");r.parameters[t.trim()]=i.trim()}return r}(r);if("text/csv"!==n.type)throw new Error(`Invalid mime type: ${r}`);const s=i.get("content-encoding")??void 0,o=n.parameters.charset??"utf-8";if(null===e.body)throw new Error("Response body is null");return d(e.body,{decomposition:s,charset:o,...t})}async function*m(e,t){const[i,r]=e.tee(),n=i.getReader(),{value:s}=await n.read();n.releaseLock(),"string"==typeof s?yield*l(r,t):s instanceof Uint8Array&&(yield*d(r,t))}async function*y(e,t){"string"==typeof e?yield*f(e,t):e instanceof ReadableStream?yield*m(e,t):e instanceof Response&&(yield*c(e,t))}!function(e){e.toArray=h}(l||(l={})),function(e){e.toArray=h}(f||(f={})),function(e){e.toArray=h}(d||(d={})),function(e){e.toArray=h}(c||(c={})),function(e){e.toArray=h}(m||(m={})),function(e){e.toArray=h}(y||(y={})),e.Field=r,e.FieldDelimiter=t,e.LexerTransformer=o,e.RecordAssemblerTransformar=a,e.RecordDelimiter=i,e.parse=y,e.parseBinaryStream=d,e.parseResponse=c,e.parseStream=m,e.parseString=f,e.parseStringStream=l}));
|
package/package.json
CHANGED
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "web-csv-toolbox",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "A CSV Toolbox utilizing Web Standard APIs.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "lib/index.js",
|
|
7
7
|
"module": "lib/index.js",
|
|
8
8
|
"types": "lib/index.d.ts",
|
|
9
|
+
"unpkg": "lib/index.umd.js",
|
|
9
10
|
"engines": {
|
|
10
11
|
"node": ">=18.0.0"
|
|
11
12
|
},
|
|
@@ -27,7 +28,7 @@
|
|
|
27
28
|
"lint": "biome lint .",
|
|
28
29
|
"check": "biome check src --apply",
|
|
29
30
|
"check:no-apply": "biome check src",
|
|
30
|
-
"build": "rollup -c rollup.config.ts --configPlugin rollup-plugin-typescript2 && biome
|
|
31
|
+
"build": "rollup -c rollup.config.ts --configPlugin rollup-plugin-typescript2 && biome check lib --apply",
|
|
31
32
|
"prepare": "husky install"
|
|
32
33
|
},
|
|
33
34
|
"repository": {
|
|
@@ -45,12 +46,13 @@
|
|
|
45
46
|
"bugs": {
|
|
46
47
|
"url": "https://github.com/kamiazya/web-csv-toolbox/issues"
|
|
47
48
|
},
|
|
48
|
-
"homepage": "https://github.
|
|
49
|
+
"homepage": "https://kamiazya.github.io/web-csv-toolbox/",
|
|
49
50
|
"devDependencies": {
|
|
50
51
|
"@biomejs/biome": "1.4.1",
|
|
51
52
|
"@changesets/changelog-github": "^0.5.0",
|
|
52
53
|
"@changesets/cli": "^2.27.1",
|
|
53
54
|
"@fast-check/vitest": "^0.0.9",
|
|
55
|
+
"@rollup/plugin-terser": "^0.4.4",
|
|
54
56
|
"changesets-github-release": "^0.1.0",
|
|
55
57
|
"husky": "^8.0.0",
|
|
56
58
|
"jsdom": "^23.0.1",
|
|
@@ -60,7 +62,8 @@
|
|
|
60
62
|
"rollup-plugin-dts": "^6.1.0",
|
|
61
63
|
"rollup-plugin-typescript2": "^0.36.0",
|
|
62
64
|
"typedoc": "^0.25.4",
|
|
65
|
+
"typedoc-plugin-mdn-links": "^3.1.9",
|
|
63
66
|
"typescript": "^5.3.2",
|
|
64
67
|
"vitest": "^1.1.0"
|
|
65
68
|
}
|
|
66
|
-
}
|
|
69
|
+
}
|