@nyaomaru/divider 1.9.19 → 1.9.21
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 +113 -73
- package/dist/index.d.cts +9 -2
- package/dist/index.d.ts +9 -2
- package/dist/index.js +113 -73
- package/package.json +4 -1
package/dist/index.cjs
CHANGED
|
@@ -61,6 +61,15 @@ var PATH_SEPARATORS = {
|
|
|
61
61
|
SLASH: "/",
|
|
62
62
|
ALT: "|"
|
|
63
63
|
};
|
|
64
|
+
var QUERY_SEPARATORS = {
|
|
65
|
+
QUESTION_MARK: "?",
|
|
66
|
+
AMPERSAND: "&",
|
|
67
|
+
EQUALS: "="
|
|
68
|
+
};
|
|
69
|
+
var QUERY_DECODE_MODES = {
|
|
70
|
+
AUTO: "auto",
|
|
71
|
+
RAW: "raw"
|
|
72
|
+
};
|
|
64
73
|
|
|
65
74
|
// src/utils/is.ts
|
|
66
75
|
function isString(value) {
|
|
@@ -224,12 +233,10 @@ function sortAscending(numbers) {
|
|
|
224
233
|
|
|
225
234
|
// src/utils/parser.ts
|
|
226
235
|
function divideString(input, numSeparators, strSeparators, options) {
|
|
227
|
-
if (
|
|
236
|
+
if (hasNoSeparators(numSeparators, strSeparators)) {
|
|
228
237
|
return [input];
|
|
229
238
|
}
|
|
230
|
-
|
|
231
|
-
throw new Error("Invalid numeric separators");
|
|
232
|
-
}
|
|
239
|
+
assertValidNumSeparators(numSeparators);
|
|
233
240
|
const regex = getRegex(strSeparators);
|
|
234
241
|
const shouldPreserveEmpty = options?.preserveEmpty === true;
|
|
235
242
|
const sortedNumSeparators = sortAscending(numSeparators);
|
|
@@ -237,6 +244,12 @@ function divideString(input, numSeparators, strSeparators, options) {
|
|
|
237
244
|
const segments = regex ? parts.flatMap((part) => part.split(regex)) : parts;
|
|
238
245
|
return shouldPreserveEmpty ? segments : segments.filter((segment) => !isEmptyString(segment));
|
|
239
246
|
}
|
|
247
|
+
var hasNoSeparators = (numSeparators, strSeparators) => isEmptyArray(numSeparators) && isEmptyArray(strSeparators);
|
|
248
|
+
var assertValidNumSeparators = (numSeparators) => {
|
|
249
|
+
if (!Array.isArray(numSeparators) || !numSeparators.every(isNumber)) {
|
|
250
|
+
throw new Error("Invalid numeric separators");
|
|
251
|
+
}
|
|
252
|
+
};
|
|
240
253
|
|
|
241
254
|
// src/utils/array.ts
|
|
242
255
|
function ensureStringArray(input) {
|
|
@@ -275,29 +288,32 @@ function trimNestedSegments(rows, preserveEmpty) {
|
|
|
275
288
|
return rows.map((row) => trimSegments(row, preserveEmpty));
|
|
276
289
|
}
|
|
277
290
|
function applyDividerOptions(result, options) {
|
|
278
|
-
let output = result;
|
|
279
291
|
const shouldPreserveEmpty = options.preserveEmpty === true;
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
output = output.flat();
|
|
285
|
-
}
|
|
286
|
-
if (!isNoneMode(options.exclude)) {
|
|
287
|
-
const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
|
|
288
|
-
let shouldKeep = () => true;
|
|
289
|
-
if (exclude in excludePredicateMap) {
|
|
290
|
-
shouldKeep = excludePredicateMap[exclude];
|
|
291
|
-
}
|
|
292
|
-
const filterNested = (arr) => {
|
|
293
|
-
const filteredRows = arr.map((row) => row.filter(shouldKeep));
|
|
294
|
-
return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
|
|
295
|
-
};
|
|
296
|
-
const filterFlat = (arr) => arr.filter(shouldKeep);
|
|
297
|
-
output = isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
|
|
298
|
-
}
|
|
292
|
+
let output = result;
|
|
293
|
+
output = applyTrimOption(output, options, shouldPreserveEmpty);
|
|
294
|
+
output = applyFlattenOption(output, options);
|
|
295
|
+
output = applyExcludeOption(output, options, shouldPreserveEmpty);
|
|
299
296
|
return output;
|
|
300
297
|
}
|
|
298
|
+
var applyTrimOption = (output, options, shouldPreserveEmpty) => {
|
|
299
|
+
if (!options.trim) return output;
|
|
300
|
+
return isNestedStringArray(output) ? trimNestedSegments(output, shouldPreserveEmpty) : trimSegments(output, shouldPreserveEmpty);
|
|
301
|
+
};
|
|
302
|
+
var applyFlattenOption = (output, options) => options.flatten ? output.flat() : output;
|
|
303
|
+
var applyExcludeOption = (output, options, shouldPreserveEmpty) => {
|
|
304
|
+
if (isNoneMode(options.exclude)) return output;
|
|
305
|
+
const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
|
|
306
|
+
let shouldKeep = () => true;
|
|
307
|
+
if (exclude in excludePredicateMap) {
|
|
308
|
+
shouldKeep = excludePredicateMap[exclude];
|
|
309
|
+
}
|
|
310
|
+
const filterNested = (arr) => {
|
|
311
|
+
const filteredRows = arr.map((row) => row.filter(shouldKeep));
|
|
312
|
+
return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
|
|
313
|
+
};
|
|
314
|
+
const filterFlat = (arr) => arr.filter(shouldKeep);
|
|
315
|
+
return isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
|
|
316
|
+
};
|
|
301
317
|
|
|
302
318
|
// src/utils/separator.ts
|
|
303
319
|
function classifySeparators(args) {
|
|
@@ -422,28 +438,8 @@ function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
|
|
|
422
438
|
const escapedPair = quoteChar + quoteChar;
|
|
423
439
|
const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
|
|
424
440
|
const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
while (left <= right && isWhitespace(text[left])) left++;
|
|
428
|
-
while (right >= left && isWhitespace(text[right])) right--;
|
|
429
|
-
if (left > right) return restoreEscapedQuotes(text);
|
|
430
|
-
const startsWithQuote = text[left] === quoteChar;
|
|
431
|
-
if (!startsWithQuote) return restoreEscapedQuotes(text);
|
|
432
|
-
const endsWithQuote = text[right] === quoteChar;
|
|
433
|
-
if (endsWithQuote && right > left) {
|
|
434
|
-
const withoutPair = text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
|
|
435
|
-
return restoreEscapedQuotes(withoutPair);
|
|
436
|
-
}
|
|
437
|
-
if (!lenient) return restoreEscapedQuotes(text);
|
|
438
|
-
let result = text.slice(0, left) + text.slice(left + 1);
|
|
439
|
-
let lastNonSpaceIndexAfterTrim = result.length - 1;
|
|
440
|
-
while (lastNonSpaceIndexAfterTrim >= 0 && isWhitespace(result[lastNonSpaceIndexAfterTrim])) {
|
|
441
|
-
lastNonSpaceIndexAfterTrim--;
|
|
442
|
-
}
|
|
443
|
-
if (lastNonSpaceIndexAfterTrim >= 0 && result[lastNonSpaceIndexAfterTrim] === quoteChar) {
|
|
444
|
-
result = result.slice(0, lastNonSpaceIndexAfterTrim) + result.slice(lastNonSpaceIndexAfterTrim + 1);
|
|
445
|
-
}
|
|
446
|
-
return restoreEscapedQuotes(result);
|
|
441
|
+
const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
|
|
442
|
+
return restoreEscapedQuotes(stripped);
|
|
447
443
|
}
|
|
448
444
|
function quotedDivide(line, {
|
|
449
445
|
delimiter = ",",
|
|
@@ -452,24 +448,65 @@ function quotedDivide(line, {
|
|
|
452
448
|
lenient = true
|
|
453
449
|
} = {}) {
|
|
454
450
|
if (isEmptyString(line)) return [""];
|
|
451
|
+
return buildQuotedFields(line, delimiter, quote, trim, lenient);
|
|
452
|
+
}
|
|
453
|
+
var findNonSpaceBounds = (text, isWhitespace) => {
|
|
454
|
+
let left = 0;
|
|
455
|
+
let right = text.length - 1;
|
|
456
|
+
while (left <= right && isWhitespace(text[left])) left++;
|
|
457
|
+
while (right >= left && isWhitespace(text[right])) right--;
|
|
458
|
+
return { left, right };
|
|
459
|
+
};
|
|
460
|
+
var stripMatchedOuterQuotes = (text, left, right) => text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
|
|
461
|
+
var removeCharAt = (text, index) => text.slice(0, index) + text.slice(index + 1);
|
|
462
|
+
var stripTrailingQuote = (text, quoteChar, isWhitespace) => {
|
|
463
|
+
let lastNonSpaceIndex = text.length - 1;
|
|
464
|
+
while (lastNonSpaceIndex >= 0 && isWhitespace(text[lastNonSpaceIndex])) {
|
|
465
|
+
lastNonSpaceIndex--;
|
|
466
|
+
}
|
|
467
|
+
if (lastNonSpaceIndex < 0 || text[lastNonSpaceIndex] !== quoteChar) {
|
|
468
|
+
return text;
|
|
469
|
+
}
|
|
470
|
+
return removeCharAt(text, lastNonSpaceIndex);
|
|
471
|
+
};
|
|
472
|
+
var stripOuterQuotesRaw = (text, quoteChar, lenient, isWhitespace) => {
|
|
473
|
+
const { left, right } = findNonSpaceBounds(text, isWhitespace);
|
|
474
|
+
if (left > right) return text;
|
|
475
|
+
if (text[left] !== quoteChar) return text;
|
|
476
|
+
if (text[right] === quoteChar && right > left) {
|
|
477
|
+
return stripMatchedOuterQuotes(text, left, right);
|
|
478
|
+
}
|
|
479
|
+
if (!lenient) return text;
|
|
480
|
+
const withoutLeading = removeCharAt(text, left);
|
|
481
|
+
return stripTrailingQuote(withoutLeading, quoteChar, isWhitespace);
|
|
482
|
+
};
|
|
483
|
+
var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
455
484
|
const pieces = dividePreserve(line, delimiter);
|
|
456
|
-
const
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
const flush = () => {
|
|
460
|
-
let fieldValue = stripOuterQuotes(currentFieldBuffer, quote, { lenient });
|
|
461
|
-
if (trim) fieldValue = fieldValue.trim();
|
|
462
|
-
fields.push(fieldValue);
|
|
463
|
-
currentFieldBuffer = "";
|
|
485
|
+
const state = {
|
|
486
|
+
fields: [],
|
|
487
|
+
current: ""
|
|
464
488
|
};
|
|
465
489
|
for (const piece of pieces) {
|
|
466
|
-
|
|
467
|
-
insideQuotes = countUnescaped(currentFieldBuffer, quote) % 2 === 1;
|
|
468
|
-
if (!insideQuotes) flush();
|
|
490
|
+
appendPiece(state, piece, delimiter, quote, trim, lenient);
|
|
469
491
|
}
|
|
470
|
-
if (!isEmptyString(
|
|
471
|
-
|
|
472
|
-
}
|
|
492
|
+
if (!isEmptyString(state.current)) {
|
|
493
|
+
flushField(state, quote, trim, lenient);
|
|
494
|
+
}
|
|
495
|
+
return state.fields;
|
|
496
|
+
};
|
|
497
|
+
var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
|
|
498
|
+
state.current = isEmptyString(state.current) ? piece : state.current + delimiter + piece;
|
|
499
|
+
const insideQuotes = countUnescaped(state.current, quote) % 2 === 1;
|
|
500
|
+
if (!insideQuotes) {
|
|
501
|
+
flushField(state, quote, trim, lenient);
|
|
502
|
+
}
|
|
503
|
+
};
|
|
504
|
+
var flushField = (state, quote, trim, lenient) => {
|
|
505
|
+
let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
|
|
506
|
+
if (trim) fieldValue = fieldValue.trim();
|
|
507
|
+
state.fields.push(fieldValue);
|
|
508
|
+
state.current = "";
|
|
509
|
+
};
|
|
473
510
|
|
|
474
511
|
// src/presets/csv-divider.ts
|
|
475
512
|
function csvDivider(line, options = {}) {
|
|
@@ -504,33 +541,36 @@ function emailDivider(input, options = {}) {
|
|
|
504
541
|
function pathDivider(input, options = {}) {
|
|
505
542
|
const { trim = false, collapse = true } = options;
|
|
506
543
|
if (isEmptyString(input)) return [""];
|
|
507
|
-
const segments =
|
|
508
|
-
|
|
509
|
-
);
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
544
|
+
const segments = buildPathSegments(input, collapse);
|
|
545
|
+
const maybeTrimmed = trimSegments2(segments, trim);
|
|
546
|
+
return applyCollapseRules(maybeTrimmed, collapse);
|
|
547
|
+
}
|
|
548
|
+
var buildPathSegments = (input, collapse) => collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
|
|
549
|
+
(part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
|
|
550
|
+
);
|
|
551
|
+
var trimSegments2 = (segments, trim) => trim ? segments.map((segment) => segment.trim()) : segments;
|
|
552
|
+
var applyCollapseRules = (segments, collapse) => collapse ? segments.filter((segment) => !isEmptyString(segment)) : segments;
|
|
513
553
|
|
|
514
554
|
// src/presets/query-divider.ts
|
|
515
555
|
function tryExtractQuery(input) {
|
|
516
556
|
try {
|
|
517
557
|
const url = new URL(input);
|
|
518
|
-
return url.search.startsWith(
|
|
558
|
+
return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
|
|
519
559
|
} catch {
|
|
520
560
|
return input;
|
|
521
561
|
}
|
|
522
562
|
}
|
|
523
563
|
function stripLeadingQuestionMark(query) {
|
|
524
|
-
return query.startsWith(
|
|
564
|
+
return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
|
|
525
565
|
}
|
|
526
566
|
function splitOnFirstEquals(part) {
|
|
527
|
-
const kv = dividePreserve(part,
|
|
567
|
+
const kv = dividePreserve(part, QUERY_SEPARATORS.EQUALS);
|
|
528
568
|
if (kv.length === 1) return [kv[0] ?? "", ""];
|
|
529
|
-
return [kv[0] ?? "", kv.slice(1).join(
|
|
569
|
+
return [kv[0] ?? "", kv.slice(1).join(QUERY_SEPARATORS.EQUALS)];
|
|
530
570
|
}
|
|
531
571
|
function decodeField(text, mode, trim) {
|
|
532
572
|
let t = text;
|
|
533
|
-
if (mode ===
|
|
573
|
+
if (mode === QUERY_DECODE_MODES.AUTO) {
|
|
534
574
|
t = t.replace(/\+/g, " ");
|
|
535
575
|
try {
|
|
536
576
|
t = decodeURIComponent(t);
|
|
@@ -540,11 +580,11 @@ function decodeField(text, mode, trim) {
|
|
|
540
580
|
if (trim) t = t.trim();
|
|
541
581
|
return t;
|
|
542
582
|
}
|
|
543
|
-
function queryDivider(input, { mode =
|
|
583
|
+
function queryDivider(input, { mode = QUERY_DECODE_MODES.AUTO, trim = false } = {}) {
|
|
544
584
|
if (input.length === 0) return [];
|
|
545
585
|
const query = stripLeadingQuestionMark(tryExtractQuery(input));
|
|
546
586
|
if (query.length === 0) return [];
|
|
547
|
-
return dividePreserve(query,
|
|
587
|
+
return dividePreserve(query, QUERY_SEPARATORS.AMPERSAND).map((part) => {
|
|
548
588
|
const [key, value] = splitOnFirstEquals(part);
|
|
549
589
|
return [decodeField(key, mode, trim), decodeField(value, mode, trim)];
|
|
550
590
|
});
|
package/dist/index.d.cts
CHANGED
|
@@ -13,6 +13,13 @@ declare const DIVIDER_EXCLUDE_MODES: {
|
|
|
13
13
|
readonly EMPTY: "empty";
|
|
14
14
|
readonly WHITESPACE: "whitespace";
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Query string decode modes.
|
|
18
|
+
*/
|
|
19
|
+
declare const QUERY_DECODE_MODES: {
|
|
20
|
+
readonly AUTO: "auto";
|
|
21
|
+
readonly RAW: "raw";
|
|
22
|
+
};
|
|
16
23
|
|
|
17
24
|
type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
|
|
18
25
|
type StringInput = string;
|
|
@@ -49,7 +56,7 @@ type HasFlattenOption<TOptions extends DividerInferredOptions> = TOptions extend
|
|
|
49
56
|
readonly flatten?: infer Flag;
|
|
50
57
|
} ? Flag extends true ? true : false : false;
|
|
51
58
|
type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : HasFlattenOption<TOptions> extends true ? DividerStringResult : DividerArrayResult;
|
|
52
|
-
type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer
|
|
59
|
+
type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer Rest, infer Last] ? Rest extends DividerArg[] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions : DividerEmptyOptions;
|
|
53
60
|
type DividerLoopOptions = DividerOptions & {
|
|
54
61
|
/** Starting position for the division (0-based) */
|
|
55
62
|
startOffset?: number;
|
|
@@ -158,7 +165,7 @@ type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
|
158
165
|
/** Collapse empty segments produced by leading/trailing or repeated separators. */
|
|
159
166
|
collapse?: boolean;
|
|
160
167
|
};
|
|
161
|
-
type QueryDecodeMode =
|
|
168
|
+
type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
|
|
162
169
|
type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
163
170
|
/** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
|
|
164
171
|
mode?: QueryDecodeMode;
|
package/dist/index.d.ts
CHANGED
|
@@ -13,6 +13,13 @@ declare const DIVIDER_EXCLUDE_MODES: {
|
|
|
13
13
|
readonly EMPTY: "empty";
|
|
14
14
|
readonly WHITESPACE: "whitespace";
|
|
15
15
|
};
|
|
16
|
+
/**
|
|
17
|
+
* Query string decode modes.
|
|
18
|
+
*/
|
|
19
|
+
declare const QUERY_DECODE_MODES: {
|
|
20
|
+
readonly AUTO: "auto";
|
|
21
|
+
readonly RAW: "raw";
|
|
22
|
+
};
|
|
16
23
|
|
|
17
24
|
type DividerExcludeMode = (typeof DIVIDER_EXCLUDE_MODES)[keyof typeof DIVIDER_EXCLUDE_MODES];
|
|
18
25
|
type StringInput = string;
|
|
@@ -49,7 +56,7 @@ type HasFlattenOption<TOptions extends DividerInferredOptions> = TOptions extend
|
|
|
49
56
|
readonly flatten?: infer Flag;
|
|
50
57
|
} ? Flag extends true ? true : false : false;
|
|
51
58
|
type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : HasFlattenOption<TOptions> extends true ? DividerStringResult : DividerArrayResult;
|
|
52
|
-
type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer
|
|
59
|
+
type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer Rest, infer Last] ? Rest extends DividerArg[] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions : DividerEmptyOptions;
|
|
53
60
|
type DividerLoopOptions = DividerOptions & {
|
|
54
61
|
/** Starting position for the division (0-based) */
|
|
55
62
|
startOffset?: number;
|
|
@@ -158,7 +165,7 @@ type PathDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
|
158
165
|
/** Collapse empty segments produced by leading/trailing or repeated separators. */
|
|
159
166
|
collapse?: boolean;
|
|
160
167
|
};
|
|
161
|
-
type QueryDecodeMode =
|
|
168
|
+
type QueryDecodeMode = (typeof QUERY_DECODE_MODES)[keyof typeof QUERY_DECODE_MODES];
|
|
162
169
|
type QueryDividerOptions = Pick<DividerOptions, 'trim'> & {
|
|
163
170
|
/** Decoding mode: 'auto' applies standard URL decoding; 'raw' leaves values untouched. */
|
|
164
171
|
mode?: QueryDecodeMode;
|
package/dist/index.js
CHANGED
|
@@ -27,6 +27,15 @@ var PATH_SEPARATORS = {
|
|
|
27
27
|
SLASH: "/",
|
|
28
28
|
ALT: "|"
|
|
29
29
|
};
|
|
30
|
+
var QUERY_SEPARATORS = {
|
|
31
|
+
QUESTION_MARK: "?",
|
|
32
|
+
AMPERSAND: "&",
|
|
33
|
+
EQUALS: "="
|
|
34
|
+
};
|
|
35
|
+
var QUERY_DECODE_MODES = {
|
|
36
|
+
AUTO: "auto",
|
|
37
|
+
RAW: "raw"
|
|
38
|
+
};
|
|
30
39
|
|
|
31
40
|
// src/utils/is.ts
|
|
32
41
|
function isString(value) {
|
|
@@ -190,12 +199,10 @@ function sortAscending(numbers) {
|
|
|
190
199
|
|
|
191
200
|
// src/utils/parser.ts
|
|
192
201
|
function divideString(input, numSeparators, strSeparators, options) {
|
|
193
|
-
if (
|
|
202
|
+
if (hasNoSeparators(numSeparators, strSeparators)) {
|
|
194
203
|
return [input];
|
|
195
204
|
}
|
|
196
|
-
|
|
197
|
-
throw new Error("Invalid numeric separators");
|
|
198
|
-
}
|
|
205
|
+
assertValidNumSeparators(numSeparators);
|
|
199
206
|
const regex = getRegex(strSeparators);
|
|
200
207
|
const shouldPreserveEmpty = options?.preserveEmpty === true;
|
|
201
208
|
const sortedNumSeparators = sortAscending(numSeparators);
|
|
@@ -203,6 +210,12 @@ function divideString(input, numSeparators, strSeparators, options) {
|
|
|
203
210
|
const segments = regex ? parts.flatMap((part) => part.split(regex)) : parts;
|
|
204
211
|
return shouldPreserveEmpty ? segments : segments.filter((segment) => !isEmptyString(segment));
|
|
205
212
|
}
|
|
213
|
+
var hasNoSeparators = (numSeparators, strSeparators) => isEmptyArray(numSeparators) && isEmptyArray(strSeparators);
|
|
214
|
+
var assertValidNumSeparators = (numSeparators) => {
|
|
215
|
+
if (!Array.isArray(numSeparators) || !numSeparators.every(isNumber)) {
|
|
216
|
+
throw new Error("Invalid numeric separators");
|
|
217
|
+
}
|
|
218
|
+
};
|
|
206
219
|
|
|
207
220
|
// src/utils/array.ts
|
|
208
221
|
function ensureStringArray(input) {
|
|
@@ -241,29 +254,32 @@ function trimNestedSegments(rows, preserveEmpty) {
|
|
|
241
254
|
return rows.map((row) => trimSegments(row, preserveEmpty));
|
|
242
255
|
}
|
|
243
256
|
function applyDividerOptions(result, options) {
|
|
244
|
-
let output = result;
|
|
245
257
|
const shouldPreserveEmpty = options.preserveEmpty === true;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
output = output.flat();
|
|
251
|
-
}
|
|
252
|
-
if (!isNoneMode(options.exclude)) {
|
|
253
|
-
const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
|
|
254
|
-
let shouldKeep = () => true;
|
|
255
|
-
if (exclude in excludePredicateMap) {
|
|
256
|
-
shouldKeep = excludePredicateMap[exclude];
|
|
257
|
-
}
|
|
258
|
-
const filterNested = (arr) => {
|
|
259
|
-
const filteredRows = arr.map((row) => row.filter(shouldKeep));
|
|
260
|
-
return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
|
|
261
|
-
};
|
|
262
|
-
const filterFlat = (arr) => arr.filter(shouldKeep);
|
|
263
|
-
output = isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
|
|
264
|
-
}
|
|
258
|
+
let output = result;
|
|
259
|
+
output = applyTrimOption(output, options, shouldPreserveEmpty);
|
|
260
|
+
output = applyFlattenOption(output, options);
|
|
261
|
+
output = applyExcludeOption(output, options, shouldPreserveEmpty);
|
|
265
262
|
return output;
|
|
266
263
|
}
|
|
264
|
+
var applyTrimOption = (output, options, shouldPreserveEmpty) => {
|
|
265
|
+
if (!options.trim) return output;
|
|
266
|
+
return isNestedStringArray(output) ? trimNestedSegments(output, shouldPreserveEmpty) : trimSegments(output, shouldPreserveEmpty);
|
|
267
|
+
};
|
|
268
|
+
var applyFlattenOption = (output, options) => options.flatten ? output.flat() : output;
|
|
269
|
+
var applyExcludeOption = (output, options, shouldPreserveEmpty) => {
|
|
270
|
+
if (isNoneMode(options.exclude)) return output;
|
|
271
|
+
const exclude = options.exclude ?? DIVIDER_EXCLUDE_MODES.NONE;
|
|
272
|
+
let shouldKeep = () => true;
|
|
273
|
+
if (exclude in excludePredicateMap) {
|
|
274
|
+
shouldKeep = excludePredicateMap[exclude];
|
|
275
|
+
}
|
|
276
|
+
const filterNested = (arr) => {
|
|
277
|
+
const filteredRows = arr.map((row) => row.filter(shouldKeep));
|
|
278
|
+
return shouldPreserveEmpty ? filteredRows : filteredRows.filter((row) => row.length > 0);
|
|
279
|
+
};
|
|
280
|
+
const filterFlat = (arr) => arr.filter(shouldKeep);
|
|
281
|
+
return isNestedStringArray(output) ? filterNested(output) : filterFlat(output);
|
|
282
|
+
};
|
|
267
283
|
|
|
268
284
|
// src/utils/separator.ts
|
|
269
285
|
function classifySeparators(args) {
|
|
@@ -388,28 +404,8 @@ function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
|
|
|
388
404
|
const escapedPair = quoteChar + quoteChar;
|
|
389
405
|
const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
|
|
390
406
|
const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
while (left <= right && isWhitespace(text[left])) left++;
|
|
394
|
-
while (right >= left && isWhitespace(text[right])) right--;
|
|
395
|
-
if (left > right) return restoreEscapedQuotes(text);
|
|
396
|
-
const startsWithQuote = text[left] === quoteChar;
|
|
397
|
-
if (!startsWithQuote) return restoreEscapedQuotes(text);
|
|
398
|
-
const endsWithQuote = text[right] === quoteChar;
|
|
399
|
-
if (endsWithQuote && right > left) {
|
|
400
|
-
const withoutPair = text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
|
|
401
|
-
return restoreEscapedQuotes(withoutPair);
|
|
402
|
-
}
|
|
403
|
-
if (!lenient) return restoreEscapedQuotes(text);
|
|
404
|
-
let result = text.slice(0, left) + text.slice(left + 1);
|
|
405
|
-
let lastNonSpaceIndexAfterTrim = result.length - 1;
|
|
406
|
-
while (lastNonSpaceIndexAfterTrim >= 0 && isWhitespace(result[lastNonSpaceIndexAfterTrim])) {
|
|
407
|
-
lastNonSpaceIndexAfterTrim--;
|
|
408
|
-
}
|
|
409
|
-
if (lastNonSpaceIndexAfterTrim >= 0 && result[lastNonSpaceIndexAfterTrim] === quoteChar) {
|
|
410
|
-
result = result.slice(0, lastNonSpaceIndexAfterTrim) + result.slice(lastNonSpaceIndexAfterTrim + 1);
|
|
411
|
-
}
|
|
412
|
-
return restoreEscapedQuotes(result);
|
|
407
|
+
const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
|
|
408
|
+
return restoreEscapedQuotes(stripped);
|
|
413
409
|
}
|
|
414
410
|
function quotedDivide(line, {
|
|
415
411
|
delimiter = ",",
|
|
@@ -418,24 +414,65 @@ function quotedDivide(line, {
|
|
|
418
414
|
lenient = true
|
|
419
415
|
} = {}) {
|
|
420
416
|
if (isEmptyString(line)) return [""];
|
|
417
|
+
return buildQuotedFields(line, delimiter, quote, trim, lenient);
|
|
418
|
+
}
|
|
419
|
+
var findNonSpaceBounds = (text, isWhitespace) => {
|
|
420
|
+
let left = 0;
|
|
421
|
+
let right = text.length - 1;
|
|
422
|
+
while (left <= right && isWhitespace(text[left])) left++;
|
|
423
|
+
while (right >= left && isWhitespace(text[right])) right--;
|
|
424
|
+
return { left, right };
|
|
425
|
+
};
|
|
426
|
+
var stripMatchedOuterQuotes = (text, left, right) => text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
|
|
427
|
+
var removeCharAt = (text, index) => text.slice(0, index) + text.slice(index + 1);
|
|
428
|
+
var stripTrailingQuote = (text, quoteChar, isWhitespace) => {
|
|
429
|
+
let lastNonSpaceIndex = text.length - 1;
|
|
430
|
+
while (lastNonSpaceIndex >= 0 && isWhitespace(text[lastNonSpaceIndex])) {
|
|
431
|
+
lastNonSpaceIndex--;
|
|
432
|
+
}
|
|
433
|
+
if (lastNonSpaceIndex < 0 || text[lastNonSpaceIndex] !== quoteChar) {
|
|
434
|
+
return text;
|
|
435
|
+
}
|
|
436
|
+
return removeCharAt(text, lastNonSpaceIndex);
|
|
437
|
+
};
|
|
438
|
+
var stripOuterQuotesRaw = (text, quoteChar, lenient, isWhitespace) => {
|
|
439
|
+
const { left, right } = findNonSpaceBounds(text, isWhitespace);
|
|
440
|
+
if (left > right) return text;
|
|
441
|
+
if (text[left] !== quoteChar) return text;
|
|
442
|
+
if (text[right] === quoteChar && right > left) {
|
|
443
|
+
return stripMatchedOuterQuotes(text, left, right);
|
|
444
|
+
}
|
|
445
|
+
if (!lenient) return text;
|
|
446
|
+
const withoutLeading = removeCharAt(text, left);
|
|
447
|
+
return stripTrailingQuote(withoutLeading, quoteChar, isWhitespace);
|
|
448
|
+
};
|
|
449
|
+
var buildQuotedFields = (line, delimiter, quote, trim, lenient) => {
|
|
421
450
|
const pieces = dividePreserve(line, delimiter);
|
|
422
|
-
const
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
const flush = () => {
|
|
426
|
-
let fieldValue = stripOuterQuotes(currentFieldBuffer, quote, { lenient });
|
|
427
|
-
if (trim) fieldValue = fieldValue.trim();
|
|
428
|
-
fields.push(fieldValue);
|
|
429
|
-
currentFieldBuffer = "";
|
|
451
|
+
const state = {
|
|
452
|
+
fields: [],
|
|
453
|
+
current: ""
|
|
430
454
|
};
|
|
431
455
|
for (const piece of pieces) {
|
|
432
|
-
|
|
433
|
-
insideQuotes = countUnescaped(currentFieldBuffer, quote) % 2 === 1;
|
|
434
|
-
if (!insideQuotes) flush();
|
|
456
|
+
appendPiece(state, piece, delimiter, quote, trim, lenient);
|
|
435
457
|
}
|
|
436
|
-
if (!isEmptyString(
|
|
437
|
-
|
|
438
|
-
}
|
|
458
|
+
if (!isEmptyString(state.current)) {
|
|
459
|
+
flushField(state, quote, trim, lenient);
|
|
460
|
+
}
|
|
461
|
+
return state.fields;
|
|
462
|
+
};
|
|
463
|
+
var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
|
|
464
|
+
state.current = isEmptyString(state.current) ? piece : state.current + delimiter + piece;
|
|
465
|
+
const insideQuotes = countUnescaped(state.current, quote) % 2 === 1;
|
|
466
|
+
if (!insideQuotes) {
|
|
467
|
+
flushField(state, quote, trim, lenient);
|
|
468
|
+
}
|
|
469
|
+
};
|
|
470
|
+
var flushField = (state, quote, trim, lenient) => {
|
|
471
|
+
let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
|
|
472
|
+
if (trim) fieldValue = fieldValue.trim();
|
|
473
|
+
state.fields.push(fieldValue);
|
|
474
|
+
state.current = "";
|
|
475
|
+
};
|
|
439
476
|
|
|
440
477
|
// src/presets/csv-divider.ts
|
|
441
478
|
function csvDivider(line, options = {}) {
|
|
@@ -470,33 +507,36 @@ function emailDivider(input, options = {}) {
|
|
|
470
507
|
function pathDivider(input, options = {}) {
|
|
471
508
|
const { trim = false, collapse = true } = options;
|
|
472
509
|
if (isEmptyString(input)) return [""];
|
|
473
|
-
const segments =
|
|
474
|
-
|
|
475
|
-
);
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
510
|
+
const segments = buildPathSegments(input, collapse);
|
|
511
|
+
const maybeTrimmed = trimSegments2(segments, trim);
|
|
512
|
+
return applyCollapseRules(maybeTrimmed, collapse);
|
|
513
|
+
}
|
|
514
|
+
var buildPathSegments = (input, collapse) => collapse ? divider(input, PATH_SEPARATORS.SLASH, PATH_SEPARATORS.ALT) : dividePreserve(input, PATH_SEPARATORS.ALT).flatMap(
|
|
515
|
+
(part) => dividePreserve(part, PATH_SEPARATORS.SLASH)
|
|
516
|
+
);
|
|
517
|
+
var trimSegments2 = (segments, trim) => trim ? segments.map((segment) => segment.trim()) : segments;
|
|
518
|
+
var applyCollapseRules = (segments, collapse) => collapse ? segments.filter((segment) => !isEmptyString(segment)) : segments;
|
|
479
519
|
|
|
480
520
|
// src/presets/query-divider.ts
|
|
481
521
|
function tryExtractQuery(input) {
|
|
482
522
|
try {
|
|
483
523
|
const url = new URL(input);
|
|
484
|
-
return url.search.startsWith(
|
|
524
|
+
return url.search.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? url.search.slice(1) : url.search;
|
|
485
525
|
} catch {
|
|
486
526
|
return input;
|
|
487
527
|
}
|
|
488
528
|
}
|
|
489
529
|
function stripLeadingQuestionMark(query) {
|
|
490
|
-
return query.startsWith(
|
|
530
|
+
return query.startsWith(QUERY_SEPARATORS.QUESTION_MARK) ? query.slice(1) : query;
|
|
491
531
|
}
|
|
492
532
|
function splitOnFirstEquals(part) {
|
|
493
|
-
const kv = dividePreserve(part,
|
|
533
|
+
const kv = dividePreserve(part, QUERY_SEPARATORS.EQUALS);
|
|
494
534
|
if (kv.length === 1) return [kv[0] ?? "", ""];
|
|
495
|
-
return [kv[0] ?? "", kv.slice(1).join(
|
|
535
|
+
return [kv[0] ?? "", kv.slice(1).join(QUERY_SEPARATORS.EQUALS)];
|
|
496
536
|
}
|
|
497
537
|
function decodeField(text, mode, trim) {
|
|
498
538
|
let t = text;
|
|
499
|
-
if (mode ===
|
|
539
|
+
if (mode === QUERY_DECODE_MODES.AUTO) {
|
|
500
540
|
t = t.replace(/\+/g, " ");
|
|
501
541
|
try {
|
|
502
542
|
t = decodeURIComponent(t);
|
|
@@ -506,11 +546,11 @@ function decodeField(text, mode, trim) {
|
|
|
506
546
|
if (trim) t = t.trim();
|
|
507
547
|
return t;
|
|
508
548
|
}
|
|
509
|
-
function queryDivider(input, { mode =
|
|
549
|
+
function queryDivider(input, { mode = QUERY_DECODE_MODES.AUTO, trim = false } = {}) {
|
|
510
550
|
if (input.length === 0) return [];
|
|
511
551
|
const query = stripLeadingQuestionMark(tryExtractQuery(input));
|
|
512
552
|
if (query.length === 0) return [];
|
|
513
|
-
return dividePreserve(query,
|
|
553
|
+
return dividePreserve(query, QUERY_SEPARATORS.AMPERSAND).map((part) => {
|
|
514
554
|
const [key, value] = splitOnFirstEquals(part);
|
|
515
555
|
return [decodeField(key, mode, trim), decodeField(value, mode, trim)];
|
|
516
556
|
});
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@nyaomaru/divider",
|
|
3
3
|
"type": "module",
|
|
4
|
-
"version": "1.9.
|
|
4
|
+
"version": "1.9.21",
|
|
5
5
|
"description": "To divide string or string[] with a given separator",
|
|
6
6
|
"main": "./dist/index.cjs",
|
|
7
7
|
"module": "./dist/index.js",
|
|
@@ -38,11 +38,14 @@
|
|
|
38
38
|
"@eslint/js": "^9.26.0",
|
|
39
39
|
"@types/jest": "^30.0.0",
|
|
40
40
|
"@types/node": "^22.15.12",
|
|
41
|
+
"@typescript-eslint/eslint-plugin": "^8.32.1",
|
|
42
|
+
"@typescript-eslint/parser": "^8.32.1",
|
|
41
43
|
"bun-types": "^1.2.21",
|
|
42
44
|
"eslint": "^9.39.1",
|
|
43
45
|
"eslint-config-prettier": "^10.1.8",
|
|
44
46
|
"eslint-plugin-prettier": "^5.5.4",
|
|
45
47
|
"jest": "^30.2.0",
|
|
48
|
+
"lefthook": "^2.0.13",
|
|
46
49
|
"prettier": "^3.5.3",
|
|
47
50
|
"ts-jest": "^29.4.5",
|
|
48
51
|
"ts-node": "^10.9.2",
|