@nyaomaru/divider 2.0.6 → 2.0.8
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/dist/index.cjs +158 -65
- package/dist/index.d.cts +87 -27
- package/dist/index.d.ts +87 -27
- package/dist/index.js +158 -65
- package/package.json +1 -1
package/dist/index.cjs
CHANGED
|
@@ -122,7 +122,10 @@ function isNoneMode(value) {
|
|
|
122
122
|
function normalizeSeparators(separators) {
|
|
123
123
|
return Array.from(new Set(separators)).filter(
|
|
124
124
|
(separator) => !isEmptyString(separator)
|
|
125
|
-
);
|
|
125
|
+
).sort((left, right) => right.length - left.length);
|
|
126
|
+
}
|
|
127
|
+
function createCacheKey(normalizedSeparators) {
|
|
128
|
+
return normalizedSeparators.join(CACHE_KEY_SEPARATOR);
|
|
126
129
|
}
|
|
127
130
|
var RegexCache = class {
|
|
128
131
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -138,6 +141,15 @@ var RegexCache = class {
|
|
|
138
141
|
*/
|
|
139
142
|
get(separators) {
|
|
140
143
|
const key = this.createKey(separators);
|
|
144
|
+
return this.getByKey(key);
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Retrieves a cached RegExp for a precomputed cache key.
|
|
148
|
+
*
|
|
149
|
+
* @param key - Cache key string
|
|
150
|
+
* @returns Cached RegExp or null if not found
|
|
151
|
+
*/
|
|
152
|
+
getByKey(key) {
|
|
141
153
|
const regex = this.cache.get(key);
|
|
142
154
|
if (regex) {
|
|
143
155
|
this.cache.delete(key);
|
|
@@ -153,6 +165,15 @@ var RegexCache = class {
|
|
|
153
165
|
*/
|
|
154
166
|
set(separators, regex) {
|
|
155
167
|
const key = this.createKey(separators);
|
|
168
|
+
this.setByKey(key, regex);
|
|
169
|
+
}
|
|
170
|
+
/**
|
|
171
|
+
* Stores a RegExp in the cache for a precomputed cache key.
|
|
172
|
+
*
|
|
173
|
+
* @param key - Cache key string
|
|
174
|
+
* @param regex - Compiled RegExp to cache
|
|
175
|
+
*/
|
|
176
|
+
setByKey(key, regex) {
|
|
156
177
|
if (this.cache.has(key)) {
|
|
157
178
|
this.cache.delete(key);
|
|
158
179
|
}
|
|
@@ -173,7 +194,7 @@ var RegexCache = class {
|
|
|
173
194
|
*/
|
|
174
195
|
createKey(separators) {
|
|
175
196
|
const normalizedSeparators = normalizeSeparators(separators);
|
|
176
|
-
return normalizedSeparators
|
|
197
|
+
return createCacheKey(normalizedSeparators);
|
|
177
198
|
}
|
|
178
199
|
/**
|
|
179
200
|
* Gets current cache size for debugging/monitoring.
|
|
@@ -194,15 +215,16 @@ var RegexCache = class {
|
|
|
194
215
|
var regexCache = new RegexCache();
|
|
195
216
|
function getRegex(separators) {
|
|
196
217
|
if (isEmptyArray(separators)) return null;
|
|
197
|
-
const cached = regexCache.get(separators);
|
|
198
|
-
if (cached) return cached;
|
|
199
218
|
const uniqueSeparators = normalizeSeparators(separators);
|
|
200
219
|
if (uniqueSeparators.length === 0) {
|
|
201
220
|
return null;
|
|
202
221
|
}
|
|
222
|
+
const cacheKey = createCacheKey(uniqueSeparators);
|
|
223
|
+
const cached = regexCache.getByKey(cacheKey);
|
|
224
|
+
if (cached) return cached;
|
|
203
225
|
const pattern = uniqueSeparators.map(escapeRegExp).join("|");
|
|
204
226
|
const regex = new RegExp(`(?:${pattern})`, "g");
|
|
205
|
-
regexCache.
|
|
227
|
+
regexCache.setByKey(cacheKey, regex);
|
|
206
228
|
return regex;
|
|
207
229
|
}
|
|
208
230
|
function escapeRegExp(str) {
|
|
@@ -326,6 +348,13 @@ function classifySeparators(args) {
|
|
|
326
348
|
);
|
|
327
349
|
}
|
|
328
350
|
|
|
351
|
+
// src/utils/transform-divider-input.ts
|
|
352
|
+
function transformDividerInput(input, transform, options) {
|
|
353
|
+
const result = isString(input) ? transform(input) : input.map(transform);
|
|
354
|
+
const resolvedOptions = options ?? {};
|
|
355
|
+
return applyDividerOptions(result, resolvedOptions);
|
|
356
|
+
}
|
|
357
|
+
|
|
329
358
|
// src/core/divider.ts
|
|
330
359
|
function divider(input, ...args) {
|
|
331
360
|
if (!isValidInput(input)) {
|
|
@@ -342,8 +371,7 @@ function divider(input, ...args) {
|
|
|
342
371
|
const applyDivision = (str) => divideString(str, numSeparators, strSeparators, {
|
|
343
372
|
preserveEmpty: options.preserveEmpty
|
|
344
373
|
});
|
|
345
|
-
|
|
346
|
-
return applyDividerOptions(result, options);
|
|
374
|
+
return transformDividerInput(input, applyDivision, options);
|
|
347
375
|
}
|
|
348
376
|
|
|
349
377
|
// src/core/divider-first.ts
|
|
@@ -394,15 +422,15 @@ function dividerLoop(input, size, options) {
|
|
|
394
422
|
console.warn("dividerLoop: chunk size must be a positive number");
|
|
395
423
|
return [];
|
|
396
424
|
}
|
|
397
|
-
const resolvedOptions = options ?? {};
|
|
398
425
|
const {
|
|
399
426
|
startOffset = PERFORMANCE_CONSTANTS.DEFAULT_START_OFFSET,
|
|
400
427
|
maxChunks = PERFORMANCE_CONSTANTS.DEFAULT_MAX_CHUNKS
|
|
401
|
-
} =
|
|
402
|
-
|
|
403
|
-
|
|
428
|
+
} = options ?? {};
|
|
429
|
+
return transformDividerInput(
|
|
430
|
+
input,
|
|
431
|
+
(str) => createChunksFromString(str, size, startOffset, maxChunks),
|
|
432
|
+
options
|
|
404
433
|
);
|
|
405
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
406
434
|
}
|
|
407
435
|
|
|
408
436
|
// src/utils/divide.ts
|
|
@@ -412,40 +440,54 @@ function divideNumberString(str) {
|
|
|
412
440
|
|
|
413
441
|
// src/core/divider-number-string.ts
|
|
414
442
|
function dividerNumberString(input, options) {
|
|
415
|
-
|
|
416
|
-
const resolvedOptions = options ?? {};
|
|
417
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
443
|
+
return transformDividerInput(input, divideNumberString, options);
|
|
418
444
|
}
|
|
419
445
|
|
|
420
|
-
// src/utils/
|
|
421
|
-
|
|
422
|
-
if (isEmptyString(input)) return [""];
|
|
423
|
-
return divider(input, separator, { preserveEmpty: true });
|
|
424
|
-
}
|
|
425
|
-
function countUnescaped(text, quote) {
|
|
426
|
-
const pair = quote + quote;
|
|
446
|
+
// src/utils/count-unescaped.ts
|
|
447
|
+
var countUnescapedSingleChar = (text, quote) => {
|
|
427
448
|
let count = 0;
|
|
428
|
-
for (
|
|
429
|
-
|
|
449
|
+
for (let index = 0; index < text.length; index++) {
|
|
450
|
+
if (text[index] !== quote) continue;
|
|
451
|
+
if (text[index + 1] === quote) {
|
|
452
|
+
index++;
|
|
453
|
+
continue;
|
|
454
|
+
}
|
|
455
|
+
count++;
|
|
430
456
|
}
|
|
431
457
|
return count;
|
|
458
|
+
};
|
|
459
|
+
var countUnescapedMultiChar = (text, quote) => {
|
|
460
|
+
const escapedPair = quote + quote;
|
|
461
|
+
const quoteLength = quote.length;
|
|
462
|
+
const escapedPairLength = escapedPair.length;
|
|
463
|
+
let count = 0;
|
|
464
|
+
for (let index = 0; index < text.length; ) {
|
|
465
|
+
if (text.startsWith(escapedPair, index)) {
|
|
466
|
+
index += escapedPairLength;
|
|
467
|
+
continue;
|
|
468
|
+
}
|
|
469
|
+
if (text.startsWith(quote, index)) {
|
|
470
|
+
count++;
|
|
471
|
+
index += quoteLength;
|
|
472
|
+
continue;
|
|
473
|
+
}
|
|
474
|
+
index++;
|
|
475
|
+
}
|
|
476
|
+
return count;
|
|
477
|
+
};
|
|
478
|
+
function countUnescaped(text, quote) {
|
|
479
|
+
if (isEmptyString(quote)) return 0;
|
|
480
|
+
if (quote.length === 1) return countUnescapedSingleChar(text, quote);
|
|
481
|
+
return countUnescapedMultiChar(text, quote);
|
|
432
482
|
}
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
return restoreEscapedQuotes(stripped);
|
|
439
|
-
}
|
|
440
|
-
function quotedDivide(line, {
|
|
441
|
-
delimiter = ",",
|
|
442
|
-
quote = '"',
|
|
443
|
-
trim = false,
|
|
444
|
-
lenient = true
|
|
445
|
-
} = {}) {
|
|
446
|
-
if (isEmptyString(line)) return [""];
|
|
447
|
-
return buildQuotedFields(line, delimiter, quote, trim, lenient);
|
|
483
|
+
|
|
484
|
+
// src/utils/divide-preserve.ts
|
|
485
|
+
function dividePreserve(input, separator) {
|
|
486
|
+
if (isEmptyString(input)) return [""];
|
|
487
|
+
return divider(input, separator, { preserveEmpty: true });
|
|
448
488
|
}
|
|
489
|
+
|
|
490
|
+
// src/utils/strip-outer-quotes.ts
|
|
449
491
|
var findNonSpaceBounds = (text, isWhitespace) => {
|
|
450
492
|
let left = 0;
|
|
451
493
|
let right = text.length - 1;
|
|
@@ -453,34 +495,70 @@ var findNonSpaceBounds = (text, isWhitespace) => {
|
|
|
453
495
|
while (right >= left && isWhitespace(text[right])) right--;
|
|
454
496
|
return { left, right };
|
|
455
497
|
};
|
|
456
|
-
var stripMatchedOuterQuotes = (text, left,
|
|
457
|
-
var
|
|
458
|
-
var stripTrailingQuote = (text,
|
|
498
|
+
var stripMatchedOuterQuotes = (text, left, rightQuoteStart, quote) => text.slice(0, left) + text.slice(left + quote.length, rightQuoteStart) + text.slice(rightQuoteStart + quote.length);
|
|
499
|
+
var removeQuoteAt = (text, start, quote) => text.slice(0, start) + text.slice(start + quote.length);
|
|
500
|
+
var stripTrailingQuote = (text, quote, isWhitespace) => {
|
|
501
|
+
const quoteLength = quote.length;
|
|
459
502
|
let lastNonSpaceIndex = text.length - 1;
|
|
460
503
|
while (lastNonSpaceIndex >= 0 && isWhitespace(text[lastNonSpaceIndex])) {
|
|
461
504
|
lastNonSpaceIndex--;
|
|
462
505
|
}
|
|
463
|
-
|
|
506
|
+
const trailingQuoteStart = lastNonSpaceIndex - quoteLength + 1;
|
|
507
|
+
if (trailingQuoteStart < 0 || !text.startsWith(quote, trailingQuoteStart)) {
|
|
464
508
|
return text;
|
|
465
509
|
}
|
|
466
|
-
return
|
|
510
|
+
return removeQuoteAt(text, trailingQuoteStart, quote);
|
|
467
511
|
};
|
|
468
|
-
var stripOuterQuotesRaw = (text,
|
|
512
|
+
var stripOuterQuotesRaw = (text, quote, lenient, isWhitespace) => {
|
|
513
|
+
if (quote.length === 0) return text;
|
|
469
514
|
const { left, right } = findNonSpaceBounds(text, isWhitespace);
|
|
470
515
|
if (left > right) return text;
|
|
471
|
-
if (text
|
|
472
|
-
|
|
473
|
-
|
|
516
|
+
if (!text.startsWith(quote, left)) return text;
|
|
517
|
+
const rightQuoteStart = right - quote.length + 1;
|
|
518
|
+
if (rightQuoteStart >= left + quote.length && text.startsWith(quote, rightQuoteStart)) {
|
|
519
|
+
return stripMatchedOuterQuotes(text, left, rightQuoteStart, quote);
|
|
474
520
|
}
|
|
475
521
|
if (!lenient) return text;
|
|
476
|
-
const withoutLeading =
|
|
477
|
-
return stripTrailingQuote(withoutLeading,
|
|
522
|
+
const withoutLeading = removeQuoteAt(text, left, quote);
|
|
523
|
+
return stripTrailingQuote(withoutLeading, quote, isWhitespace);
|
|
524
|
+
};
|
|
525
|
+
function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
|
|
526
|
+
const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
|
|
527
|
+
const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
|
|
528
|
+
if (quoteChar.length === 0) return stripped;
|
|
529
|
+
const escapedPair = quoteChar + quoteChar;
|
|
530
|
+
return stripped.split(escapedPair).join(quoteChar);
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
// src/utils/quoted-parser.ts
|
|
534
|
+
var advanceQuoteState = (insideQuotes, segment, quote) => {
|
|
535
|
+
let nextInsideQuotes = insideQuotes;
|
|
536
|
+
for (const char of segment) {
|
|
537
|
+
if (char === quote) nextInsideQuotes = !nextInsideQuotes;
|
|
538
|
+
}
|
|
539
|
+
return nextInsideQuotes;
|
|
540
|
+
};
|
|
541
|
+
var flushField = (state, quote, trim, lenient) => {
|
|
542
|
+
let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
|
|
543
|
+
if (trim) fieldValue = fieldValue.trim();
|
|
544
|
+
state.fields.push(fieldValue);
|
|
545
|
+
state.current = "";
|
|
546
|
+
state.insideQuotes = false;
|
|
547
|
+
};
|
|
548
|
+
var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
|
|
549
|
+
const segment = isEmptyString(state.current) ? piece : delimiter + piece;
|
|
550
|
+
state.current += segment;
|
|
551
|
+
state.insideQuotes = quote.length === 1 ? advanceQuoteState(state.insideQuotes, segment, quote) : countUnescaped(state.current, quote) % 2 === 1;
|
|
552
|
+
if (!state.insideQuotes) {
|
|
553
|
+
flushField(state, quote, trim, lenient);
|
|
554
|
+
}
|
|
478
555
|
};
|
|
479
556
|
var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
480
557
|
const pieces = dividePreserve(line, delimiter);
|
|
481
558
|
const state = {
|
|
482
559
|
fields: [],
|
|
483
|
-
current: ""
|
|
560
|
+
current: "",
|
|
561
|
+
insideQuotes: false
|
|
484
562
|
};
|
|
485
563
|
for (const piece of pieces) {
|
|
486
564
|
appendPiece(state, piece, delimiter, quote, trim, lenient);
|
|
@@ -490,19 +568,15 @@ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
|
490
568
|
}
|
|
491
569
|
return state.fields;
|
|
492
570
|
};
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
if (trim) fieldValue = fieldValue.trim();
|
|
503
|
-
state.fields.push(fieldValue);
|
|
504
|
-
state.current = "";
|
|
505
|
-
};
|
|
571
|
+
function quotedDivide(line, {
|
|
572
|
+
delimiter = ",",
|
|
573
|
+
quote = '"',
|
|
574
|
+
trim = false,
|
|
575
|
+
lenient = true
|
|
576
|
+
} = {}) {
|
|
577
|
+
if (isEmptyString(line)) return [""];
|
|
578
|
+
return buildQuotedFields(line, delimiter, quote, trim, lenient);
|
|
579
|
+
}
|
|
506
580
|
|
|
507
581
|
// src/presets/csv-divider.ts
|
|
508
582
|
function csvDivider(line, options = {}) {
|
|
@@ -553,8 +627,27 @@ function tryExtractQuery(input) {
|
|
|
553
627
|
const url = new URL(input);
|
|
554
628
|
return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
|
|
555
629
|
} catch {
|
|
556
|
-
return input;
|
|
630
|
+
return extractQueryFromQuestionMark(input);
|
|
631
|
+
}
|
|
632
|
+
}
|
|
633
|
+
function extractQueryFromQuestionMark(input) {
|
|
634
|
+
const fragmentIndex = input.indexOf("#");
|
|
635
|
+
const withoutFragment = fragmentIndex >= 0 ? input.slice(0, fragmentIndex) : input;
|
|
636
|
+
const questionMarkIndex = withoutFragment.indexOf(
|
|
637
|
+
QUERY_SEPARATORS.QUESTION_MARK
|
|
638
|
+
);
|
|
639
|
+
if (questionMarkIndex < 0) {
|
|
640
|
+
return withoutFragment;
|
|
641
|
+
}
|
|
642
|
+
if (questionMarkIndex === 0) {
|
|
643
|
+
return withoutFragment.slice(1);
|
|
644
|
+
}
|
|
645
|
+
const prefix = withoutFragment.slice(0, questionMarkIndex);
|
|
646
|
+
const hasQuerySeparatorBefore = prefix.includes(QUERY_SEPARATORS.AMPERSAND) || prefix.includes(QUERY_SEPARATORS.EQUALS);
|
|
647
|
+
if (hasQuerySeparatorBefore) {
|
|
648
|
+
return withoutFragment;
|
|
557
649
|
}
|
|
650
|
+
return withoutFragment.slice(questionMarkIndex + 1);
|
|
558
651
|
}
|
|
559
652
|
function stripLeadingQuestionMark(query) {
|
|
560
653
|
return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
|
package/dist/index.d.cts
CHANGED
|
@@ -21,21 +21,42 @@ declare const QUERY_DECODE_MODES: {
|
|
|
21
21
|
readonly RAW: "raw";
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Supported exclusion modes used to filter divider output.
|
|
26
|
+
*/
|
|
24
27
|
type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
|
|
28
|
+
/**
|
|
29
|
+
* Single string input accepted by divider functions.
|
|
30
|
+
*/
|
|
25
31
|
type StringInput = string;
|
|
32
|
+
/**
|
|
33
|
+
* Readonly string array input accepted by divider functions.
|
|
34
|
+
*/
|
|
26
35
|
type StringArrayInput = readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Supported input shapes for divider functions.
|
|
38
|
+
*/
|
|
27
39
|
type DividerInput = StringInput | StringArrayInput;
|
|
40
|
+
/**
|
|
41
|
+
* Flat divider result.
|
|
42
|
+
*/
|
|
28
43
|
type DividerStringResult = string[];
|
|
44
|
+
/**
|
|
45
|
+
* Nested divider result for `string[]` inputs when `flatten` is not enabled.
|
|
46
|
+
*/
|
|
29
47
|
type DividerArrayResult = string[][];
|
|
48
|
+
/**
|
|
49
|
+
* Shared options supported by divider functions.
|
|
50
|
+
*/
|
|
30
51
|
type DividerOptions = {
|
|
31
52
|
/** If true, flattens nested arrays into a single array */
|
|
32
|
-
flatten?: boolean;
|
|
53
|
+
readonly flatten?: boolean;
|
|
33
54
|
/** If true, trims whitespace from each divided segment */
|
|
34
|
-
trim?: boolean;
|
|
55
|
+
readonly trim?: boolean;
|
|
35
56
|
/** If true, retains empty segments produced by division */
|
|
36
|
-
preserveEmpty?: boolean;
|
|
57
|
+
readonly preserveEmpty?: boolean;
|
|
37
58
|
/** Controls how empty or whitespace segments are handled */
|
|
38
|
-
exclude?: DividerExcludeMode;
|
|
59
|
+
readonly exclude?: DividerExcludeMode;
|
|
39
60
|
};
|
|
40
61
|
/**
|
|
41
62
|
* Represents the absence of user-specified divider options while retaining
|
|
@@ -52,37 +73,76 @@ type DividerEmptyOptions = {
|
|
|
52
73
|
readonly exclude?: never;
|
|
53
74
|
};
|
|
54
75
|
type DividerInferredOptions = DividerOptions | DividerEmptyOptions;
|
|
55
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Computes whether the provided options explicitly enable `flatten`.
|
|
78
|
+
*/
|
|
79
|
+
type IsFlattenEnabled<TOptions extends DividerInferredOptions> = TOptions extends {
|
|
56
80
|
readonly flatten?: infer Flag;
|
|
57
|
-
} ? Flag extends true ? true : false : false;
|
|
58
|
-
|
|
81
|
+
} ? [Flag] extends [true] ? true : false : false;
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the divider output shape from the input and options.
|
|
84
|
+
*/
|
|
85
|
+
type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : IsFlattenEnabled<TOptions> extends true ? DividerStringResult : DividerArrayResult;
|
|
86
|
+
/**
|
|
87
|
+
* Numeric separator interpreted as an index.
|
|
88
|
+
*/
|
|
89
|
+
type NumericSeparator = number;
|
|
90
|
+
/**
|
|
91
|
+
* String separator interpreted as a delimiter.
|
|
92
|
+
*/
|
|
93
|
+
type StringSeparator = string;
|
|
94
|
+
/**
|
|
95
|
+
* Any supported divider separator.
|
|
96
|
+
*/
|
|
97
|
+
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
98
|
+
/**
|
|
99
|
+
* A readonly list of separators without options.
|
|
100
|
+
*/
|
|
101
|
+
type DividerSeparators = readonly DividerSeparator[];
|
|
102
|
+
/**
|
|
103
|
+
* Any single divider argument.
|
|
104
|
+
*/
|
|
105
|
+
type DividerArg = DividerSeparator | DividerOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Divider arguments with an optional trailing options object.
|
|
108
|
+
*
|
|
109
|
+
* WHY: Runtime only consumes options when they are passed last. This tuple
|
|
110
|
+
* shape keeps the public type contract aligned with that behavior.
|
|
111
|
+
*/
|
|
112
|
+
type DividerArgs = DividerSeparators | readonly [...DividerSeparator[], DividerOptions];
|
|
113
|
+
/**
|
|
114
|
+
* Extracts trailing divider options from a variadic argument tuple.
|
|
115
|
+
*/
|
|
116
|
+
type ExtractedDividerOptions<TArgs extends readonly unknown[]> = TArgs extends readonly [...unknown[], infer Last] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions;
|
|
59
117
|
/**
|
|
60
118
|
* Computes the divider return type based on the input and provided arguments.
|
|
61
119
|
*
|
|
62
120
|
* WHY: When no arguments are provided, divider returns the input as-is for
|
|
63
121
|
* string arrays. This type keeps the signature aligned with runtime behavior.
|
|
64
122
|
*/
|
|
65
|
-
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T :
|
|
66
|
-
|
|
123
|
+
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T : IsFlattenEnabled<ExtractedDividerOptions<TArgs>> extends true ? DividerStringResult : DividerArrayResult;
|
|
124
|
+
/**
|
|
125
|
+
* Additional options supported by `dividerLoop`.
|
|
126
|
+
*/
|
|
67
127
|
type DividerLoopOptions = DividerOptions & {
|
|
68
128
|
/** Starting position for the division (0-based) */
|
|
69
|
-
startOffset?: number;
|
|
129
|
+
readonly startOffset?: number;
|
|
70
130
|
/** Maximum number of chunks to produce */
|
|
71
|
-
maxChunks?: number;
|
|
131
|
+
readonly maxChunks?: number;
|
|
72
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Represents the absence of loop-specific options while preserving inference.
|
|
135
|
+
*/
|
|
73
136
|
type DividerLoopEmptyOptions = DividerEmptyOptions & {
|
|
74
137
|
/** Placeholder to signal startOffset is intentionally absent */
|
|
75
138
|
readonly startOffset?: never;
|
|
76
139
|
/** Placeholder to signal maxChunks is intentionally absent */
|
|
77
140
|
readonly maxChunks?: never;
|
|
78
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Accepted option shapes for `dividerLoop`.
|
|
144
|
+
*/
|
|
79
145
|
type DividerLoopOptionsLike = DividerLoopOptions | DividerLoopEmptyOptions;
|
|
80
|
-
type NumericSeparator = number;
|
|
81
|
-
type StringSeparator = string;
|
|
82
|
-
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
83
|
-
type DividerSeparators = readonly DividerSeparator[];
|
|
84
|
-
type DividerArg = DividerSeparator | DividerOptions;
|
|
85
|
-
type DividerArgs = readonly DividerArg[];
|
|
86
146
|
|
|
87
147
|
/**
|
|
88
148
|
* Main divider function that splits input based on numeric positions or string delimiters.
|
|
@@ -96,7 +156,7 @@ type DividerArgs = readonly DividerArg[];
|
|
|
96
156
|
* @param args - Array of separators (numbers/strings) and optional options object
|
|
97
157
|
* @returns Divided string segments based on input type and options
|
|
98
158
|
*/
|
|
99
|
-
declare function divider<T extends DividerInput, const TArgs extends
|
|
159
|
+
declare function divider<T extends DividerInput, const TArgs extends DividerArgs>(input: T, ...args: TArgs): DividerReturn<T, TArgs>;
|
|
100
160
|
|
|
101
161
|
/**
|
|
102
162
|
* Extracts the first segment after dividing the input using specified separators.
|
|
@@ -160,22 +220,22 @@ declare function dividerNumberString<T extends DividerInput, O extends DividerIn
|
|
|
160
220
|
|
|
161
221
|
type EmailDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
162
222
|
/** Split top-level domain from the rest of the email address. */
|
|
163
|
-
splitTLD?: boolean;
|
|
223
|
+
readonly splitTLD?: boolean;
|
|
164
224
|
};
|
|
165
225
|
type CsvDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
166
|
-
/**
|
|
167
|
-
quoteChar?: string;
|
|
168
|
-
/**
|
|
169
|
-
delimiter?: string;
|
|
226
|
+
/** Quote string used for quoting values. */
|
|
227
|
+
readonly quoteChar?: string;
|
|
228
|
+
/** Delimiter string used to separate CSV fields. */
|
|
229
|
+
readonly delimiter?: string;
|
|
170
230
|
};
|
|
171
231
|
type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
172
232
|
/** Collapse empty segments produced by leading/trailing or repeated separators. */
|
|
173
|
-
collapse?: boolean;
|
|
233
|
+
readonly collapse?: boolean;
|
|
174
234
|
};
|
|
175
235
|
type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
|
|
176
236
|
type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
177
237
|
/** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
|
|
178
|
-
mode?: QueryDecodeMode;
|
|
238
|
+
readonly mode?: QueryDecodeMode;
|
|
179
239
|
};
|
|
180
240
|
|
|
181
241
|
/**
|
|
@@ -183,8 +243,8 @@ type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
|
183
243
|
*
|
|
184
244
|
* @param line - The CSV line string to be divided into fields
|
|
185
245
|
* @param options - Configuration options for CSV parsing
|
|
186
|
-
* @param options.delimiter - The
|
|
187
|
-
* @param options.quoteChar - The
|
|
246
|
+
* @param options.delimiter - The delimiter string used to separate fields (default: ',')
|
|
247
|
+
* @param options.quoteChar - The quote string used to wrap fields containing delimiters or newlines (default: '"')
|
|
188
248
|
* @param options.trim - Whether to trim whitespace from field values (default: false)
|
|
189
249
|
* @returns A DividerStringResult containing the parsed CSV fields
|
|
190
250
|
*/
|
package/dist/index.d.ts
CHANGED
|
@@ -21,21 +21,42 @@ declare const QUERY_DECODE_MODES: {
|
|
|
21
21
|
readonly RAW: "raw";
|
|
22
22
|
};
|
|
23
23
|
|
|
24
|
+
/**
|
|
25
|
+
* Supported exclusion modes used to filter divider output.
|
|
26
|
+
*/
|
|
24
27
|
type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
|
|
28
|
+
/**
|
|
29
|
+
* Single string input accepted by divider functions.
|
|
30
|
+
*/
|
|
25
31
|
type StringInput = string;
|
|
32
|
+
/**
|
|
33
|
+
* Readonly string array input accepted by divider functions.
|
|
34
|
+
*/
|
|
26
35
|
type StringArrayInput = readonly string[];
|
|
36
|
+
/**
|
|
37
|
+
* Supported input shapes for divider functions.
|
|
38
|
+
*/
|
|
27
39
|
type DividerInput = StringInput | StringArrayInput;
|
|
40
|
+
/**
|
|
41
|
+
* Flat divider result.
|
|
42
|
+
*/
|
|
28
43
|
type DividerStringResult = string[];
|
|
44
|
+
/**
|
|
45
|
+
* Nested divider result for `string[]` inputs when `flatten` is not enabled.
|
|
46
|
+
*/
|
|
29
47
|
type DividerArrayResult = string[][];
|
|
48
|
+
/**
|
|
49
|
+
* Shared options supported by divider functions.
|
|
50
|
+
*/
|
|
30
51
|
type DividerOptions = {
|
|
31
52
|
/** If true, flattens nested arrays into a single array */
|
|
32
|
-
flatten?: boolean;
|
|
53
|
+
readonly flatten?: boolean;
|
|
33
54
|
/** If true, trims whitespace from each divided segment */
|
|
34
|
-
trim?: boolean;
|
|
55
|
+
readonly trim?: boolean;
|
|
35
56
|
/** If true, retains empty segments produced by division */
|
|
36
|
-
preserveEmpty?: boolean;
|
|
57
|
+
readonly preserveEmpty?: boolean;
|
|
37
58
|
/** Controls how empty or whitespace segments are handled */
|
|
38
|
-
exclude?: DividerExcludeMode;
|
|
59
|
+
readonly exclude?: DividerExcludeMode;
|
|
39
60
|
};
|
|
40
61
|
/**
|
|
41
62
|
* Represents the absence of user-specified divider options while retaining
|
|
@@ -52,37 +73,76 @@ type DividerEmptyOptions = {
|
|
|
52
73
|
readonly exclude?: never;
|
|
53
74
|
};
|
|
54
75
|
type DividerInferredOptions = DividerOptions | DividerEmptyOptions;
|
|
55
|
-
|
|
76
|
+
/**
|
|
77
|
+
* Computes whether the provided options explicitly enable `flatten`.
|
|
78
|
+
*/
|
|
79
|
+
type IsFlattenEnabled<TOptions extends DividerInferredOptions> = TOptions extends {
|
|
56
80
|
readonly flatten?: infer Flag;
|
|
57
|
-
} ? Flag extends true ? true : false : false;
|
|
58
|
-
|
|
81
|
+
} ? [Flag] extends [true] ? true : false : false;
|
|
82
|
+
/**
|
|
83
|
+
* Resolves the divider output shape from the input and options.
|
|
84
|
+
*/
|
|
85
|
+
type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : IsFlattenEnabled<TOptions> extends true ? DividerStringResult : DividerArrayResult;
|
|
86
|
+
/**
|
|
87
|
+
* Numeric separator interpreted as an index.
|
|
88
|
+
*/
|
|
89
|
+
type NumericSeparator = number;
|
|
90
|
+
/**
|
|
91
|
+
* String separator interpreted as a delimiter.
|
|
92
|
+
*/
|
|
93
|
+
type StringSeparator = string;
|
|
94
|
+
/**
|
|
95
|
+
* Any supported divider separator.
|
|
96
|
+
*/
|
|
97
|
+
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
98
|
+
/**
|
|
99
|
+
* A readonly list of separators without options.
|
|
100
|
+
*/
|
|
101
|
+
type DividerSeparators = readonly DividerSeparator[];
|
|
102
|
+
/**
|
|
103
|
+
* Any single divider argument.
|
|
104
|
+
*/
|
|
105
|
+
type DividerArg = DividerSeparator | DividerOptions;
|
|
106
|
+
/**
|
|
107
|
+
* Divider arguments with an optional trailing options object.
|
|
108
|
+
*
|
|
109
|
+
* WHY: Runtime only consumes options when they are passed last. This tuple
|
|
110
|
+
* shape keeps the public type contract aligned with that behavior.
|
|
111
|
+
*/
|
|
112
|
+
type DividerArgs = DividerSeparators | readonly [...DividerSeparator[], DividerOptions];
|
|
113
|
+
/**
|
|
114
|
+
* Extracts trailing divider options from a variadic argument tuple.
|
|
115
|
+
*/
|
|
116
|
+
type ExtractedDividerOptions<TArgs extends readonly unknown[]> = TArgs extends readonly [...unknown[], infer Last] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions;
|
|
59
117
|
/**
|
|
60
118
|
* Computes the divider return type based on the input and provided arguments.
|
|
61
119
|
*
|
|
62
120
|
* WHY: When no arguments are provided, divider returns the input as-is for
|
|
63
121
|
* string arrays. This type keeps the signature aligned with runtime behavior.
|
|
64
122
|
*/
|
|
65
|
-
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T :
|
|
66
|
-
|
|
123
|
+
type DividerReturn<T extends DividerInput, TArgs extends DividerArgs> = T extends StringInput ? DividerStringResult : TArgs['length'] extends 0 ? T : IsFlattenEnabled<ExtractedDividerOptions<TArgs>> extends true ? DividerStringResult : DividerArrayResult;
|
|
124
|
+
/**
|
|
125
|
+
* Additional options supported by `dividerLoop`.
|
|
126
|
+
*/
|
|
67
127
|
type DividerLoopOptions = DividerOptions & {
|
|
68
128
|
/** Starting position for the division (0-based) */
|
|
69
|
-
startOffset?: number;
|
|
129
|
+
readonly startOffset?: number;
|
|
70
130
|
/** Maximum number of chunks to produce */
|
|
71
|
-
maxChunks?: number;
|
|
131
|
+
readonly maxChunks?: number;
|
|
72
132
|
};
|
|
133
|
+
/**
|
|
134
|
+
* Represents the absence of loop-specific options while preserving inference.
|
|
135
|
+
*/
|
|
73
136
|
type DividerLoopEmptyOptions = DividerEmptyOptions & {
|
|
74
137
|
/** Placeholder to signal startOffset is intentionally absent */
|
|
75
138
|
readonly startOffset?: never;
|
|
76
139
|
/** Placeholder to signal maxChunks is intentionally absent */
|
|
77
140
|
readonly maxChunks?: never;
|
|
78
141
|
};
|
|
142
|
+
/**
|
|
143
|
+
* Accepted option shapes for `dividerLoop`.
|
|
144
|
+
*/
|
|
79
145
|
type DividerLoopOptionsLike = DividerLoopOptions | DividerLoopEmptyOptions;
|
|
80
|
-
type NumericSeparator = number;
|
|
81
|
-
type StringSeparator = string;
|
|
82
|
-
type DividerSeparator = NumericSeparator | StringSeparator;
|
|
83
|
-
type DividerSeparators = readonly DividerSeparator[];
|
|
84
|
-
type DividerArg = DividerSeparator | DividerOptions;
|
|
85
|
-
type DividerArgs = readonly DividerArg[];
|
|
86
146
|
|
|
87
147
|
/**
|
|
88
148
|
* Main divider function that splits input based on numeric positions or string delimiters.
|
|
@@ -96,7 +156,7 @@ type DividerArgs = readonly DividerArg[];
|
|
|
96
156
|
* @param args - Array of separators (numbers/strings) and optional options object
|
|
97
157
|
* @returns Divided string segments based on input type and options
|
|
98
158
|
*/
|
|
99
|
-
declare function divider<T extends DividerInput, const TArgs extends
|
|
159
|
+
declare function divider<T extends DividerInput, const TArgs extends DividerArgs>(input: T, ...args: TArgs): DividerReturn<T, TArgs>;
|
|
100
160
|
|
|
101
161
|
/**
|
|
102
162
|
* Extracts the first segment after dividing the input using specified separators.
|
|
@@ -160,22 +220,22 @@ declare function dividerNumberString<T extends DividerInput, O extends DividerIn
|
|
|
160
220
|
|
|
161
221
|
type EmailDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
162
222
|
/** Split top-level domain from the rest of the email address. */
|
|
163
|
-
splitTLD?: boolean;
|
|
223
|
+
readonly splitTLD?: boolean;
|
|
164
224
|
};
|
|
165
225
|
type CsvDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
166
|
-
/**
|
|
167
|
-
quoteChar?: string;
|
|
168
|
-
/**
|
|
169
|
-
delimiter?: string;
|
|
226
|
+
/** Quote string used for quoting values. */
|
|
227
|
+
readonly quoteChar?: string;
|
|
228
|
+
/** Delimiter string used to separate CSV fields. */
|
|
229
|
+
readonly delimiter?: string;
|
|
170
230
|
};
|
|
171
231
|
type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
172
232
|
/** Collapse empty segments produced by leading/trailing or repeated separators. */
|
|
173
|
-
collapse?: boolean;
|
|
233
|
+
readonly collapse?: boolean;
|
|
174
234
|
};
|
|
175
235
|
type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
|
|
176
236
|
type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
177
237
|
/** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
|
|
178
|
-
mode?: QueryDecodeMode;
|
|
238
|
+
readonly mode?: QueryDecodeMode;
|
|
179
239
|
};
|
|
180
240
|
|
|
181
241
|
/**
|
|
@@ -183,8 +243,8 @@ type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
|
183
243
|
*
|
|
184
244
|
* @param line - The CSV line string to be divided into fields
|
|
185
245
|
* @param options - Configuration options for CSV parsing
|
|
186
|
-
* @param options.delimiter - The
|
|
187
|
-
* @param options.quoteChar - The
|
|
246
|
+
* @param options.delimiter - The delimiter string used to separate fields (default: ',')
|
|
247
|
+
* @param options.quoteChar - The quote string used to wrap fields containing delimiters or newlines (default: '"')
|
|
188
248
|
* @param options.trim - Whether to trim whitespace from field values (default: false)
|
|
189
249
|
* @returns A DividerStringResult containing the parsed CSV fields
|
|
190
250
|
*/
|
package/dist/index.js
CHANGED
|
@@ -88,7 +88,10 @@ function isNoneMode(value) {
|
|
|
88
88
|
function normalizeSeparators(separators) {
|
|
89
89
|
return Array.from(new Set(separators)).filter(
|
|
90
90
|
(separator) => !isEmptyString(separator)
|
|
91
|
-
);
|
|
91
|
+
).sort((left, right) => right.length - left.length);
|
|
92
|
+
}
|
|
93
|
+
function createCacheKey(normalizedSeparators) {
|
|
94
|
+
return normalizedSeparators.join(CACHE_KEY_SEPARATOR);
|
|
92
95
|
}
|
|
93
96
|
var RegexCache = class {
|
|
94
97
|
cache = /* @__PURE__ */ new Map();
|
|
@@ -104,6 +107,15 @@ var RegexCache = class {
|
|
|
104
107
|
*/
|
|
105
108
|
get(separators) {
|
|
106
109
|
const key = this.createKey(separators);
|
|
110
|
+
return this.getByKey(key);
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Retrieves a cached RegExp for a precomputed cache key.
|
|
114
|
+
*
|
|
115
|
+
* @param key - Cache key string
|
|
116
|
+
* @returns Cached RegExp or null if not found
|
|
117
|
+
*/
|
|
118
|
+
getByKey(key) {
|
|
107
119
|
const regex = this.cache.get(key);
|
|
108
120
|
if (regex) {
|
|
109
121
|
this.cache.delete(key);
|
|
@@ -119,6 +131,15 @@ var RegexCache = class {
|
|
|
119
131
|
*/
|
|
120
132
|
set(separators, regex) {
|
|
121
133
|
const key = this.createKey(separators);
|
|
134
|
+
this.setByKey(key, regex);
|
|
135
|
+
}
|
|
136
|
+
/**
|
|
137
|
+
* Stores a RegExp in the cache for a precomputed cache key.
|
|
138
|
+
*
|
|
139
|
+
* @param key - Cache key string
|
|
140
|
+
* @param regex - Compiled RegExp to cache
|
|
141
|
+
*/
|
|
142
|
+
setByKey(key, regex) {
|
|
122
143
|
if (this.cache.has(key)) {
|
|
123
144
|
this.cache.delete(key);
|
|
124
145
|
}
|
|
@@ -139,7 +160,7 @@ var RegexCache = class {
|
|
|
139
160
|
*/
|
|
140
161
|
createKey(separators) {
|
|
141
162
|
const normalizedSeparators = normalizeSeparators(separators);
|
|
142
|
-
return normalizedSeparators
|
|
163
|
+
return createCacheKey(normalizedSeparators);
|
|
143
164
|
}
|
|
144
165
|
/**
|
|
145
166
|
* Gets current cache size for debugging/monitoring.
|
|
@@ -160,15 +181,16 @@ var RegexCache = class {
|
|
|
160
181
|
var regexCache = new RegexCache();
|
|
161
182
|
function getRegex(separators) {
|
|
162
183
|
if (isEmptyArray(separators)) return null;
|
|
163
|
-
const cached = regexCache.get(separators);
|
|
164
|
-
if (cached) return cached;
|
|
165
184
|
const uniqueSeparators = normalizeSeparators(separators);
|
|
166
185
|
if (uniqueSeparators.length === 0) {
|
|
167
186
|
return null;
|
|
168
187
|
}
|
|
188
|
+
const cacheKey = createCacheKey(uniqueSeparators);
|
|
189
|
+
const cached = regexCache.getByKey(cacheKey);
|
|
190
|
+
if (cached) return cached;
|
|
169
191
|
const pattern = uniqueSeparators.map(escapeRegExp).join("|");
|
|
170
192
|
const regex = new RegExp(`(?:${pattern})`, "g");
|
|
171
|
-
regexCache.
|
|
193
|
+
regexCache.setByKey(cacheKey, regex);
|
|
172
194
|
return regex;
|
|
173
195
|
}
|
|
174
196
|
function escapeRegExp(str) {
|
|
@@ -292,6 +314,13 @@ function classifySeparators(args) {
|
|
|
292
314
|
);
|
|
293
315
|
}
|
|
294
316
|
|
|
317
|
+
// src/utils/transform-divider-input.ts
|
|
318
|
+
function transformDividerInput(input, transform, options) {
|
|
319
|
+
const result = isString(input) ? transform(input) : input.map(transform);
|
|
320
|
+
const resolvedOptions = options ?? {};
|
|
321
|
+
return applyDividerOptions(result, resolvedOptions);
|
|
322
|
+
}
|
|
323
|
+
|
|
295
324
|
// src/core/divider.ts
|
|
296
325
|
function divider(input, ...args) {
|
|
297
326
|
if (!isValidInput(input)) {
|
|
@@ -308,8 +337,7 @@ function divider(input, ...args) {
|
|
|
308
337
|
const applyDivision = (str) => divideString(str, numSeparators, strSeparators, {
|
|
309
338
|
preserveEmpty: options.preserveEmpty
|
|
310
339
|
});
|
|
311
|
-
|
|
312
|
-
return applyDividerOptions(result, options);
|
|
340
|
+
return transformDividerInput(input, applyDivision, options);
|
|
313
341
|
}
|
|
314
342
|
|
|
315
343
|
// src/core/divider-first.ts
|
|
@@ -360,15 +388,15 @@ function dividerLoop(input, size, options) {
|
|
|
360
388
|
console.warn("dividerLoop: chunk size must be a positive number");
|
|
361
389
|
return [];
|
|
362
390
|
}
|
|
363
|
-
const resolvedOptions = options ?? {};
|
|
364
391
|
const {
|
|
365
392
|
startOffset = PERFORMANCE_CONSTANTS.DEFAULT_START_OFFSET,
|
|
366
393
|
maxChunks = PERFORMANCE_CONSTANTS.DEFAULT_MAX_CHUNKS
|
|
367
|
-
} =
|
|
368
|
-
|
|
369
|
-
|
|
394
|
+
} = options ?? {};
|
|
395
|
+
return transformDividerInput(
|
|
396
|
+
input,
|
|
397
|
+
(str) => createChunksFromString(str, size, startOffset, maxChunks),
|
|
398
|
+
options
|
|
370
399
|
);
|
|
371
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
372
400
|
}
|
|
373
401
|
|
|
374
402
|
// src/utils/divide.ts
|
|
@@ -378,40 +406,54 @@ function divideNumberString(str) {
|
|
|
378
406
|
|
|
379
407
|
// src/core/divider-number-string.ts
|
|
380
408
|
function dividerNumberString(input, options) {
|
|
381
|
-
|
|
382
|
-
const resolvedOptions = options ?? {};
|
|
383
|
-
return applyDividerOptions(result, resolvedOptions);
|
|
409
|
+
return transformDividerInput(input, divideNumberString, options);
|
|
384
410
|
}
|
|
385
411
|
|
|
386
|
-
// src/utils/
|
|
387
|
-
|
|
388
|
-
if (isEmptyString(input)) return [""];
|
|
389
|
-
return divider(input, separator, { preserveEmpty: true });
|
|
390
|
-
}
|
|
391
|
-
function countUnescaped(text, quote) {
|
|
392
|
-
const pair = quote + quote;
|
|
412
|
+
// src/utils/count-unescaped.ts
|
|
413
|
+
var countUnescapedSingleChar = (text, quote) => {
|
|
393
414
|
let count = 0;
|
|
394
|
-
for (
|
|
395
|
-
|
|
415
|
+
for (let index = 0; index < text.length; index++) {
|
|
416
|
+
if (text[index] !== quote) continue;
|
|
417
|
+
if (text[index + 1] === quote) {
|
|
418
|
+
index++;
|
|
419
|
+
continue;
|
|
420
|
+
}
|
|
421
|
+
count++;
|
|
396
422
|
}
|
|
397
423
|
return count;
|
|
424
|
+
};
|
|
425
|
+
var countUnescapedMultiChar = (text, quote) => {
|
|
426
|
+
const escapedPair = quote + quote;
|
|
427
|
+
const quoteLength = quote.length;
|
|
428
|
+
const escapedPairLength = escapedPair.length;
|
|
429
|
+
let count = 0;
|
|
430
|
+
for (let index = 0; index < text.length; ) {
|
|
431
|
+
if (text.startsWith(escapedPair, index)) {
|
|
432
|
+
index += escapedPairLength;
|
|
433
|
+
continue;
|
|
434
|
+
}
|
|
435
|
+
if (text.startsWith(quote, index)) {
|
|
436
|
+
count++;
|
|
437
|
+
index += quoteLength;
|
|
438
|
+
continue;
|
|
439
|
+
}
|
|
440
|
+
index++;
|
|
441
|
+
}
|
|
442
|
+
return count;
|
|
443
|
+
};
|
|
444
|
+
function countUnescaped(text, quote) {
|
|
445
|
+
if (isEmptyString(quote)) return 0;
|
|
446
|
+
if (quote.length === 1) return countUnescapedSingleChar(text, quote);
|
|
447
|
+
return countUnescapedMultiChar(text, quote);
|
|
398
448
|
}
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
return restoreEscapedQuotes(stripped);
|
|
405
|
-
}
|
|
406
|
-
function quotedDivide(line, {
|
|
407
|
-
delimiter = ",",
|
|
408
|
-
quote = '"',
|
|
409
|
-
trim = false,
|
|
410
|
-
lenient = true
|
|
411
|
-
} = {}) {
|
|
412
|
-
if (isEmptyString(line)) return [""];
|
|
413
|
-
return buildQuotedFields(line, delimiter, quote, trim, lenient);
|
|
449
|
+
|
|
450
|
+
// src/utils/divide-preserve.ts
|
|
451
|
+
function dividePreserve(input, separator) {
|
|
452
|
+
if (isEmptyString(input)) return [""];
|
|
453
|
+
return divider(input, separator, { preserveEmpty: true });
|
|
414
454
|
}
|
|
455
|
+
|
|
456
|
+
// src/utils/strip-outer-quotes.ts
|
|
415
457
|
var findNonSpaceBounds = (text, isWhitespace) => {
|
|
416
458
|
let left = 0;
|
|
417
459
|
let right = text.length - 1;
|
|
@@ -419,34 +461,70 @@ var findNonSpaceBounds = (text, isWhitespace) => {
|
|
|
419
461
|
while (right >= left && isWhitespace(text[right])) right--;
|
|
420
462
|
return { left, right };
|
|
421
463
|
};
|
|
422
|
-
var stripMatchedOuterQuotes = (text, left,
|
|
423
|
-
var
|
|
424
|
-
var stripTrailingQuote = (text,
|
|
464
|
+
var stripMatchedOuterQuotes = (text, left, rightQuoteStart, quote) => text.slice(0, left) + text.slice(left + quote.length, rightQuoteStart) + text.slice(rightQuoteStart + quote.length);
|
|
465
|
+
var removeQuoteAt = (text, start, quote) => text.slice(0, start) + text.slice(start + quote.length);
|
|
466
|
+
var stripTrailingQuote = (text, quote, isWhitespace) => {
|
|
467
|
+
const quoteLength = quote.length;
|
|
425
468
|
let lastNonSpaceIndex = text.length - 1;
|
|
426
469
|
while (lastNonSpaceIndex >= 0 && isWhitespace(text[lastNonSpaceIndex])) {
|
|
427
470
|
lastNonSpaceIndex--;
|
|
428
471
|
}
|
|
429
|
-
|
|
472
|
+
const trailingQuoteStart = lastNonSpaceIndex - quoteLength + 1;
|
|
473
|
+
if (trailingQuoteStart < 0 || !text.startsWith(quote, trailingQuoteStart)) {
|
|
430
474
|
return text;
|
|
431
475
|
}
|
|
432
|
-
return
|
|
476
|
+
return removeQuoteAt(text, trailingQuoteStart, quote);
|
|
433
477
|
};
|
|
434
|
-
var stripOuterQuotesRaw = (text,
|
|
478
|
+
var stripOuterQuotesRaw = (text, quote, lenient, isWhitespace) => {
|
|
479
|
+
if (quote.length === 0) return text;
|
|
435
480
|
const { left, right } = findNonSpaceBounds(text, isWhitespace);
|
|
436
481
|
if (left > right) return text;
|
|
437
|
-
if (text
|
|
438
|
-
|
|
439
|
-
|
|
482
|
+
if (!text.startsWith(quote, left)) return text;
|
|
483
|
+
const rightQuoteStart = right - quote.length + 1;
|
|
484
|
+
if (rightQuoteStart >= left + quote.length && text.startsWith(quote, rightQuoteStart)) {
|
|
485
|
+
return stripMatchedOuterQuotes(text, left, rightQuoteStart, quote);
|
|
440
486
|
}
|
|
441
487
|
if (!lenient) return text;
|
|
442
|
-
const withoutLeading =
|
|
443
|
-
return stripTrailingQuote(withoutLeading,
|
|
488
|
+
const withoutLeading = removeQuoteAt(text, left, quote);
|
|
489
|
+
return stripTrailingQuote(withoutLeading, quote, isWhitespace);
|
|
490
|
+
};
|
|
491
|
+
function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
|
|
492
|
+
const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
|
|
493
|
+
const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
|
|
494
|
+
if (quoteChar.length === 0) return stripped;
|
|
495
|
+
const escapedPair = quoteChar + quoteChar;
|
|
496
|
+
return stripped.split(escapedPair).join(quoteChar);
|
|
497
|
+
}
|
|
498
|
+
|
|
499
|
+
// src/utils/quoted-parser.ts
|
|
500
|
+
var advanceQuoteState = (insideQuotes, segment, quote) => {
|
|
501
|
+
let nextInsideQuotes = insideQuotes;
|
|
502
|
+
for (const char of segment) {
|
|
503
|
+
if (char === quote) nextInsideQuotes = !nextInsideQuotes;
|
|
504
|
+
}
|
|
505
|
+
return nextInsideQuotes;
|
|
506
|
+
};
|
|
507
|
+
var flushField = (state, quote, trim, lenient) => {
|
|
508
|
+
let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
|
|
509
|
+
if (trim) fieldValue = fieldValue.trim();
|
|
510
|
+
state.fields.push(fieldValue);
|
|
511
|
+
state.current = "";
|
|
512
|
+
state.insideQuotes = false;
|
|
513
|
+
};
|
|
514
|
+
var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
|
|
515
|
+
const segment = isEmptyString(state.current) ? piece : delimiter + piece;
|
|
516
|
+
state.current += segment;
|
|
517
|
+
state.insideQuotes = quote.length === 1 ? advanceQuoteState(state.insideQuotes, segment, quote) : countUnescaped(state.current, quote) % 2 === 1;
|
|
518
|
+
if (!state.insideQuotes) {
|
|
519
|
+
flushField(state, quote, trim, lenient);
|
|
520
|
+
}
|
|
444
521
|
};
|
|
445
522
|
var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
446
523
|
const pieces = dividePreserve(line, delimiter);
|
|
447
524
|
const state = {
|
|
448
525
|
fields: [],
|
|
449
|
-
current: ""
|
|
526
|
+
current: "",
|
|
527
|
+
insideQuotes: false
|
|
450
528
|
};
|
|
451
529
|
for (const piece of pieces) {
|
|
452
530
|
appendPiece(state, piece, delimiter, quote, trim, lenient);
|
|
@@ -456,19 +534,15 @@ var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
|
456
534
|
}
|
|
457
535
|
return state.fields;
|
|
458
536
|
};
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
if (trim) fieldValue = fieldValue.trim();
|
|
469
|
-
state.fields.push(fieldValue);
|
|
470
|
-
state.current = "";
|
|
471
|
-
};
|
|
537
|
+
function quotedDivide(line, {
|
|
538
|
+
delimiter = ",",
|
|
539
|
+
quote = '"',
|
|
540
|
+
trim = false,
|
|
541
|
+
lenient = true
|
|
542
|
+
} = {}) {
|
|
543
|
+
if (isEmptyString(line)) return [""];
|
|
544
|
+
return buildQuotedFields(line, delimiter, quote, trim, lenient);
|
|
545
|
+
}
|
|
472
546
|
|
|
473
547
|
// src/presets/csv-divider.ts
|
|
474
548
|
function csvDivider(line, options = {}) {
|
|
@@ -519,8 +593,27 @@ function tryExtractQuery(input) {
|
|
|
519
593
|
const url = new URL(input);
|
|
520
594
|
return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
|
|
521
595
|
} catch {
|
|
522
|
-
return input;
|
|
596
|
+
return extractQueryFromQuestionMark(input);
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function extractQueryFromQuestionMark(input) {
|
|
600
|
+
const fragmentIndex = input.indexOf("#");
|
|
601
|
+
const withoutFragment = fragmentIndex >= 0 ? input.slice(0, fragmentIndex) : input;
|
|
602
|
+
const questionMarkIndex = withoutFragment.indexOf(
|
|
603
|
+
QUERY_SEPARATORS.QUESTION_MARK
|
|
604
|
+
);
|
|
605
|
+
if (questionMarkIndex < 0) {
|
|
606
|
+
return withoutFragment;
|
|
607
|
+
}
|
|
608
|
+
if (questionMarkIndex === 0) {
|
|
609
|
+
return withoutFragment.slice(1);
|
|
610
|
+
}
|
|
611
|
+
const prefix = withoutFragment.slice(0, questionMarkIndex);
|
|
612
|
+
const hasQuerySeparatorBefore = prefix.includes(QUERY_SEPARATORS.AMPERSAND) || prefix.includes(QUERY_SEPARATORS.EQUALS);
|
|
613
|
+
if (hasQuerySeparatorBefore) {
|
|
614
|
+
return withoutFragment;
|
|
523
615
|
}
|
|
616
|
+
return withoutFragment.slice(questionMarkIndex + 1);
|
|
524
617
|
}
|
|
525
618
|
function stripLeadingQuestionMark(query) {
|
|
526
619
|
return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
|