@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 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.join(CACHE_KEY_SEPARATOR);
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.set(separators, regex);
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
- const result = isString(input) ? applyDivision(input) : input.map(applyDivision);
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
- } = resolvedOptions;
402
- const result = isString(input) ? createChunksFromString(input, size, startOffset, maxChunks) : input.map(
403
- (str) => createChunksFromString(str, size, startOffset, maxChunks)
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
- const result = isString(input) ? divideNumberString(input) : input.map(divideNumberString);
416
- const resolvedOptions = options ?? {};
417
- return applyDividerOptions(result, resolvedOptions);
443
+ return transformDividerInput(input, divideNumberString, options);
418
444
  }
419
445
 
420
- // src/utils/quoted.ts
421
- function dividePreserve(input, separator) {
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 (const chunk of dividePreserve(text, pair)) {
429
- count += dividePreserve(chunk, quote).length - 1;
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
- function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
434
- const escapedPair = quoteChar + quoteChar;
435
- const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
436
- const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
437
- const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
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, right) => text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
457
- var removeCharAt = (text, index) => text.slice(0, index) + text.slice(index + 1);
458
- var stripTrailingQuote = (text, quoteChar, isWhitespace) => {
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
- if (lastNonSpaceIndex < 0 || text[lastNonSpaceIndex] !== quoteChar) {
506
+ const trailingQuoteStart = lastNonSpaceIndex - quoteLength + 1;
507
+ if (trailingQuoteStart < 0 || !text.startsWith(quote, trailingQuoteStart)) {
464
508
  return text;
465
509
  }
466
- return removeCharAt(text, lastNonSpaceIndex);
510
+ return removeQuoteAt(text, trailingQuoteStart, quote);
467
511
  };
468
- var stripOuterQuotesRaw = (text, quoteChar, lenient, isWhitespace) => {
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[left] !== quoteChar) return text;
472
- if (text[right] === quoteChar && right > left) {
473
- return stripMatchedOuterQuotes(text, left, right);
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 = removeCharAt(text, left);
477
- return stripTrailingQuote(withoutLeading, quoteChar, isWhitespace);
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
- var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
494
- state.current = isEmptyString(state.current) ? piece : state.current + delimiter + piece;
495
- const insideQuotes = countUnescaped(state.current, quote) % 2 === 1;
496
- if (!insideQuotes) {
497
- flushField(state, quote, trim, lenient);
498
- }
499
- };
500
- var flushField = (state, quote, trim, lenient) => {
501
- let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
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
- type HasFlattenOption<TOptions extends DividerInferredOptions> = TOptions extends {
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
- type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : HasFlattenOption<TOptions> extends true ? DividerStringResult : DividerArrayResult;
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 : HasFlattenOption<ExtractedDividerOptions<TArgs>> extends true ? DividerStringResult : DividerArrayResult;
66
- type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer Rest, infer Last] ? Rest extends DividerArg[] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions : DividerEmptyOptions;
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 readonly DividerArg[]>(input: T, ...args: TArgs): DividerReturn<T, TArgs>;
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
- /** Character used for quoting values. */
167
- quoteChar?: string;
168
- /** Character used to separate CSV fields. */
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 character used to separate fields (default: ',')
187
- * @param options.quoteChar - The character used to quote fields containing delimiters or newlines (default: '"')
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
- type HasFlattenOption<TOptions extends DividerInferredOptions> = TOptions extends {
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
- type DividerResult<T extends DividerInput, TOptions extends DividerInferredOptions = DividerEmptyOptions> = T extends StringInput ? DividerStringResult : HasFlattenOption<TOptions> extends true ? DividerStringResult : DividerArrayResult;
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 : HasFlattenOption<ExtractedDividerOptions<TArgs>> extends true ? DividerStringResult : DividerArrayResult;
66
- type ExtractedDividerOptions<TArgs extends DividerArgs> = TArgs extends readonly [...infer Rest, infer Last] ? Rest extends DividerArg[] ? Last extends DividerOptions ? Last : DividerEmptyOptions : DividerEmptyOptions : DividerEmptyOptions;
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 readonly DividerArg[]>(input: T, ...args: TArgs): DividerReturn<T, TArgs>;
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
- /** Character used for quoting values. */
167
- quoteChar?: string;
168
- /** Character used to separate CSV fields. */
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 character used to separate fields (default: ',')
187
- * @param options.quoteChar - The character used to quote fields containing delimiters or newlines (default: '"')
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.join(CACHE_KEY_SEPARATOR);
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.set(separators, regex);
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
- const result = isString(input) ? applyDivision(input) : input.map(applyDivision);
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
- } = resolvedOptions;
368
- const result = isString(input) ? createChunksFromString(input, size, startOffset, maxChunks) : input.map(
369
- (str) => createChunksFromString(str, size, startOffset, maxChunks)
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
- const result = isString(input) ? divideNumberString(input) : input.map(divideNumberString);
382
- const resolvedOptions = options ?? {};
383
- return applyDividerOptions(result, resolvedOptions);
409
+ return transformDividerInput(input, divideNumberString, options);
384
410
  }
385
411
 
386
- // src/utils/quoted.ts
387
- function dividePreserve(input, separator) {
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 (const chunk of dividePreserve(text, pair)) {
395
- count += dividePreserve(chunk, quote).length - 1;
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
- function stripOuterQuotes(text, quoteChar, { lenient = true } = {}) {
400
- const escapedPair = quoteChar + quoteChar;
401
- const isWhitespace = (char) => char === WHITE_SPACE || char === TAB;
402
- const restoreEscapedQuotes = (fieldText) => fieldText.split(escapedPair).join(quoteChar);
403
- const stripped = stripOuterQuotesRaw(text, quoteChar, lenient, isWhitespace);
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, right) => text.slice(0, left) + text.slice(left + 1, right) + text.slice(right + 1);
423
- var removeCharAt = (text, index) => text.slice(0, index) + text.slice(index + 1);
424
- var stripTrailingQuote = (text, quoteChar, isWhitespace) => {
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
- if (lastNonSpaceIndex < 0 || text[lastNonSpaceIndex] !== quoteChar) {
472
+ const trailingQuoteStart = lastNonSpaceIndex - quoteLength + 1;
473
+ if (trailingQuoteStart < 0 || !text.startsWith(quote, trailingQuoteStart)) {
430
474
  return text;
431
475
  }
432
- return removeCharAt(text, lastNonSpaceIndex);
476
+ return removeQuoteAt(text, trailingQuoteStart, quote);
433
477
  };
434
- var stripOuterQuotesRaw = (text, quoteChar, lenient, isWhitespace) => {
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[left] !== quoteChar) return text;
438
- if (text[right] === quoteChar && right > left) {
439
- return stripMatchedOuterQuotes(text, left, right);
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 = removeCharAt(text, left);
443
- return stripTrailingQuote(withoutLeading, quoteChar, isWhitespace);
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
- var appendPiece = (state, piece, delimiter, quote, trim, lenient) => {
460
- state.current = isEmptyString(state.current) ? piece : state.current + delimiter + piece;
461
- const insideQuotes = countUnescaped(state.current, quote) % 2 === 1;
462
- if (!insideQuotes) {
463
- flushField(state, quote, trim, lenient);
464
- }
465
- };
466
- var flushField = (state, quote, trim, lenient) => {
467
- let fieldValue = stripOuterQuotes(state.current, quote, { lenient });
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;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@nyaomaru/divider",
3
3
  "type": "module",
4
- "version": "2.0.6",
4
+ "version": "2.0.8",
5
5
  "description": "To divide string or string[] with a given separator",
6
6
  "main": "./dist/index.cjs",
7
7
  "module": "./dist/index.js",