jtlt 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGES.md +9 -0
- package/README.md +46 -78
- package/demo/codemirror.esm.js +28242 -0
- package/demo/codemirror.js +94 -0
- package/demo/index.css +7 -0
- package/demo/index.html +11 -14
- package/demo/index.js +206 -26
- package/demo/vendor/jamilih/dist/jml.mjs +2341 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.ObjectArrayDelegator.js +356 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.Stringifier.js +186 -0
- package/demo/vendor/jhtml/src/SAJJ/SAJJ.js +746 -0
- package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.html +33 -0
- package/demo/vendor/jhtml/src/SAJJ/testing/SAJJ.testing.js +25 -0
- package/demo/vendor/jhtml/src/jhtml-browser.js +5 -0
- package/demo/vendor/jhtml/src/jhtml-node.cts +3 -0
- package/demo/vendor/jhtml/src/jhtml-node.js +8 -0
- package/demo/vendor/jhtml/src/jhtml-node.mts +1 -0
- package/demo/vendor/jhtml/src/jhtml.cts +3 -0
- package/demo/vendor/jhtml/src/jhtml.js +602 -0
- package/demo/vendor/jhtml/src/jhtml.mts +1 -0
- package/demo/vendor/jsonpath-plus/dist/index-browser-esm.js +2158 -0
- package/demo/vendor/simple-get-json/dist/index-es.js +151 -0
- package/dist/JSONPathTransformerContext.d.ts +88 -4
- package/dist/JSONPathTransformerContext.d.ts.map +1 -1
- package/dist/XPathTransformer.d.ts +2 -2
- package/dist/XPathTransformerContext.d.ts +92 -8
- package/dist/XPathTransformerContext.d.ts.map +1 -1
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/docs/API.expanded.md +5 -3
- package/docs/API.md +1 -1
- package/docs/TO-DO.md +55 -31
- package/eslint.config.js +3 -1
- package/package.json +24 -4
- package/rollup.config.js +13 -0
- package/src/JSONPathTransformerContext.js +422 -4
- package/src/XPathTransformer.js +1 -1
- package/src/XPathTransformerContext.js +462 -11
- package/src/index.js +9 -6
- package/tsconfig.json +5 -2
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import xpath2 from 'xpath2.js'; // Runtime JS import; ambient types declared
|
|
2
|
-
//
|
|
2
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
3
|
+
// xpathVersion: 1 => browser/native XPathEvaluator API; 2 => xpath2.js, 3 => fontoxpath
|
|
4
|
+
import fontoxpath from 'fontoxpath';
|
|
3
5
|
|
|
4
6
|
/**
|
|
5
7
|
* @typedef {object} XPathTransformerContextConfig
|
|
6
8
|
* @property {unknown} [data] - XML/DOM root to transform
|
|
7
|
-
* @property {number} [xpathVersion] - 1
|
|
9
|
+
* @property {number} [xpathVersion] - 1, 2, 3.1 (default 1)
|
|
8
10
|
* @property {import('./index.js').
|
|
9
11
|
* JoiningTransformer} joiningTransformer Joiner
|
|
10
12
|
* @property {boolean} [errorOnEqualPriority]
|
|
@@ -15,13 +17,13 @@ import xpath2 from 'xpath2.js'; // Runtime JS import; ambient types declared
|
|
|
15
17
|
* Execution context for XPath-driven template application.
|
|
16
18
|
*
|
|
17
19
|
* Similar to JSONPathTransformerContext but uses XPath expressions on a
|
|
18
|
-
* DOM/XML-like tree. Supports XPath 1.0 (default)
|
|
19
|
-
* `xpathVersion: 2`.
|
|
20
|
+
* DOM/XML-like tree. Supports XPath 1.0 (default), 2.0 when
|
|
21
|
+
* `xpathVersion: 2`, or 3.1 when `xpathVersion: 3.1`.
|
|
20
22
|
*
|
|
21
23
|
* Expected config:
|
|
22
24
|
* - data: A Document, Element, or XML-like root node.
|
|
23
25
|
* - joiningTransformer: joiner with append(), string(), object(), array(), etc.
|
|
24
|
-
* - xpathVersion: 1|2 (default 1)
|
|
26
|
+
* - xpathVersion: 1|2|3.1 (default 1)
|
|
25
27
|
* - errorOnEqualPriority, specificityPriorityResolver (same semantics).
|
|
26
28
|
*/
|
|
27
29
|
class XPathTransformerContext {
|
|
@@ -69,7 +71,7 @@ class XPathTransformerContext {
|
|
|
69
71
|
if (!expr) {
|
|
70
72
|
return this._contextNode;
|
|
71
73
|
}
|
|
72
|
-
const version = this._config.xpathVersion
|
|
74
|
+
const version = this._config.xpathVersion ?? 1;
|
|
73
75
|
if (version === 1) {
|
|
74
76
|
// Use native XPath (browser-like); rely on DOM doc if available.
|
|
75
77
|
const doc = this._contextNode && this._contextNode.ownerDocument
|
|
@@ -141,8 +143,25 @@ class XPathTransformerContext {
|
|
|
141
143
|
/* c8 ignore stop */
|
|
142
144
|
}
|
|
143
145
|
}
|
|
144
|
-
|
|
145
|
-
|
|
146
|
+
if (version === 2) {
|
|
147
|
+
// Version 2: xpath2.js
|
|
148
|
+
const result = xpath2.evaluate(expr, this._contextNode);
|
|
149
|
+
if (asNodes) {
|
|
150
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
151
|
+
/* c8 ignore next -- array wrap/identity branch counted in other tests */
|
|
152
|
+
return Array.isArray(result) ? result : [result];
|
|
153
|
+
}
|
|
154
|
+
/* c8 ignore next -- scalar return trivial; wrap behavior tested */
|
|
155
|
+
return result;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
159
|
+
// eslint-disable-next-line import/no-named-as-default-member -- Only as default
|
|
160
|
+
const result = fontoxpath.evaluateXPath(
|
|
161
|
+
expr, this._contextNode, undefined, undefined,
|
|
162
|
+
// Non-deprecated, predictable all results
|
|
163
|
+
14 // ReturnType.ALL_RESULTS
|
|
164
|
+
);
|
|
146
165
|
if (asNodes) {
|
|
147
166
|
/* c8 ignore next -- array wrap/identity branch counted in other tests */
|
|
148
167
|
return Array.isArray(result) ? result : [result];
|
|
@@ -523,14 +542,309 @@ class XPathTransformerContext {
|
|
|
523
542
|
return this;
|
|
524
543
|
}
|
|
525
544
|
/**
|
|
526
|
-
* Append number.
|
|
527
|
-
* @param {number
|
|
545
|
+
* Append number with xsl:number-like formatting.
|
|
546
|
+
* @param {number|string|{
|
|
547
|
+
* value?: number|string,
|
|
548
|
+
* count?: string,
|
|
549
|
+
* level?: 'single'|'multiple'|'any',
|
|
550
|
+
* from?: string,
|
|
551
|
+
* format?: string,
|
|
552
|
+
* groupingSeparator?: string,
|
|
553
|
+
* groupingSize?: number
|
|
554
|
+
* }} num - Number value, "position()" string, or options object
|
|
528
555
|
* @returns {XPathTransformerContext}
|
|
529
556
|
*/
|
|
530
557
|
number (num) {
|
|
531
|
-
|
|
558
|
+
// Handle xsl:number-like functionality
|
|
559
|
+
if (typeof num === 'object' && num !== null) {
|
|
560
|
+
const opts = num;
|
|
561
|
+
let {value} = opts;
|
|
562
|
+
|
|
563
|
+
// Handle position() calculation
|
|
564
|
+
if (value === 'position()' || value === undefined) {
|
|
565
|
+
const {count} = opts;
|
|
566
|
+
const level = opts.level || 'single';
|
|
567
|
+
const {from} = opts;
|
|
568
|
+
|
|
569
|
+
switch (level) {
|
|
570
|
+
case 'single': {
|
|
571
|
+
value = this._calculatePosition(count, from);
|
|
572
|
+
|
|
573
|
+
break;
|
|
574
|
+
}
|
|
575
|
+
case 'multiple': {
|
|
576
|
+
// Hierarchical numbering: get position for each ancestor up to root
|
|
577
|
+
const positions = [];
|
|
578
|
+
let node = /** @type {any} */ (this._config).currentNode;
|
|
579
|
+
while (node) {
|
|
580
|
+
positions.unshift(this._calculatePosition(count, undefined));
|
|
581
|
+
node = node.parentNode;
|
|
582
|
+
if (from) {
|
|
583
|
+
const fromResult = /** @type {any} */ (
|
|
584
|
+
this._evalXPath(from, node)
|
|
585
|
+
);
|
|
586
|
+
if (fromResult && fromResult.length > 0) {
|
|
587
|
+
break;
|
|
588
|
+
}
|
|
589
|
+
}
|
|
590
|
+
}
|
|
591
|
+
value = positions.join('.');
|
|
592
|
+
|
|
593
|
+
break;
|
|
594
|
+
}
|
|
595
|
+
case 'any': {
|
|
596
|
+
value = this._calculatePositionAny(count, from);
|
|
597
|
+
|
|
598
|
+
break;
|
|
599
|
+
}
|
|
600
|
+
// No default
|
|
601
|
+
}
|
|
602
|
+
}
|
|
603
|
+
|
|
604
|
+
// Determine format string and locale
|
|
605
|
+
let format = opts.format || '1';
|
|
606
|
+
// @ts-expect-error: dynamic property access
|
|
607
|
+
const locale = opts.lang || 'en';
|
|
608
|
+
// @ts-expect-error: dynamic property access
|
|
609
|
+
const {letterValue} = opts;
|
|
610
|
+
|
|
611
|
+
// If letterValue is 'alphabetic', force alphabetic format
|
|
612
|
+
if (letterValue === 'alphabetic') {
|
|
613
|
+
format = (opts.format && (/^[aA]$/v).test(opts.format)) ? opts.format : 'a';
|
|
614
|
+
}
|
|
615
|
+
|
|
616
|
+
const numValue = typeof value === 'string' ? Number(value) : (value || 1);
|
|
617
|
+
const formatted = this._formatNumber(
|
|
618
|
+
numValue,
|
|
619
|
+
format,
|
|
620
|
+
opts.groupingSeparator,
|
|
621
|
+
opts.groupingSize,
|
|
622
|
+
locale
|
|
623
|
+
);
|
|
624
|
+
this._getJoiningTransformer().plainText(formatted);
|
|
625
|
+
} else if (num === 'position()') {
|
|
626
|
+
// Simple position() call
|
|
627
|
+
const pos = this._calculatePosition();
|
|
628
|
+
this._getJoiningTransformer().number(pos);
|
|
629
|
+
} else {
|
|
630
|
+
// Simple number
|
|
631
|
+
this._getJoiningTransformer().number(
|
|
632
|
+
typeof num === 'string' ? Number(num) : num
|
|
633
|
+
);
|
|
634
|
+
}
|
|
532
635
|
return this;
|
|
533
636
|
}
|
|
637
|
+
|
|
638
|
+
/**
|
|
639
|
+
* Calculate position of current node.
|
|
640
|
+
* @param {string} [count] - XPath pattern to match
|
|
641
|
+
* @param {string} [from] - XPath pattern for ancestor
|
|
642
|
+
* @returns {number}
|
|
643
|
+
* @private
|
|
644
|
+
*/
|
|
645
|
+
_calculatePosition (count, from) {
|
|
646
|
+
// eslint-disable-next-line prefer-destructuring -- TS
|
|
647
|
+
const currentNode = /** @type {any} */ (this._config).currentNode;
|
|
648
|
+
if (!currentNode) {
|
|
649
|
+
return 1;
|
|
650
|
+
}
|
|
651
|
+
|
|
652
|
+
// Get parent node
|
|
653
|
+
const parent = currentNode.parentNode;
|
|
654
|
+
if (!parent) {
|
|
655
|
+
return 1;
|
|
656
|
+
}
|
|
657
|
+
|
|
658
|
+
// If from pattern specified, find that ancestor
|
|
659
|
+
let startNode = parent;
|
|
660
|
+
if (from) {
|
|
661
|
+
const fromResult = /** @type {any} */ (
|
|
662
|
+
this._evalXPath(from, currentNode)
|
|
663
|
+
);
|
|
664
|
+
if (fromResult && fromResult.length > 0) {
|
|
665
|
+
startNode = fromResult[0];
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
// Count preceding siblings
|
|
670
|
+
let position = 1;
|
|
671
|
+
let sibling = currentNode.previousSibling;
|
|
672
|
+
|
|
673
|
+
while (sibling) {
|
|
674
|
+
if (count) {
|
|
675
|
+
// Check if sibling matches count pattern
|
|
676
|
+
const matches = /** @type {any} */ (this._evalXPath(count, sibling));
|
|
677
|
+
if (matches && matches.length > 0) {
|
|
678
|
+
position++;
|
|
679
|
+
}
|
|
680
|
+
} else if (sibling.nodeType === currentNode.nodeType &&
|
|
681
|
+
(!currentNode.nodeName || sibling.nodeName === currentNode.nodeName)) {
|
|
682
|
+
position++;
|
|
683
|
+
}
|
|
684
|
+
sibling = sibling.previousSibling;
|
|
685
|
+
}
|
|
686
|
+
|
|
687
|
+
return position;
|
|
688
|
+
}
|
|
689
|
+
|
|
690
|
+
/**
|
|
691
|
+
* Calculate position counting all ancestors (level=any).
|
|
692
|
+
* @param {string} [count] - XPath pattern to match
|
|
693
|
+
* @param {string} [from] - XPath pattern for ancestor
|
|
694
|
+
* @returns {number}
|
|
695
|
+
* @private
|
|
696
|
+
*/
|
|
697
|
+
_calculatePositionAny (count, from) {
|
|
698
|
+
// eslint-disable-next-line prefer-destructuring -- TS
|
|
699
|
+
const currentNode = /** @type {any} */ (this._config).currentNode;
|
|
700
|
+
if (!currentNode) {
|
|
701
|
+
return 1;
|
|
702
|
+
}
|
|
703
|
+
|
|
704
|
+
// Find root or 'from' node
|
|
705
|
+
let root = currentNode.ownerDocument || currentNode;
|
|
706
|
+
if (from) {
|
|
707
|
+
const fromResult = /** @type {any} */ (
|
|
708
|
+
this._evalXPath(from, currentNode)
|
|
709
|
+
);
|
|
710
|
+
if (fromResult && fromResult.length > 0) {
|
|
711
|
+
root = fromResult[0];
|
|
712
|
+
}
|
|
713
|
+
}
|
|
714
|
+
|
|
715
|
+
// Count all matching nodes in document order up to current
|
|
716
|
+
const pattern = count || 'node()';
|
|
717
|
+
const allNodes = /** @type {any[]} */ (
|
|
718
|
+
this._evalXPath('//' + pattern, root)
|
|
719
|
+
);
|
|
720
|
+
|
|
721
|
+
for (const [i, allNode] of allNodes.entries()) {
|
|
722
|
+
if (allNode === currentNode) {
|
|
723
|
+
return i + 1;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
726
|
+
|
|
727
|
+
return 1;
|
|
728
|
+
}
|
|
729
|
+
|
|
730
|
+
/**
|
|
731
|
+
* Format a number according to format string.
|
|
732
|
+
* @param {number} num - Number to format
|
|
733
|
+
* @param {string} format - Format string (1, a, A, i, I, 01, etc.)
|
|
734
|
+
* @param {string} [groupingSeparator] - Separator for grouping (e.g., ',')
|
|
735
|
+
* @param {number} [groupingSize] - Size of groups (e.g., 3 for 1,000)
|
|
736
|
+
* @param {string} [locale]
|
|
737
|
+
* @returns {string}
|
|
738
|
+
* @private
|
|
739
|
+
*/
|
|
740
|
+
_formatNumber (num, format, groupingSeparator, groupingSize, locale = 'en') {
|
|
741
|
+
if (Number.isNaN(num)) {
|
|
742
|
+
return String(num);
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
let result;
|
|
746
|
+
const formatChar = format.charAt(0);
|
|
747
|
+
|
|
748
|
+
switch (formatChar) {
|
|
749
|
+
case 'i': {
|
|
750
|
+
result = this._toRoman(num).toLowerCase();
|
|
751
|
+
|
|
752
|
+
break;
|
|
753
|
+
}
|
|
754
|
+
case 'I': {
|
|
755
|
+
result = this._toRoman(num);
|
|
756
|
+
|
|
757
|
+
break;
|
|
758
|
+
}
|
|
759
|
+
case 'a': {
|
|
760
|
+
result = this._toAlphabetic(num, false);
|
|
761
|
+
|
|
762
|
+
break;
|
|
763
|
+
}
|
|
764
|
+
case 'A': {
|
|
765
|
+
result = this._toAlphabetic(num, true);
|
|
766
|
+
|
|
767
|
+
break;
|
|
768
|
+
}
|
|
769
|
+
case '0': {
|
|
770
|
+
const width = format.length;
|
|
771
|
+
result = String(num).padStart(width, '0');
|
|
772
|
+
|
|
773
|
+
break;
|
|
774
|
+
}
|
|
775
|
+
default: {
|
|
776
|
+
// Use Intl.NumberFormat for decimal formatting if grouping/locale
|
|
777
|
+
// options are provided
|
|
778
|
+
let options = {};
|
|
779
|
+
if (groupingSeparator || groupingSize) {
|
|
780
|
+
options = {
|
|
781
|
+
useGrouping: true
|
|
782
|
+
};
|
|
783
|
+
}
|
|
784
|
+
try {
|
|
785
|
+
result = new Intl.NumberFormat(locale, options).format(num);
|
|
786
|
+
if (groupingSeparator) {
|
|
787
|
+
result = result.replaceAll(',', groupingSeparator);
|
|
788
|
+
}
|
|
789
|
+
} catch (e) {
|
|
790
|
+
result = String(num);
|
|
791
|
+
}
|
|
792
|
+
}
|
|
793
|
+
}
|
|
794
|
+
return result;
|
|
795
|
+
}
|
|
796
|
+
|
|
797
|
+
/**
|
|
798
|
+
* Convert number to Roman numerals.
|
|
799
|
+
* @param {number} num - Number to convert (1-3999)
|
|
800
|
+
* @returns {string}
|
|
801
|
+
* @private
|
|
802
|
+
*/
|
|
803
|
+
// eslint-disable-next-line class-methods-use-this -- Avoid for now
|
|
804
|
+
_toRoman (num) {
|
|
805
|
+
if (num < 1 || num > 3999) {
|
|
806
|
+
return String(num);
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
const vals = [1000, 900, 500, 400, 100, 90, 50, 40, 10, 9, 5, 4, 1];
|
|
810
|
+
const syms = [
|
|
811
|
+
'M', 'CM', 'D', 'CD', 'C', 'XC', 'L', 'XL', 'X', 'IX', 'V', 'IV', 'I'
|
|
812
|
+
];
|
|
813
|
+
|
|
814
|
+
let result = '';
|
|
815
|
+
for (const [i, val] of vals.entries()) {
|
|
816
|
+
while (num >= val) {
|
|
817
|
+
result += syms[i];
|
|
818
|
+
num -= val;
|
|
819
|
+
}
|
|
820
|
+
}
|
|
821
|
+
return result;
|
|
822
|
+
}
|
|
823
|
+
|
|
824
|
+
/**
|
|
825
|
+
* Convert number to alphabetic sequence.
|
|
826
|
+
* @param {number} num - Number to convert
|
|
827
|
+
* @param {boolean} uppercase - Use uppercase letters
|
|
828
|
+
* @returns {string}
|
|
829
|
+
* @private
|
|
830
|
+
*/
|
|
831
|
+
// eslint-disable-next-line class-methods-use-this -- Avoid for now
|
|
832
|
+
_toAlphabetic (num, uppercase) {
|
|
833
|
+
if (num < 1) {
|
|
834
|
+
return String(num);
|
|
835
|
+
}
|
|
836
|
+
|
|
837
|
+
let result = '';
|
|
838
|
+
const base = uppercase ? 65 : 97; // 'A' or 'a'
|
|
839
|
+
|
|
840
|
+
while (num > 0) {
|
|
841
|
+
num--; // Make 0-indexed
|
|
842
|
+
result = String.fromCodePoint(base + (num % 26)) + result;
|
|
843
|
+
num = Math.floor(num / 26);
|
|
844
|
+
}
|
|
845
|
+
|
|
846
|
+
return result;
|
|
847
|
+
}
|
|
534
848
|
/**
|
|
535
849
|
* Append plain text (no escaping changes).
|
|
536
850
|
* @param {string} str Text
|
|
@@ -824,6 +1138,143 @@ class XPathTransformerContext {
|
|
|
824
1138
|
return this;
|
|
825
1139
|
}
|
|
826
1140
|
|
|
1141
|
+
/**
|
|
1142
|
+
* Analyze a string with a regular expression, equivalent to
|
|
1143
|
+
* xsl:analyze-string. Processes matching and non-matching substrings
|
|
1144
|
+
* with separate callbacks.
|
|
1145
|
+
* @param {string} str - The string to analyze
|
|
1146
|
+
* @param {string|RegExp} regex - Regular expression to match against
|
|
1147
|
+
* @param {{
|
|
1148
|
+
* matchingSubstring?: (
|
|
1149
|
+
* this: XPathTransformerContext,
|
|
1150
|
+
* substring: string,
|
|
1151
|
+
* groups: string[],
|
|
1152
|
+
* regexGroup: (n: number) => string
|
|
1153
|
+
* ) => void,
|
|
1154
|
+
* nonMatchingSubstring?: (
|
|
1155
|
+
* this: XPathTransformerContext,
|
|
1156
|
+
* substring: string
|
|
1157
|
+
* ) => void,
|
|
1158
|
+
* flags?: string
|
|
1159
|
+
* }} options - Options object
|
|
1160
|
+
* @returns {XPathTransformerContext}
|
|
1161
|
+
*/
|
|
1162
|
+
analyzeString (str, regex, options = {}) {
|
|
1163
|
+
// Ensure we have a string
|
|
1164
|
+
const inputString = String(str || '');
|
|
1165
|
+
|
|
1166
|
+
// If empty string, do nothing
|
|
1167
|
+
if (inputString.length === 0) {
|
|
1168
|
+
return this;
|
|
1169
|
+
}
|
|
1170
|
+
|
|
1171
|
+
const {
|
|
1172
|
+
matchingSubstring,
|
|
1173
|
+
nonMatchingSubstring,
|
|
1174
|
+
flags = ''
|
|
1175
|
+
} = options;
|
|
1176
|
+
|
|
1177
|
+
// Convert regex to RegExp if it's a string
|
|
1178
|
+
let regexObj;
|
|
1179
|
+
if (typeof regex === 'string') {
|
|
1180
|
+
// Ensure 'g' flag is present for global matching
|
|
1181
|
+
const actualFlags = flags.includes('g') ? flags : flags + 'g';
|
|
1182
|
+
regexObj = new RegExp(regex, actualFlags);
|
|
1183
|
+
} else {
|
|
1184
|
+
regexObj = regex;
|
|
1185
|
+
// Ensure global flag is set
|
|
1186
|
+
if (!regexObj.global) {
|
|
1187
|
+
regexObj = new RegExp(
|
|
1188
|
+
regexObj.source,
|
|
1189
|
+
regexObj.flags + 'g'
|
|
1190
|
+
);
|
|
1191
|
+
}
|
|
1192
|
+
}
|
|
1193
|
+
|
|
1194
|
+
// Check for zero-length matches (error condition in XSLT)
|
|
1195
|
+
if (regexObj.test('')) {
|
|
1196
|
+
throw new Error(
|
|
1197
|
+
'Regular expression matches zero-length string'
|
|
1198
|
+
);
|
|
1199
|
+
}
|
|
1200
|
+
|
|
1201
|
+
// Store captured groups for access during callback
|
|
1202
|
+
/** @type {string[] | undefined} */
|
|
1203
|
+
let currentCapturedGroups;
|
|
1204
|
+
|
|
1205
|
+
/**
|
|
1206
|
+
* Get captured group by index.
|
|
1207
|
+
* @param {number} groupNumber - Group index
|
|
1208
|
+
* @returns {string} - Captured group or empty string
|
|
1209
|
+
*/
|
|
1210
|
+
const getRegexGroup = (groupNumber) => {
|
|
1211
|
+
if (!currentCapturedGroups ||
|
|
1212
|
+
groupNumber < 0 ||
|
|
1213
|
+
groupNumber >= currentCapturedGroups.length) {
|
|
1214
|
+
return '';
|
|
1215
|
+
}
|
|
1216
|
+
return currentCapturedGroups[groupNumber] || '';
|
|
1217
|
+
};
|
|
1218
|
+
|
|
1219
|
+
// Save previous context to restore later
|
|
1220
|
+
const prevContext = this._contextNode;
|
|
1221
|
+
|
|
1222
|
+
let lastIndex = 0;
|
|
1223
|
+
let match;
|
|
1224
|
+
|
|
1225
|
+
// Bind callbacks to this context
|
|
1226
|
+
const boundMatchingSubstring = matchingSubstring
|
|
1227
|
+
? matchingSubstring.bind(this)
|
|
1228
|
+
: undefined;
|
|
1229
|
+
const boundNonMatchingSubstring = nonMatchingSubstring
|
|
1230
|
+
? nonMatchingSubstring.bind(this)
|
|
1231
|
+
: undefined;
|
|
1232
|
+
|
|
1233
|
+
// Find all matches
|
|
1234
|
+
while ((match = regexObj.exec(inputString)) !== null) {
|
|
1235
|
+
// Process non-matching substring before this match
|
|
1236
|
+
if (match.index > lastIndex) {
|
|
1237
|
+
const nonMatchingStr = inputString.slice(lastIndex, match.index);
|
|
1238
|
+
if (boundNonMatchingSubstring) {
|
|
1239
|
+
boundNonMatchingSubstring(nonMatchingStr);
|
|
1240
|
+
}
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// Process matching substring
|
|
1244
|
+
if (boundMatchingSubstring) {
|
|
1245
|
+
const matchingStr = match[0];
|
|
1246
|
+
// Store captured groups: [full match, group1, group2, ...]
|
|
1247
|
+
currentCapturedGroups = [...match];
|
|
1248
|
+
boundMatchingSubstring(
|
|
1249
|
+
matchingStr, currentCapturedGroups, getRegexGroup
|
|
1250
|
+
);
|
|
1251
|
+
currentCapturedGroups = undefined;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
const {lastIndex: newLastIndex} = regexObj;
|
|
1255
|
+
lastIndex = newLastIndex;
|
|
1256
|
+
|
|
1257
|
+
// Prevent infinite loop on zero-length matches (shouldn't happen
|
|
1258
|
+
// due to earlier check, but defensive)
|
|
1259
|
+
if (match.index === regexObj.lastIndex) {
|
|
1260
|
+
regexObj.lastIndex++;
|
|
1261
|
+
}
|
|
1262
|
+
}
|
|
1263
|
+
|
|
1264
|
+
// Process final non-matching substring
|
|
1265
|
+
if (lastIndex < inputString.length) {
|
|
1266
|
+
const nonMatchingStr = inputString.slice(lastIndex);
|
|
1267
|
+
if (boundNonMatchingSubstring) {
|
|
1268
|
+
boundNonMatchingSubstring(nonMatchingStr);
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
|
|
1272
|
+
// Restore previous context
|
|
1273
|
+
this._contextNode = prevContext;
|
|
1274
|
+
|
|
1275
|
+
return this;
|
|
1276
|
+
}
|
|
1277
|
+
|
|
827
1278
|
/* c8 ignore start -- static default rules object has spotty function
|
|
828
1279
|
* attribution under coverage; behavior is exercised via applyTemplates */
|
|
829
1280
|
static DefaultTemplateRules = {
|
package/src/index.js
CHANGED
|
@@ -175,8 +175,8 @@ export const setWindow = (win) => {
|
|
|
175
175
|
* import('./JSONPathTransformerContext.js').default
|
|
176
176
|
* >],
|
|
177
177
|
* engineType: 'xpath',
|
|
178
|
-
* xpathVersion?: 1|2,
|
|
179
|
-
* outputType?:
|
|
178
|
+
* xpathVersion?: 1|2|3.1,
|
|
179
|
+
* outputType?: T
|
|
180
180
|
* }} XPathJTLTOptions
|
|
181
181
|
*/
|
|
182
182
|
|
|
@@ -390,10 +390,13 @@ class JTLT {
|
|
|
390
390
|
: null
|
|
391
391
|
);
|
|
392
392
|
this.config.templates = query
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
393
|
+
? this.config.engineType === 'xpath'
|
|
394
|
+
? /** @type {XPathTemplateObject<joiningTypes>[]} */ ([
|
|
395
|
+
{name: 'root', path: '//*', template: query}
|
|
396
|
+
])
|
|
397
|
+
: /** @type {JSONPathTemplateObject<joiningTypes>[]} */ ([
|
|
398
|
+
{name: 'root', path: '$', template: query}
|
|
399
|
+
])
|
|
397
400
|
// eslint-disable-next-line @stylistic/max-len -- Long
|
|
398
401
|
: /** @type {JSONPathTemplateObject<joiningTypes>[]|XPathTemplateObject<joiningTypes>[]} */ (
|
|
399
402
|
cfg.templates || [cfg.template]
|
package/tsconfig.json
CHANGED
|
@@ -9,6 +9,9 @@
|
|
|
9
9
|
"noEmit": true,
|
|
10
10
|
"strict": true
|
|
11
11
|
},
|
|
12
|
-
"include": [
|
|
13
|
-
|
|
12
|
+
"include": [
|
|
13
|
+
"*.js", "*.d.ts", "src/**/*.js", "demo/**/*.js", "test/**/*.js",
|
|
14
|
+
"typings/xpath2-js.d.ts"
|
|
15
|
+
],
|
|
16
|
+
"exclude": ["node_modules", "./dist/**/*.js", "demo/codemirror.esm.js", "demo/vendor"]
|
|
14
17
|
}
|