path-expression-matcher 1.2.1 → 1.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/README.md +175 -10
- package/lib/pem.cjs +1 -1
- package/lib/pem.d.cts +111 -0
- package/lib/pem.min.js +1 -1
- package/lib/pem.min.js.map +1 -1
- package/package.json +1 -1
- package/src/Expression.js +2 -2
- package/src/ExpressionSet.js +209 -0
- package/src/Matcher.js +50 -20
- package/src/index.d.ts +189 -1
- package/src/index.js +3 -2
package/README.md
CHANGED
|
@@ -138,7 +138,7 @@ console.log(matcher.toString()); // "soap:Envelope.soap:Body.ns:UserId"
|
|
|
138
138
|
#### Constructor
|
|
139
139
|
|
|
140
140
|
```javascript
|
|
141
|
-
new Expression(pattern, options)
|
|
141
|
+
new Expression(pattern, options = {}, data)
|
|
142
142
|
```
|
|
143
143
|
|
|
144
144
|
**Parameters:**
|
|
@@ -149,6 +149,8 @@ new Expression(pattern, options)
|
|
|
149
149
|
```javascript
|
|
150
150
|
const expr1 = new Expression("root.users.user");
|
|
151
151
|
const expr2 = new Expression("root/users/user", { separator: '/' });
|
|
152
|
+
const expr3 = new Expression("root/users/user", { separator: '/' }, { extra: "data"});
|
|
153
|
+
console.log(expr3.data) // { extra: "data" }
|
|
152
154
|
```
|
|
153
155
|
|
|
154
156
|
#### Methods
|
|
@@ -240,6 +242,22 @@ if (matcher.matches(expr)) {
|
|
|
240
242
|
}
|
|
241
243
|
```
|
|
242
244
|
|
|
245
|
+
#### `matchesAny(exprSet)` → `boolean`
|
|
246
|
+
|
|
247
|
+
Please check `ExpressionSet` class for more details.
|
|
248
|
+
|
|
249
|
+
```javascript
|
|
250
|
+
const matcher = new Matcher();
|
|
251
|
+
const exprSet = new ExpressionSet();
|
|
252
|
+
exprSet.add(new Expression("root.users.user"));
|
|
253
|
+
exprSet.add(new Expression("root.config.*"));
|
|
254
|
+
exprSet.seal();
|
|
255
|
+
|
|
256
|
+
if (matcher.matchesAny(exprSet)) {
|
|
257
|
+
// Current path matches any expression in the set
|
|
258
|
+
}
|
|
259
|
+
```
|
|
260
|
+
|
|
243
261
|
##### `getCurrentTag()`
|
|
244
262
|
|
|
245
263
|
Get current tag name.
|
|
@@ -674,21 +692,168 @@ for (let i = 0; i < 1000; i++) {
|
|
|
674
692
|
}
|
|
675
693
|
```
|
|
676
694
|
|
|
677
|
-
### Batch Pattern Checking
|
|
695
|
+
### Batch Pattern Checking with ExpressionSet (Recommended)
|
|
696
|
+
|
|
697
|
+
For checking multiple patterns on every tag, use `ExpressionSet` instead of a manual loop.
|
|
698
|
+
It pre-indexes expressions at build time so each call to `matchesAny()` does an O(1) bucket
|
|
699
|
+
lookup rather than a full O(N) scan:
|
|
678
700
|
|
|
679
701
|
```javascript
|
|
680
|
-
|
|
681
|
-
const patterns = [
|
|
682
|
-
new Expression("..user"),
|
|
683
|
-
new Expression("..post"),
|
|
684
|
-
new Expression("..comment"),
|
|
685
|
-
];
|
|
702
|
+
import { Expression, ExpressionSet, Matcher } from 'path-expression-matcher';
|
|
686
703
|
|
|
687
|
-
|
|
688
|
-
|
|
704
|
+
// Build once at config/startup time
|
|
705
|
+
const stopNodes = new ExpressionSet();
|
|
706
|
+
stopNodes
|
|
707
|
+
.add(new Expression('root.users.user'))
|
|
708
|
+
.add(new Expression('root.config.*'))
|
|
709
|
+
.add(new Expression('..script'))
|
|
710
|
+
.seal(); // prevent accidental mutation during parsing
|
|
711
|
+
|
|
712
|
+
// Per-tag — hot path
|
|
713
|
+
if (stopNodes.matchesAny(matcher)) {
|
|
714
|
+
// handle stop node
|
|
715
|
+
}
|
|
716
|
+
```
|
|
717
|
+
|
|
718
|
+
This replaces the manual loop pattern:
|
|
719
|
+
|
|
720
|
+
```javascript
|
|
721
|
+
// ❌ Before — O(N) per tag
|
|
722
|
+
function isStopNode(expressions, matcher) {
|
|
723
|
+
for (let i = 0; i < expressions.length; i++) {
|
|
724
|
+
if (matcher.matches(expressions[i])) return true;
|
|
725
|
+
}
|
|
726
|
+
return false;
|
|
689
727
|
}
|
|
728
|
+
|
|
729
|
+
// ✅ After — O(1) lookup per tag
|
|
730
|
+
const stopNodes = new ExpressionSet();
|
|
731
|
+
stopNodes.addAll(expressions);
|
|
732
|
+
stopNodes.matchesAny(matcher);
|
|
733
|
+
//or matcher.matchesAny(stopNodes)
|
|
734
|
+
```
|
|
735
|
+
|
|
736
|
+
---
|
|
737
|
+
|
|
738
|
+
## 📦 ExpressionSet API
|
|
739
|
+
|
|
740
|
+
`ExpressionSet` is an indexed collection of `Expression` objects designed for efficient
|
|
741
|
+
bulk matching. Build it once from your config, then call `matchesAny()` on every tag.
|
|
742
|
+
|
|
743
|
+
### Constructor
|
|
744
|
+
|
|
745
|
+
```javascript
|
|
746
|
+
const set = new ExpressionSet();
|
|
747
|
+
```
|
|
748
|
+
|
|
749
|
+
### `add(expression)` → `this`
|
|
750
|
+
|
|
751
|
+
Add a single `Expression`. Duplicate patterns (same pattern string) are silently ignored.
|
|
752
|
+
Returns `this` for chaining. Throws `TypeError` if the set is sealed.
|
|
753
|
+
|
|
754
|
+
```javascript
|
|
755
|
+
set.add(new Expression('root.users.user'));
|
|
756
|
+
set.add(new Expression('..script'));
|
|
757
|
+
```
|
|
758
|
+
|
|
759
|
+
### `addAll(expressions)` → `this`
|
|
760
|
+
|
|
761
|
+
Add an array of `Expression` objects at once. Returns `this` for chaining.
|
|
762
|
+
|
|
763
|
+
```javascript
|
|
764
|
+
set.addAll(config.stopNodes.map(p => new Expression(p)));
|
|
765
|
+
```
|
|
766
|
+
|
|
767
|
+
### `has(expression)` → `boolean`
|
|
768
|
+
|
|
769
|
+
Check whether an expression with the same pattern is already present.
|
|
770
|
+
|
|
771
|
+
```javascript
|
|
772
|
+
set.has(new Expression('root.users.user')); // true / false
|
|
690
773
|
```
|
|
691
774
|
|
|
775
|
+
### `seal()` → `this`
|
|
776
|
+
|
|
777
|
+
Prevent further additions. Any subsequent call to `add()` or `addAll()` throws a `TypeError`.
|
|
778
|
+
Useful to guard against accidental mutation once parsing has started.
|
|
779
|
+
|
|
780
|
+
```javascript
|
|
781
|
+
const stopNodes = new ExpressionSet();
|
|
782
|
+
stopNodes.addAll(patterns).seal();
|
|
783
|
+
|
|
784
|
+
stopNodes.add(new Expression('root.extra')); // ❌ TypeError: ExpressionSet is sealed
|
|
785
|
+
```
|
|
786
|
+
|
|
787
|
+
### `size` → `number`
|
|
788
|
+
|
|
789
|
+
Number of distinct expressions in the set.
|
|
790
|
+
|
|
791
|
+
```javascript
|
|
792
|
+
set.size; // 3
|
|
793
|
+
```
|
|
794
|
+
|
|
795
|
+
### `isSealed` → `boolean`
|
|
796
|
+
|
|
797
|
+
Whether `seal()` has been called.
|
|
798
|
+
|
|
799
|
+
### `matchesAny(matcher)` → `boolean`
|
|
800
|
+
|
|
801
|
+
Returns `true` if the matcher's current path matches **any** expression in the set.
|
|
802
|
+
Accepts both a `Matcher` instance and a `ReadOnlyMatcher` view.
|
|
803
|
+
|
|
804
|
+
```javascript
|
|
805
|
+
if (stopNodes.matchesAny(matcher)) { /* ... */ }
|
|
806
|
+
if (stopNodes.matchesAny(matcher.readOnly())) { /* ... */ } // also works
|
|
807
|
+
```
|
|
808
|
+
|
|
809
|
+
**How indexing works:** expressions are bucketed at `add()` time, not at match time.
|
|
810
|
+
|
|
811
|
+
| Expression type | Bucket | Lookup cost |
|
|
812
|
+
|---|---|---|
|
|
813
|
+
| Fixed path, concrete tag (`root.users.user`) | `depth:tag` map | O(1) |
|
|
814
|
+
| Fixed path, wildcard tag (`root.config.*`) | `depth` map | O(1) |
|
|
815
|
+
| Deep wildcard (`..script`) | flat list | O(D) — always scanned |
|
|
816
|
+
|
|
817
|
+
In practice, deep-wildcard expressions are rare in configs, so the list stays small.
|
|
818
|
+
|
|
819
|
+
### `findMatch(matcher)` → `Expression`
|
|
820
|
+
|
|
821
|
+
Returns the Expression instance that matched the current path. Accepts both a `Matcher` instance and a `ReadOnlyMatcher` view.
|
|
822
|
+
|
|
823
|
+
```javascript
|
|
824
|
+
const node = stopNodes.findMatch(matcher);
|
|
825
|
+
```
|
|
826
|
+
|
|
827
|
+
|
|
828
|
+
### Example 7: ExpressionSet in a real parser loop
|
|
829
|
+
|
|
830
|
+
```javascript
|
|
831
|
+
import { XMLParser } from 'fast-xml-parser';
|
|
832
|
+
import { Expression, ExpressionSet, Matcher } from 'path-expression-matcher';
|
|
833
|
+
|
|
834
|
+
// Config-time setup
|
|
835
|
+
const stopNodes = new ExpressionSet();
|
|
836
|
+
stopNodes
|
|
837
|
+
.addAll(['script', 'style'].map(t => new Expression(`..${t}`)))
|
|
838
|
+
.seal();
|
|
839
|
+
|
|
840
|
+
const matcher = new Matcher();
|
|
841
|
+
|
|
842
|
+
const parser = new XMLParser({
|
|
843
|
+
onOpenTag(tagName, attrs) {
|
|
844
|
+
matcher.push(tagName, attrs);
|
|
845
|
+
if (stopNodes.matchesAny(matcher)) {
|
|
846
|
+
// treat as stop node
|
|
847
|
+
}
|
|
848
|
+
},
|
|
849
|
+
onCloseTag() {
|
|
850
|
+
matcher.pop();
|
|
851
|
+
},
|
|
852
|
+
});
|
|
853
|
+
```
|
|
854
|
+
|
|
855
|
+
|
|
856
|
+
|
|
692
857
|
## 🔗 Integration with fast-xml-parser
|
|
693
858
|
|
|
694
859
|
**Basic integration:**
|
package/lib/pem.cjs
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
(()=>{"use strict";var t={d:(e,s)=>{for(var
|
|
1
|
+
(()=>{"use strict";var t={d:(e,s)=>{for(var n in s)t.o(s,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:s[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{Expression:()=>s,ExpressionSet:()=>r,Matcher:()=>i,default:()=>a});class s{constructor(t,e={},s){this.pattern=t,this.separator=e.separator||".",this.segments=this._parse(t),this.data=s,this._hasDeepWildcard=this.segments.some(t=>"deep-wildcard"===t.type),this._hasAttributeCondition=this.segments.some(t=>void 0!==t.attrName),this._hasPositionSelector=this.segments.some(t=>void 0!==t.position)}_parse(t){const e=[];let s=0,n="";for(;s<t.length;)t[s]===this.separator?s+1<t.length&&t[s+1]===this.separator?(n.trim()&&(e.push(this._parseSegment(n.trim())),n=""),e.push({type:"deep-wildcard"}),s+=2):(n.trim()&&e.push(this._parseSegment(n.trim())),n="",s++):(n+=t[s],s++);return n.trim()&&e.push(this._parseSegment(n.trim())),e}_parseSegment(t){const e={type:"tag"};let s=null,n=t;const i=t.match(/^([^\[]+)(\[[^\]]*\])(.*)$/);if(i&&(n=i[1]+i[3],i[2])){const t=i[2].slice(1,-1);t&&(s=t)}let r,a,h=n;if(n.includes("::")){const e=n.indexOf("::");if(r=n.substring(0,e).trim(),h=n.substring(e+2).trim(),!r)throw new Error(`Invalid namespace in pattern: ${t}`)}let l=null;if(h.includes(":")){const t=h.lastIndexOf(":"),e=h.substring(0,t).trim(),s=h.substring(t+1).trim();["first","last","odd","even"].includes(s)||/^nth\(\d+\)$/.test(s)?(a=e,l=s):a=h}else a=h;if(!a)throw new Error(`Invalid segment pattern: ${t}`);if(e.tag=a,r&&(e.namespace=r),s)if(s.includes("=")){const t=s.indexOf("=");e.attrName=s.substring(0,t).trim(),e.attrValue=s.substring(t+1).trim()}else e.attrName=s.trim();if(l){const t=l.match(/^nth\((\d+)\)$/);t?(e.position="nth",e.positionValue=parseInt(t[1],10)):e.position=l}return e}get length(){return this.segments.length}hasDeepWildcard(){return this._hasDeepWildcard}hasAttributeCondition(){return this._hasAttributeCondition}hasPositionSelector(){return this._hasPositionSelector}toString(){return this.pattern}}const n=new Set(["push","pop","reset","updateCurrent","restore"]);class i{constructor(t={}){this.separator=t.separator||".",this.path=[],this.siblingStacks=[],this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null}push(t,e=null,s=null){this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null,this.path.length>0&&(this.path[this.path.length-1].values=void 0);const n=this.path.length;this.siblingStacks[n]||(this.siblingStacks[n]=new Map);const i=this.siblingStacks[n],r=s?`${s}:${t}`:t,a=i.get(r)||0;let h=0;for(const t of i.values())h+=t;i.set(r,a+1);const l={tag:t,position:h,counter:a};null!=s&&(l.namespace=s),null!=e&&(l.values=e),this.path.push(l)}pop(){if(0===this.path.length)return;this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null;const t=this.path.pop();return this.siblingStacks.length>this.path.length+1&&(this.siblingStacks.length=this.path.length+1),t}updateCurrent(t){if(this.path.length>0){const e=this.path[this.path.length-1];null!=t&&(e.values=t,this._frozenPathCache=null)}}getCurrentTag(){return this.path.length>0?this.path[this.path.length-1].tag:void 0}getCurrentNamespace(){return this.path.length>0?this.path[this.path.length-1].namespace:void 0}getAttrValue(t){if(0===this.path.length)return;const e=this.path[this.path.length-1];return e.values?.[t]}hasAttr(t){if(0===this.path.length)return!1;const e=this.path[this.path.length-1];return void 0!==e.values&&t in e.values}getPosition(){return 0===this.path.length?-1:this.path[this.path.length-1].position??0}getCounter(){return 0===this.path.length?-1:this.path[this.path.length-1].counter??0}getIndex(){return this.getPosition()}getDepth(){return this.path.length}toString(t,e=!0){const s=t||this.separator;if(s===this.separator&&!0===e){if(null!==this._pathStringCache&&void 0!==this._pathStringCache)return this._pathStringCache;const t=this.path.map(t=>e&&t.namespace?`${t.namespace}:${t.tag}`:t.tag).join(s);return this._pathStringCache=t,t}return this.path.map(t=>e&&t.namespace?`${t.namespace}:${t.tag}`:t.tag).join(s)}toArray(){return this.path.map(t=>t.tag)}reset(){this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null,this.path=[],this.siblingStacks=[]}matches(t){const e=t.segments;return 0!==e.length&&(t.hasDeepWildcard()?this._matchWithDeepWildcard(e):this._matchSimple(e))}_matchSimple(t){if(this.path.length!==t.length)return!1;for(let e=0;e<t.length;e++){const s=t[e],n=this.path[e],i=e===this.path.length-1;if(!this._matchSegment(s,n,i))return!1}return!0}_matchWithDeepWildcard(t){let e=this.path.length-1,s=t.length-1;for(;s>=0&&e>=0;){const n=t[s];if("deep-wildcard"===n.type){if(s--,s<0)return!0;const n=t[s];let i=!1;for(let t=e;t>=0;t--){const r=t===this.path.length-1;if(this._matchSegment(n,this.path[t],r)){e=t-1,s--,i=!0;break}}if(!i)return!1}else{const t=e===this.path.length-1;if(!this._matchSegment(n,this.path[e],t))return!1;e--,s--}}return s<0}_matchSegment(t,e,s){if("*"!==t.tag&&t.tag!==e.tag)return!1;if(void 0!==t.namespace&&"*"!==t.namespace&&t.namespace!==e.namespace)return!1;if(void 0!==t.attrName){if(!s)return!1;if(!e.values||!(t.attrName in e.values))return!1;if(void 0!==t.attrValue){const s=e.values[t.attrName];if(String(s)!==String(t.attrValue))return!1}}if(void 0!==t.position){if(!s)return!1;const n=e.counter??0;if("first"===t.position&&0!==n)return!1;if("odd"===t.position&&n%2!=1)return!1;if("even"===t.position&&n%2!=0)return!1;if("nth"===t.position&&n!==t.positionValue)return!1}return!0}matchesAny(t){return t.matchesAny(this)}snapshot(){return{path:this.path.map(t=>({...t})),siblingStacks:this.siblingStacks.map(t=>new Map(t))}}restore(t){this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null,this.path=t.path.map(t=>({...t})),this.siblingStacks=t.siblingStacks.map(t=>new Map(t))}readOnly(){return new Proxy(this,{get(t,e,s){if(n.has(e))return()=>{throw new TypeError(`Cannot call '${e}' on a read-only Matcher. Obtain a writable instance to mutate state.`)};if("path"===e)return null===t._frozenPathCache&&(t._frozenPathCache=Object.freeze(t.path.map(t=>Object.freeze({...t})))),t._frozenPathCache;if("siblingStacks"===e)return null===t._frozenSiblingsCache&&(t._frozenSiblingsCache=Object.freeze(t.siblingStacks.map(t=>Object.freeze(new Map(t))))),t._frozenSiblingsCache;const i=Reflect.get(t,e,s);return"function"==typeof i?i.bind(t):i},set(t,e){throw new TypeError(`Cannot set property '${String(e)}' on a read-only Matcher.`)},deleteProperty(t,e){throw new TypeError(`Cannot delete property '${String(e)}' from a read-only Matcher.`)}})}}class r{constructor(){this._byDepthAndTag=new Map,this._wildcardByDepth=new Map,this._deepWildcards=[],this._patterns=new Set,this._sealed=!1}add(t){if(this._sealed)throw new TypeError("ExpressionSet is sealed. Create a new ExpressionSet to add more expressions.");if(this._patterns.has(t.pattern))return this;if(this._patterns.add(t.pattern),t.hasDeepWildcard())return this._deepWildcards.push(t),this;const e=t.length,s=t.segments[t.segments.length-1],n=s?.tag;if(n&&"*"!==n){const s=`${e}:${n}`;this._byDepthAndTag.has(s)||this._byDepthAndTag.set(s,[]),this._byDepthAndTag.get(s).push(t)}else this._wildcardByDepth.has(e)||this._wildcardByDepth.set(e,[]),this._wildcardByDepth.get(e).push(t);return this}addAll(t){for(const e of t)this.add(e);return this}has(t){return this._patterns.has(t.pattern)}get size(){return this._patterns.size}seal(){return this._sealed=!0,this}get isSealed(){return this._sealed}matchesAny(t){return null!==this.findMatch(t)}findMatch(t){const e=t.getDepth(),s=`${e}:${t.getCurrentTag()}`,n=this._byDepthAndTag.get(s);if(n)for(let e=0;e<n.length;e++)if(t.matches(n[e]))return n[e];const i=this._wildcardByDepth.get(e);if(i)for(let e=0;e<i.length;e++)if(t.matches(i[e]))return i[e];for(let e=0;e<this._deepWildcards.length;e++)if(t.matches(this._deepWildcards[e]))return this._deepWildcards[e];return null}}const a={Expression:s,Matcher:i,ExpressionSet:r};module.exports=e})();
|
package/lib/pem.d.cts
CHANGED
|
@@ -299,6 +299,13 @@ declare interface ReadOnlyMatcher {
|
|
|
299
299
|
*/
|
|
300
300
|
matches(expression: Expression): boolean;
|
|
301
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Test whether the matcher's current path matches **any** expression in the set.
|
|
304
|
+
*
|
|
305
|
+
* @param exprSet - A `ExpressionSet` instance
|
|
306
|
+
* @returns `true` if at least one expression matches the current path
|
|
307
|
+
*/
|
|
308
|
+
matchesAny(exprSet: ExpressionSet): boolean;
|
|
302
309
|
/**
|
|
303
310
|
* Create a snapshot of current state
|
|
304
311
|
* @returns State snapshot that can be restored later
|
|
@@ -490,6 +497,31 @@ declare class Matcher {
|
|
|
490
497
|
*/
|
|
491
498
|
matches(expression: Expression): boolean;
|
|
492
499
|
|
|
500
|
+
|
|
501
|
+
/**
|
|
502
|
+
* Test whether the matcher's current path matches **any** expression in the set.
|
|
503
|
+
*
|
|
504
|
+
* Uses the pre-built index to evaluate only the relevant bucket(s):
|
|
505
|
+
* 1. Exact depth + tag — O(1) lookup
|
|
506
|
+
* 2. Depth-matched wildcard tag — O(1) lookup
|
|
507
|
+
* 3. Deep-wildcard expressions — always scanned (typically a small list)
|
|
508
|
+
*
|
|
509
|
+
* @param exprSet - A `ExpressionSet` instance
|
|
510
|
+
* @returns `true` if at least one expression matches the current path
|
|
511
|
+
*
|
|
512
|
+
* @example
|
|
513
|
+
* ```typescript
|
|
514
|
+
* // Replaces:
|
|
515
|
+
* // for (const expr of stopNodeExpressions) {
|
|
516
|
+
* // if (matcher.matches(expr)) return true;
|
|
517
|
+
* // }
|
|
518
|
+
*
|
|
519
|
+
* if (matcher.matchesAny(stopNodes)) {
|
|
520
|
+
* // current tag is a stop node
|
|
521
|
+
* }
|
|
522
|
+
* ```
|
|
523
|
+
*/
|
|
524
|
+
matchesAny(exprSet: ExpressionSet): boolean;
|
|
493
525
|
/**
|
|
494
526
|
* Create a snapshot of current state
|
|
495
527
|
* @returns State snapshot that can be restored later
|
|
@@ -508,10 +540,89 @@ declare class Matcher {
|
|
|
508
540
|
readOnly(): ReadOnlyMatcher;
|
|
509
541
|
}
|
|
510
542
|
|
|
543
|
+
/**
|
|
544
|
+
* ExpressionSet - An indexed collection of Expressions for efficient bulk matching
|
|
545
|
+
*
|
|
546
|
+
* Pre-indexes expressions at insertion time by depth and terminal tag name so
|
|
547
|
+
* that `matchesAny()` performs an O(1) bucket lookup rather than a full O(E)
|
|
548
|
+
* linear scan on every tag.
|
|
549
|
+
*
|
|
550
|
+
* @example
|
|
551
|
+
* ```javascript
|
|
552
|
+
* const { Expression, ExpressionSet, Matcher } = require('path-expression-matcher');
|
|
553
|
+
*
|
|
554
|
+
* // Build once at config time
|
|
555
|
+
* const stopNodes = new ExpressionSet();
|
|
556
|
+
* stopNodes
|
|
557
|
+
* .add(new Expression('root.users.user'))
|
|
558
|
+
* .add(new Expression('root.config.*'))
|
|
559
|
+
* .add(new Expression('..script'))
|
|
560
|
+
* .seal();
|
|
561
|
+
*
|
|
562
|
+
* // Per-tag — hot path
|
|
563
|
+
* if (stopNodes.matchesAny(matcher)) { ... }
|
|
564
|
+
* ```
|
|
565
|
+
*/
|
|
566
|
+
declare class ExpressionSet {
|
|
567
|
+
constructor();
|
|
568
|
+
|
|
569
|
+
/** Number of expressions currently in the set. */
|
|
570
|
+
readonly size: number;
|
|
571
|
+
|
|
572
|
+
/** Whether the set has been sealed against further modifications. */
|
|
573
|
+
readonly isSealed: boolean;
|
|
574
|
+
|
|
575
|
+
/**
|
|
576
|
+
* Add a single Expression. Duplicate patterns are silently ignored.
|
|
577
|
+
* @throws {TypeError} if the set has been sealed
|
|
578
|
+
*/
|
|
579
|
+
add(expression: Expression): this;
|
|
580
|
+
|
|
581
|
+
/**
|
|
582
|
+
* Add multiple expressions at once.
|
|
583
|
+
* @throws {TypeError} if the set has been sealed
|
|
584
|
+
*/
|
|
585
|
+
addAll(expressions: Expression[]): this;
|
|
586
|
+
|
|
587
|
+
/** Check whether an expression with the same pattern is already present. */
|
|
588
|
+
has(expression: Expression): boolean;
|
|
589
|
+
|
|
590
|
+
/**
|
|
591
|
+
* Seal the set against further modifications.
|
|
592
|
+
* Any subsequent call to add() or addAll() will throw a TypeError.
|
|
593
|
+
*/
|
|
594
|
+
seal(): this;
|
|
595
|
+
|
|
596
|
+
/**
|
|
597
|
+
* Test whether the matcher's current path matches any expression in the set.
|
|
598
|
+
* Accepts both a Matcher instance and a ReadOnlyMatcher view.
|
|
599
|
+
*
|
|
600
|
+
*
|
|
601
|
+
* @param matcher - A `Matcher` instance or a `ReadOnlyMatcher` view
|
|
602
|
+
* @returns Expression if at least one expression matches the current path
|
|
603
|
+
*/
|
|
604
|
+
matchesAny(matcher: Matcher | ReadOnlyMatcher): boolean;
|
|
605
|
+
|
|
606
|
+
/**
|
|
607
|
+
* Find the first expression in the set that matches the matcher's current path.
|
|
608
|
+
|
|
609
|
+
*
|
|
610
|
+
* @param matcher - A `Matcher` instance or a `ReadOnlyMatcher` view
|
|
611
|
+
* @returns Expression if at least one expression matches the current path
|
|
612
|
+
*
|
|
613
|
+
* @example
|
|
614
|
+
* ```typescript
|
|
615
|
+
* const node = stopNodes.findMatch(matcher);
|
|
616
|
+
* ```
|
|
617
|
+
*/
|
|
618
|
+
findMatch(matcher: Matcher | ReadOnlyMatcher): Expression;
|
|
619
|
+
}
|
|
620
|
+
|
|
511
621
|
declare namespace pathExpressionMatcher {
|
|
512
622
|
export {
|
|
513
623
|
Expression,
|
|
514
624
|
Matcher,
|
|
625
|
+
ExpressionSet,
|
|
515
626
|
ExpressionOptions,
|
|
516
627
|
MatcherOptions,
|
|
517
628
|
Segment,
|
package/lib/pem.min.js
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
|
-
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.pem=e():t.pem=e()}(this,()=>(()=>{"use strict";var t={d:(e,s)=>{for(var n in s)t.o(s,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:s[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{Expression:()=>s,Matcher:()=>i,default:()=>
|
|
1
|
+
!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.pem=e():t.pem=e()}(this,()=>(()=>{"use strict";var t={d:(e,s)=>{for(var n in s)t.o(s,n)&&!t.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:s[n]})},o:(t,e)=>Object.prototype.hasOwnProperty.call(t,e),r:t=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(t,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(t,"__esModule",{value:!0})}},e={};t.r(e),t.d(e,{Expression:()=>s,ExpressionSet:()=>r,Matcher:()=>i,default:()=>a});class s{constructor(t,e={},s){this.pattern=t,this.separator=e.separator||".",this.segments=this._parse(t),this.data=s,this._hasDeepWildcard=this.segments.some(t=>"deep-wildcard"===t.type),this._hasAttributeCondition=this.segments.some(t=>void 0!==t.attrName),this._hasPositionSelector=this.segments.some(t=>void 0!==t.position)}_parse(t){const e=[];let s=0,n="";for(;s<t.length;)t[s]===this.separator?s+1<t.length&&t[s+1]===this.separator?(n.trim()&&(e.push(this._parseSegment(n.trim())),n=""),e.push({type:"deep-wildcard"}),s+=2):(n.trim()&&e.push(this._parseSegment(n.trim())),n="",s++):(n+=t[s],s++);return n.trim()&&e.push(this._parseSegment(n.trim())),e}_parseSegment(t){const e={type:"tag"};let s=null,n=t;const i=t.match(/^([^\[]+)(\[[^\]]*\])(.*)$/);if(i&&(n=i[1]+i[3],i[2])){const t=i[2].slice(1,-1);t&&(s=t)}let r,a,h=n;if(n.includes("::")){const e=n.indexOf("::");if(r=n.substring(0,e).trim(),h=n.substring(e+2).trim(),!r)throw new Error(`Invalid namespace in pattern: ${t}`)}let o=null;if(h.includes(":")){const t=h.lastIndexOf(":"),e=h.substring(0,t).trim(),s=h.substring(t+1).trim();["first","last","odd","even"].includes(s)||/^nth\(\d+\)$/.test(s)?(a=e,o=s):a=h}else a=h;if(!a)throw new Error(`Invalid segment pattern: ${t}`);if(e.tag=a,r&&(e.namespace=r),s)if(s.includes("=")){const t=s.indexOf("=");e.attrName=s.substring(0,t).trim(),e.attrValue=s.substring(t+1).trim()}else e.attrName=s.trim();if(o){const t=o.match(/^nth\((\d+)\)$/);t?(e.position="nth",e.positionValue=parseInt(t[1],10)):e.position=o}return e}get length(){return this.segments.length}hasDeepWildcard(){return this._hasDeepWildcard}hasAttributeCondition(){return this._hasAttributeCondition}hasPositionSelector(){return this._hasPositionSelector}toString(){return this.pattern}}const n=new Set(["push","pop","reset","updateCurrent","restore"]);class i{constructor(t={}){this.separator=t.separator||".",this.path=[],this.siblingStacks=[],this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null}push(t,e=null,s=null){this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null,this.path.length>0&&(this.path[this.path.length-1].values=void 0);const n=this.path.length;this.siblingStacks[n]||(this.siblingStacks[n]=new Map);const i=this.siblingStacks[n],r=s?`${s}:${t}`:t,a=i.get(r)||0;let h=0;for(const t of i.values())h+=t;i.set(r,a+1);const o={tag:t,position:h,counter:a};null!=s&&(o.namespace=s),null!=e&&(o.values=e),this.path.push(o)}pop(){if(0===this.path.length)return;this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null;const t=this.path.pop();return this.siblingStacks.length>this.path.length+1&&(this.siblingStacks.length=this.path.length+1),t}updateCurrent(t){if(this.path.length>0){const e=this.path[this.path.length-1];null!=t&&(e.values=t,this._frozenPathCache=null)}}getCurrentTag(){return this.path.length>0?this.path[this.path.length-1].tag:void 0}getCurrentNamespace(){return this.path.length>0?this.path[this.path.length-1].namespace:void 0}getAttrValue(t){if(0===this.path.length)return;const e=this.path[this.path.length-1];return e.values?.[t]}hasAttr(t){if(0===this.path.length)return!1;const e=this.path[this.path.length-1];return void 0!==e.values&&t in e.values}getPosition(){return 0===this.path.length?-1:this.path[this.path.length-1].position??0}getCounter(){return 0===this.path.length?-1:this.path[this.path.length-1].counter??0}getIndex(){return this.getPosition()}getDepth(){return this.path.length}toString(t,e=!0){const s=t||this.separator;if(s===this.separator&&!0===e){if(null!==this._pathStringCache&&void 0!==this._pathStringCache)return this._pathStringCache;const t=this.path.map(t=>e&&t.namespace?`${t.namespace}:${t.tag}`:t.tag).join(s);return this._pathStringCache=t,t}return this.path.map(t=>e&&t.namespace?`${t.namespace}:${t.tag}`:t.tag).join(s)}toArray(){return this.path.map(t=>t.tag)}reset(){this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null,this.path=[],this.siblingStacks=[]}matches(t){const e=t.segments;return 0!==e.length&&(t.hasDeepWildcard()?this._matchWithDeepWildcard(e):this._matchSimple(e))}_matchSimple(t){if(this.path.length!==t.length)return!1;for(let e=0;e<t.length;e++){const s=t[e],n=this.path[e],i=e===this.path.length-1;if(!this._matchSegment(s,n,i))return!1}return!0}_matchWithDeepWildcard(t){let e=this.path.length-1,s=t.length-1;for(;s>=0&&e>=0;){const n=t[s];if("deep-wildcard"===n.type){if(s--,s<0)return!0;const n=t[s];let i=!1;for(let t=e;t>=0;t--){const r=t===this.path.length-1;if(this._matchSegment(n,this.path[t],r)){e=t-1,s--,i=!0;break}}if(!i)return!1}else{const t=e===this.path.length-1;if(!this._matchSegment(n,this.path[e],t))return!1;e--,s--}}return s<0}_matchSegment(t,e,s){if("*"!==t.tag&&t.tag!==e.tag)return!1;if(void 0!==t.namespace&&"*"!==t.namespace&&t.namespace!==e.namespace)return!1;if(void 0!==t.attrName){if(!s)return!1;if(!e.values||!(t.attrName in e.values))return!1;if(void 0!==t.attrValue){const s=e.values[t.attrName];if(String(s)!==String(t.attrValue))return!1}}if(void 0!==t.position){if(!s)return!1;const n=e.counter??0;if("first"===t.position&&0!==n)return!1;if("odd"===t.position&&n%2!=1)return!1;if("even"===t.position&&n%2!=0)return!1;if("nth"===t.position&&n!==t.positionValue)return!1}return!0}matchesAny(t){return t.matchesAny(this)}snapshot(){return{path:this.path.map(t=>({...t})),siblingStacks:this.siblingStacks.map(t=>new Map(t))}}restore(t){this._pathStringCache=null,this._frozenPathCache=null,this._frozenSiblingsCache=null,this.path=t.path.map(t=>({...t})),this.siblingStacks=t.siblingStacks.map(t=>new Map(t))}readOnly(){return new Proxy(this,{get(t,e,s){if(n.has(e))return()=>{throw new TypeError(`Cannot call '${e}' on a read-only Matcher. Obtain a writable instance to mutate state.`)};if("path"===e)return null===t._frozenPathCache&&(t._frozenPathCache=Object.freeze(t.path.map(t=>Object.freeze({...t})))),t._frozenPathCache;if("siblingStacks"===e)return null===t._frozenSiblingsCache&&(t._frozenSiblingsCache=Object.freeze(t.siblingStacks.map(t=>Object.freeze(new Map(t))))),t._frozenSiblingsCache;const i=Reflect.get(t,e,s);return"function"==typeof i?i.bind(t):i},set(t,e){throw new TypeError(`Cannot set property '${String(e)}' on a read-only Matcher.`)},deleteProperty(t,e){throw new TypeError(`Cannot delete property '${String(e)}' from a read-only Matcher.`)}})}}class r{constructor(){this._byDepthAndTag=new Map,this._wildcardByDepth=new Map,this._deepWildcards=[],this._patterns=new Set,this._sealed=!1}add(t){if(this._sealed)throw new TypeError("ExpressionSet is sealed. Create a new ExpressionSet to add more expressions.");if(this._patterns.has(t.pattern))return this;if(this._patterns.add(t.pattern),t.hasDeepWildcard())return this._deepWildcards.push(t),this;const e=t.length,s=t.segments[t.segments.length-1],n=s?.tag;if(n&&"*"!==n){const s=`${e}:${n}`;this._byDepthAndTag.has(s)||this._byDepthAndTag.set(s,[]),this._byDepthAndTag.get(s).push(t)}else this._wildcardByDepth.has(e)||this._wildcardByDepth.set(e,[]),this._wildcardByDepth.get(e).push(t);return this}addAll(t){for(const e of t)this.add(e);return this}has(t){return this._patterns.has(t.pattern)}get size(){return this._patterns.size}seal(){return this._sealed=!0,this}get isSealed(){return this._sealed}matchesAny(t){return null!==this.findMatch(t)}findMatch(t){const e=t.getDepth(),s=`${e}:${t.getCurrentTag()}`,n=this._byDepthAndTag.get(s);if(n)for(let e=0;e<n.length;e++)if(t.matches(n[e]))return n[e];const i=this._wildcardByDepth.get(e);if(i)for(let e=0;e<i.length;e++)if(t.matches(i[e]))return i[e];for(let e=0;e<this._deepWildcards.length;e++)if(t.matches(this._deepWildcards[e]))return this._deepWildcards[e];return null}}const a={Expression:s,Matcher:i,ExpressionSet:r};return e})());
|
|
2
2
|
//# sourceMappingURL=pem.min.js.map
|
package/lib/pem.min.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"./lib/pem.min.js","mappings":"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","sources":["webpack://pem/webpack/universalModuleDefinition","webpack://pem/webpack/bootstrap","webpack://pem/webpack/runtime/define property getters","webpack://pem/webpack/runtime/hasOwnProperty shorthand","webpack://pem/webpack/runtime/make namespace object","webpack://pem/./src/Expression.js","webpack://pem/./src/Matcher.js","webpack://pem/./src/index.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"pem\"] = factory();\n\telse\n\t\troot[\"pem\"] = factory();\n})(this, () => {\nreturn ","// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","/**\n * Expression - Parses and stores a tag pattern expression\n * \n * Patterns are parsed once and stored in an optimized structure for fast matching.\n * \n * @example\n * const expr = new Expression(\"root.users.user\");\n * const expr2 = new Expression(\"..user[id]:first\");\n * const expr3 = new Expression(\"root/users/user\", { separator: '/' });\n */\nexport default class Expression {\n /**\n * Create a new Expression\n * @param {string} pattern - Pattern string (e.g., \"root.users.user\", \"..user[id]\")\n * @param {Object} options - Configuration options\n * @param {string} options.separator - Path separator (default: '.')\n */\n constructor(pattern, options = {}) {\n this.pattern = pattern;\n this.separator = options.separator || '.';\n this.segments = this._parse(pattern);\n\n // Cache expensive checks for performance (O(1) instead of O(n))\n this._hasDeepWildcard = this.segments.some(seg => seg.type === 'deep-wildcard');\n this._hasAttributeCondition = this.segments.some(seg => seg.attrName !== undefined);\n this._hasPositionSelector = this.segments.some(seg => seg.position !== undefined);\n }\n\n /**\n * Parse pattern string into segments\n * @private\n * @param {string} pattern - Pattern to parse\n * @returns {Array} Array of segment objects\n */\n _parse(pattern) {\n const segments = [];\n\n // Split by separator but handle \"..\" specially\n let i = 0;\n let currentPart = '';\n\n while (i < pattern.length) {\n if (pattern[i] === this.separator) {\n // Check if next char is also separator (deep wildcard)\n if (i + 1 < pattern.length && pattern[i + 1] === this.separator) {\n // Flush current part if any\n if (currentPart.trim()) {\n segments.push(this._parseSegment(currentPart.trim()));\n currentPart = '';\n }\n // Add deep wildcard\n segments.push({ type: 'deep-wildcard' });\n i += 2; // Skip both separators\n } else {\n // Regular separator\n if (currentPart.trim()) {\n segments.push(this._parseSegment(currentPart.trim()));\n }\n currentPart = '';\n i++;\n }\n } else {\n currentPart += pattern[i];\n i++;\n }\n }\n\n // Flush remaining part\n if (currentPart.trim()) {\n segments.push(this._parseSegment(currentPart.trim()));\n }\n\n return segments;\n }\n\n /**\n * Parse a single segment\n * @private\n * @param {string} part - Segment string (e.g., \"user\", \"ns::user\", \"user[id]\", \"ns::user:first\")\n * @returns {Object} Segment object\n */\n _parseSegment(part) {\n const segment = { type: 'tag' };\n\n // NEW NAMESPACE SYNTAX (v2.0):\n // ============================\n // Namespace uses DOUBLE colon (::)\n // Position uses SINGLE colon (:)\n // \n // Examples:\n // \"user\" → tag\n // \"user:first\" → tag + position\n // \"user[id]\" → tag + attribute\n // \"user[id]:first\" → tag + attribute + position\n // \"ns::user\" → namespace + tag\n // \"ns::user:first\" → namespace + tag + position\n // \"ns::user[id]\" → namespace + tag + attribute\n // \"ns::user[id]:first\" → namespace + tag + attribute + position\n // \"ns::first\" → namespace + tag named \"first\" (NO ambiguity!)\n //\n // This eliminates all ambiguity:\n // :: = namespace separator\n // : = position selector\n // [] = attributes\n\n // Step 1: Extract brackets [attr] or [attr=value]\n let bracketContent = null;\n let withoutBrackets = part;\n\n const bracketMatch = part.match(/^([^\\[]+)(\\[[^\\]]*\\])(.*)$/);\n if (bracketMatch) {\n withoutBrackets = bracketMatch[1] + bracketMatch[3];\n if (bracketMatch[2]) {\n const content = bracketMatch[2].slice(1, -1);\n if (content) {\n bracketContent = content;\n }\n }\n }\n\n // Step 2: Check for namespace (double colon ::)\n let namespace = undefined;\n let tagAndPosition = withoutBrackets;\n\n if (withoutBrackets.includes('::')) {\n const nsIndex = withoutBrackets.indexOf('::');\n namespace = withoutBrackets.substring(0, nsIndex).trim();\n tagAndPosition = withoutBrackets.substring(nsIndex + 2).trim(); // Skip ::\n\n if (!namespace) {\n throw new Error(`Invalid namespace in pattern: ${part}`);\n }\n }\n\n // Step 3: Parse tag and position (single colon :)\n let tag = undefined;\n let positionMatch = null;\n\n if (tagAndPosition.includes(':')) {\n const colonIndex = tagAndPosition.lastIndexOf(':'); // Use last colon for position\n const tagPart = tagAndPosition.substring(0, colonIndex).trim();\n const posPart = tagAndPosition.substring(colonIndex + 1).trim();\n\n // Verify position is a valid keyword\n const isPositionKeyword = ['first', 'last', 'odd', 'even'].includes(posPart) ||\n /^nth\\(\\d+\\)$/.test(posPart);\n\n if (isPositionKeyword) {\n tag = tagPart;\n positionMatch = posPart;\n } else {\n // Not a valid position keyword, treat whole thing as tag\n tag = tagAndPosition;\n }\n } else {\n tag = tagAndPosition;\n }\n\n if (!tag) {\n throw new Error(`Invalid segment pattern: ${part}`);\n }\n\n segment.tag = tag;\n if (namespace) {\n segment.namespace = namespace;\n }\n\n // Step 4: Parse attributes\n if (bracketContent) {\n if (bracketContent.includes('=')) {\n const eqIndex = bracketContent.indexOf('=');\n segment.attrName = bracketContent.substring(0, eqIndex).trim();\n segment.attrValue = bracketContent.substring(eqIndex + 1).trim();\n } else {\n segment.attrName = bracketContent.trim();\n }\n }\n\n // Step 5: Parse position selector\n if (positionMatch) {\n const nthMatch = positionMatch.match(/^nth\\((\\d+)\\)$/);\n if (nthMatch) {\n segment.position = 'nth';\n segment.positionValue = parseInt(nthMatch[1], 10);\n } else {\n segment.position = positionMatch;\n }\n }\n\n return segment;\n }\n\n /**\n * Get the number of segments\n * @returns {number}\n */\n get length() {\n return this.segments.length;\n }\n\n /**\n * Check if expression contains deep wildcard\n * @returns {boolean}\n */\n hasDeepWildcard() {\n return this._hasDeepWildcard;\n }\n\n /**\n * Check if expression has attribute conditions\n * @returns {boolean}\n */\n hasAttributeCondition() {\n return this._hasAttributeCondition;\n }\n\n /**\n * Check if expression has position selectors\n * @returns {boolean}\n */\n hasPositionSelector() {\n return this._hasPositionSelector;\n }\n\n /**\n * Get string representation\n * @returns {string}\n */\n toString() {\n return this.pattern;\n }\n}","/**\n * Matcher - Tracks current path in XML/JSON tree and matches against Expressions\n * \n * The matcher maintains a stack of nodes representing the current path from root to\n * current tag. It only stores attribute values for the current (top) node to minimize\n * memory usage. Sibling tracking is used to auto-calculate position and counter.\n * \n * @example\n * const matcher = new Matcher();\n * matcher.push(\"root\", {});\n * matcher.push(\"users\", {});\n * matcher.push(\"user\", { id: \"123\", type: \"admin\" });\n * \n * const expr = new Expression(\"root.users.user\");\n * matcher.matches(expr); // true\n */\n\n/**\n * Names of methods that mutate Matcher state.\n * Any attempt to call these on a read-only view throws a TypeError.\n * @type {Set<string>}\n */\nconst MUTATING_METHODS = new Set(['push', 'pop', 'reset', 'updateCurrent', 'restore']);\n\nexport default class Matcher {\n /**\n * Create a new Matcher\n * @param {Object} options - Configuration options\n * @param {string} options.separator - Default path separator (default: '.')\n */\n constructor(options = {}) {\n this.separator = options.separator || '.';\n this.path = [];\n this.siblingStacks = [];\n // Each path node: { tag: string, values: object, position: number, counter: number }\n // values only present for current (last) node\n // Each siblingStacks entry: Map<tagName, count> tracking occurrences at each level\n }\n\n /**\n * Push a new tag onto the path\n * @param {string} tagName - Name of the tag\n * @param {Object} attrValues - Attribute key-value pairs for current node (optional)\n * @param {string} namespace - Namespace for the tag (optional)\n */\n push(tagName, attrValues = null, namespace = null) {\n this._pathStringCache = null; // invalidate\n // Remove values from previous current node (now becoming ancestor)\n if (this.path.length > 0) {\n const prev = this.path[this.path.length - 1];\n prev.values = undefined;\n }\n\n // Get or create sibling tracking for current level\n const currentLevel = this.path.length;\n if (!this.siblingStacks[currentLevel]) {\n this.siblingStacks[currentLevel] = new Map();\n }\n\n const siblings = this.siblingStacks[currentLevel];\n\n // Create a unique key for sibling tracking that includes namespace\n const siblingKey = namespace ? `${namespace}:${tagName}` : tagName;\n\n // Calculate counter (how many times this tag appeared at this level)\n const counter = siblings.get(siblingKey) || 0;\n\n // Calculate position (total children at this level so far)\n let position = 0;\n for (const count of siblings.values()) {\n position += count;\n }\n\n // Update sibling count for this tag\n siblings.set(siblingKey, counter + 1);\n\n // Create new node\n const node = {\n tag: tagName,\n position: position,\n counter: counter\n };\n\n // Store namespace if provided\n if (namespace !== null && namespace !== undefined) {\n node.namespace = namespace;\n }\n\n // Store values only for current node\n if (attrValues !== null && attrValues !== undefined) {\n node.values = attrValues;\n }\n\n this.path.push(node);\n }\n\n /**\n * Pop the last tag from the path\n * @returns {Object|undefined} The popped node\n */\n pop() {\n if (this.path.length === 0) {\n return undefined;\n }\n this._pathStringCache = null; // invalidate\n const node = this.path.pop();\n\n // Clean up sibling tracking for levels deeper than current\n // After pop, path.length is the new depth\n // We need to clean up siblingStacks[path.length + 1] and beyond\n if (this.siblingStacks.length > this.path.length + 1) {\n this.siblingStacks.length = this.path.length + 1;\n }\n\n return node;\n }\n\n /**\n * Update current node's attribute values\n * Useful when attributes are parsed after push\n * @param {Object} attrValues - Attribute values\n */\n updateCurrent(attrValues) {\n if (this.path.length > 0) {\n const current = this.path[this.path.length - 1];\n if (attrValues !== null && attrValues !== undefined) {\n current.values = attrValues;\n }\n }\n }\n\n /**\n * Get current tag name\n * @returns {string|undefined}\n */\n getCurrentTag() {\n return this.path.length > 0 ? this.path[this.path.length - 1].tag : undefined;\n }\n\n /**\n * Get current namespace\n * @returns {string|undefined}\n */\n getCurrentNamespace() {\n return this.path.length > 0 ? this.path[this.path.length - 1].namespace : undefined;\n }\n\n /**\n * Get current node's attribute value\n * @param {string} attrName - Attribute name\n * @returns {*} Attribute value or undefined\n */\n getAttrValue(attrName) {\n if (this.path.length === 0) return undefined;\n const current = this.path[this.path.length - 1];\n return current.values?.[attrName];\n }\n\n /**\n * Check if current node has an attribute\n * @param {string} attrName - Attribute name\n * @returns {boolean}\n */\n hasAttr(attrName) {\n if (this.path.length === 0) return false;\n const current = this.path[this.path.length - 1];\n return current.values !== undefined && attrName in current.values;\n }\n\n /**\n * Get current node's sibling position (child index in parent)\n * @returns {number}\n */\n getPosition() {\n if (this.path.length === 0) return -1;\n return this.path[this.path.length - 1].position ?? 0;\n }\n\n /**\n * Get current node's repeat counter (occurrence count of this tag name)\n * @returns {number}\n */\n getCounter() {\n if (this.path.length === 0) return -1;\n return this.path[this.path.length - 1].counter ?? 0;\n }\n\n /**\n * Get current node's sibling index (alias for getPosition for backward compatibility)\n * @returns {number}\n * @deprecated Use getPosition() or getCounter() instead\n */\n getIndex() {\n return this.getPosition();\n }\n\n /**\n * Get current path depth\n * @returns {number}\n */\n getDepth() {\n return this.path.length;\n }\n\n /**\n * Get path as string\n * @param {string} separator - Optional separator (uses default if not provided)\n * @param {boolean} includeNamespace - Whether to include namespace in output (default: true)\n * @returns {string}\n */\n toString(separator, includeNamespace = true) {\n const sep = separator || this.separator;\n const isDefault = (sep === this.separator && includeNamespace === true);\n\n if (isDefault) {\n if (this._pathStringCache !== null && this._pathStringCache !== undefined) {\n return this._pathStringCache;\n }\n const result = this.path.map(n =>\n (includeNamespace && n.namespace) ? `${n.namespace}:${n.tag}` : n.tag\n ).join(sep);\n this._pathStringCache = result;\n return result;\n }\n\n // Non-default separator or includeNamespace=false: don't cache (rare case)\n return this.path.map(n =>\n (includeNamespace && n.namespace) ? `${n.namespace}:${n.tag}` : n.tag\n ).join(sep);\n }\n\n /**\n * Get path as array of tag names\n * @returns {string[]}\n */\n toArray() {\n return this.path.map(n => n.tag);\n }\n\n /**\n * Reset the path to empty\n */\n reset() {\n this._pathStringCache = null; // invalidate\n this.path = [];\n this.siblingStacks = [];\n }\n\n /**\n * Match current path against an Expression\n * @param {Expression} expression - The expression to match against\n * @returns {boolean} True if current path matches the expression\n */\n matches(expression) {\n const segments = expression.segments;\n\n if (segments.length === 0) {\n return false;\n }\n\n // Handle deep wildcard patterns\n if (expression.hasDeepWildcard()) {\n return this._matchWithDeepWildcard(segments);\n }\n\n // Simple path matching (no deep wildcards)\n return this._matchSimple(segments);\n }\n\n /**\n * Match simple path (no deep wildcards)\n * @private\n */\n _matchSimple(segments) {\n // Path must be same length as segments\n if (this.path.length !== segments.length) {\n return false;\n }\n\n // Match each segment bottom-to-top\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n const node = this.path[i];\n const isCurrentNode = (i === this.path.length - 1);\n\n if (!this._matchSegment(segment, node, isCurrentNode)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Match path with deep wildcards\n * @private\n */\n _matchWithDeepWildcard(segments) {\n let pathIdx = this.path.length - 1; // Start from current node (bottom)\n let segIdx = segments.length - 1; // Start from last segment\n\n while (segIdx >= 0 && pathIdx >= 0) {\n const segment = segments[segIdx];\n\n if (segment.type === 'deep-wildcard') {\n // \"..\" matches zero or more levels\n segIdx--;\n\n if (segIdx < 0) {\n // Pattern ends with \"..\", always matches\n return true;\n }\n\n // Find where next segment matches in the path\n const nextSeg = segments[segIdx];\n let found = false;\n\n for (let i = pathIdx; i >= 0; i--) {\n const isCurrentNode = (i === this.path.length - 1);\n if (this._matchSegment(nextSeg, this.path[i], isCurrentNode)) {\n pathIdx = i - 1;\n segIdx--;\n found = true;\n break;\n }\n }\n\n if (!found) {\n return false;\n }\n } else {\n // Regular segment\n const isCurrentNode = (pathIdx === this.path.length - 1);\n if (!this._matchSegment(segment, this.path[pathIdx], isCurrentNode)) {\n return false;\n }\n pathIdx--;\n segIdx--;\n }\n }\n\n // All segments must be consumed\n return segIdx < 0;\n }\n\n /**\n * Match a single segment against a node\n * @private\n * @param {Object} segment - Segment from Expression\n * @param {Object} node - Node from path\n * @param {boolean} isCurrentNode - Whether this is the current (last) node\n * @returns {boolean}\n */\n _matchSegment(segment, node, isCurrentNode) {\n // Match tag name (* is wildcard)\n if (segment.tag !== '*' && segment.tag !== node.tag) {\n return false;\n }\n\n // Match namespace if specified in segment\n if (segment.namespace !== undefined) {\n // Segment has namespace - node must match it\n if (segment.namespace !== '*' && segment.namespace !== node.namespace) {\n return false;\n }\n }\n // If segment has no namespace, it matches nodes with or without namespace\n\n // Match attribute name (check if node has this attribute)\n // Can only check for current node since ancestors don't have values\n if (segment.attrName !== undefined) {\n if (!isCurrentNode) {\n // Can't check attributes for ancestor nodes (values not stored)\n return false;\n }\n\n if (!node.values || !(segment.attrName in node.values)) {\n return false;\n }\n\n // Match attribute value (only possible for current node)\n if (segment.attrValue !== undefined) {\n const actualValue = node.values[segment.attrName];\n // Both should be strings\n if (String(actualValue) !== String(segment.attrValue)) {\n return false;\n }\n }\n }\n\n // Match position (only for current node)\n if (segment.position !== undefined) {\n if (!isCurrentNode) {\n // Can't check position for ancestor nodes\n return false;\n }\n\n const counter = node.counter ?? 0;\n\n if (segment.position === 'first' && counter !== 0) {\n return false;\n } else if (segment.position === 'odd' && counter % 2 !== 1) {\n return false;\n } else if (segment.position === 'even' && counter % 2 !== 0) {\n return false;\n } else if (segment.position === 'nth') {\n if (counter !== segment.positionValue) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n /**\n * Create a snapshot of current state\n * @returns {Object} State snapshot\n */\n snapshot() {\n return {\n path: this.path.map(node => ({ ...node })),\n siblingStacks: this.siblingStacks.map(map => new Map(map))\n };\n }\n\n /**\n * Restore state from snapshot\n * @param {Object} snapshot - State snapshot\n */\n restore(snapshot) {\n this._pathStringCache = null; // invalidate\n this.path = snapshot.path.map(node => ({ ...node }));\n this.siblingStacks = snapshot.siblingStacks.map(map => new Map(map));\n }\n\n /**\n * Return a read-only view of this matcher.\n *\n * The returned object exposes all query/inspection methods but throws a\n * TypeError if any state-mutating method is called (`push`, `pop`, `reset`,\n * `updateCurrent`, `restore`). Property reads (e.g. `.path`, `.separator`)\n * are allowed but the returned arrays/objects are frozen so callers cannot\n * mutate internal state through them either.\n *\n * @returns {ReadOnlyMatcher} A proxy that forwards read operations and blocks writes.\n *\n * @example\n * const matcher = new Matcher();\n * matcher.push(\"root\", {});\n *\n * const ro = matcher.readOnly();\n * ro.matches(expr); // ✓ works\n * ro.getCurrentTag(); // ✓ works\n * ro.push(\"child\", {}); // ✗ throws TypeError\n * ro.reset(); // ✗ throws TypeError\n */\n readOnly() {\n const self = this;\n\n return new Proxy(self, {\n get(target, prop, receiver) {\n // Block mutating methods\n if (MUTATING_METHODS.has(prop)) {\n return () => {\n throw new TypeError(\n `Cannot call '${prop}' on a read-only Matcher. ` +\n `Obtain a writable instance to mutate state.`\n );\n };\n }\n\n const value = Reflect.get(target, prop, receiver);\n\n // Freeze array/object properties so callers can't mutate internal\n // state through direct property access (e.g. matcher.path.push(...))\n if (prop === 'path' || prop === 'siblingStacks') {\n return Object.freeze(\n Array.isArray(value)\n ? value.map(item =>\n item instanceof Map\n ? Object.freeze(new Map(item)) // freeze a copy of each Map\n : Object.freeze({ ...item }) // freeze a copy of each node\n )\n : value\n );\n }\n\n // Bind methods so `this` inside them still refers to the real Matcher\n if (typeof value === 'function') {\n return value.bind(target);\n }\n\n return value;\n },\n\n // Prevent any property assignment on the read-only view\n set(_target, prop) {\n throw new TypeError(\n `Cannot set property '${String(prop)}' on a read-only Matcher.`\n );\n },\n\n // Prevent property deletion\n deleteProperty(_target, prop) {\n throw new TypeError(\n `Cannot delete property '${String(prop)}' from a read-only Matcher.`\n );\n }\n });\n }\n}","/**\n * fast-xml-tagger - XML/JSON path matching library\n * \n * Provides efficient path tracking and pattern matching for XML/JSON parsers.\n * \n * @example\n * import { Expression, Matcher } from 'fast-xml-tagger';\n * \n * // Create expression (parse once)\n * const expr = new Expression(\"root.users.user[id]\");\n * \n * // Create matcher (track path)\n * const matcher = new Matcher();\n * matcher.push(\"root\", [], {}, 0);\n * matcher.push(\"users\", [], {}, 0);\n * matcher.push(\"user\", [\"id\", \"type\"], { id: \"123\", type: \"admin\" }, 0);\n * \n * // Match\n * if (matcher.matches(expr)) {\n * console.log(\"Match found!\");\n * }\n */\n\nimport Expression from './Expression.js';\nimport Matcher from './Matcher.js';\n\nexport { Expression, Matcher };\nexport default { Expression, Matcher };\n"],"names":["root","factory","exports","module","define","amd","this","__webpack_require__","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","Expression","constructor","pattern","options","separator","segments","_parse","_hasDeepWildcard","some","seg","type","_hasAttributeCondition","undefined","attrName","_hasPositionSelector","position","i","currentPart","length","trim","push","_parseSegment","part","segment","bracketContent","withoutBrackets","bracketMatch","match","content","slice","namespace","tag","tagAndPosition","includes","nsIndex","indexOf","substring","Error","positionMatch","colonIndex","lastIndexOf","tagPart","posPart","test","eqIndex","attrValue","nthMatch","positionValue","parseInt","hasDeepWildcard","hasAttributeCondition","hasPositionSelector","toString","MUTATING_METHODS","Set","Matcher","path","siblingStacks","tagName","attrValues","_pathStringCache","values","currentLevel","Map","siblings","siblingKey","counter","count","set","node","pop","updateCurrent","current","getCurrentTag","getCurrentNamespace","getAttrValue","hasAttr","getPosition","getCounter","getIndex","getDepth","includeNamespace","sep","result","map","n","join","toArray","reset","matches","expression","_matchWithDeepWildcard","_matchSimple","isCurrentNode","_matchSegment","pathIdx","segIdx","nextSeg","found","actualValue","String","snapshot","restore","readOnly","Proxy","target","receiver","has","TypeError","Reflect","freeze","Array","isArray","item","bind","_target","deleteProperty"],"sourceRoot":""}
|
|
1
|
+
{"version":3,"file":"./lib/pem.min.js","mappings":"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","sources":["webpack://pem/webpack/universalModuleDefinition","webpack://pem/webpack/bootstrap","webpack://pem/webpack/runtime/define property getters","webpack://pem/webpack/runtime/hasOwnProperty shorthand","webpack://pem/webpack/runtime/make namespace object","webpack://pem/./src/Expression.js","webpack://pem/./src/Matcher.js","webpack://pem/./src/ExpressionSet.js","webpack://pem/./src/index.js"],"sourcesContent":["(function webpackUniversalModuleDefinition(root, factory) {\n\tif(typeof exports === 'object' && typeof module === 'object')\n\t\tmodule.exports = factory();\n\telse if(typeof define === 'function' && define.amd)\n\t\tdefine([], factory);\n\telse if(typeof exports === 'object')\n\t\texports[\"pem\"] = factory();\n\telse\n\t\troot[\"pem\"] = factory();\n})(this, () => {\nreturn ","// The require scope\nvar __webpack_require__ = {};\n\n","// define getter functions for harmony exports\n__webpack_require__.d = (exports, definition) => {\n\tfor(var key in definition) {\n\t\tif(__webpack_require__.o(definition, key) && !__webpack_require__.o(exports, key)) {\n\t\t\tObject.defineProperty(exports, key, { enumerable: true, get: definition[key] });\n\t\t}\n\t}\n};","__webpack_require__.o = (obj, prop) => (Object.prototype.hasOwnProperty.call(obj, prop))","// define __esModule on exports\n__webpack_require__.r = (exports) => {\n\tif(typeof Symbol !== 'undefined' && Symbol.toStringTag) {\n\t\tObject.defineProperty(exports, Symbol.toStringTag, { value: 'Module' });\n\t}\n\tObject.defineProperty(exports, '__esModule', { value: true });\n};","/**\n * Expression - Parses and stores a tag pattern expression\n * \n * Patterns are parsed once and stored in an optimized structure for fast matching.\n * \n * @example\n * const expr = new Expression(\"root.users.user\");\n * const expr2 = new Expression(\"..user[id]:first\");\n * const expr3 = new Expression(\"root/users/user\", { separator: '/' });\n */\nexport default class Expression {\n /**\n * Create a new Expression\n * @param {string} pattern - Pattern string (e.g., \"root.users.user\", \"..user[id]\")\n * @param {Object} options - Configuration options\n * @param {string} options.separator - Path separator (default: '.')\n */\n constructor(pattern, options = {}, data) {\n this.pattern = pattern;\n this.separator = options.separator || '.';\n this.segments = this._parse(pattern);\n this.data = data;\n // Cache expensive checks for performance (O(1) instead of O(n))\n this._hasDeepWildcard = this.segments.some(seg => seg.type === 'deep-wildcard');\n this._hasAttributeCondition = this.segments.some(seg => seg.attrName !== undefined);\n this._hasPositionSelector = this.segments.some(seg => seg.position !== undefined);\n }\n\n /**\n * Parse pattern string into segments\n * @private\n * @param {string} pattern - Pattern to parse\n * @returns {Array} Array of segment objects\n */\n _parse(pattern) {\n const segments = [];\n\n // Split by separator but handle \"..\" specially\n let i = 0;\n let currentPart = '';\n\n while (i < pattern.length) {\n if (pattern[i] === this.separator) {\n // Check if next char is also separator (deep wildcard)\n if (i + 1 < pattern.length && pattern[i + 1] === this.separator) {\n // Flush current part if any\n if (currentPart.trim()) {\n segments.push(this._parseSegment(currentPart.trim()));\n currentPart = '';\n }\n // Add deep wildcard\n segments.push({ type: 'deep-wildcard' });\n i += 2; // Skip both separators\n } else {\n // Regular separator\n if (currentPart.trim()) {\n segments.push(this._parseSegment(currentPart.trim()));\n }\n currentPart = '';\n i++;\n }\n } else {\n currentPart += pattern[i];\n i++;\n }\n }\n\n // Flush remaining part\n if (currentPart.trim()) {\n segments.push(this._parseSegment(currentPart.trim()));\n }\n\n return segments;\n }\n\n /**\n * Parse a single segment\n * @private\n * @param {string} part - Segment string (e.g., \"user\", \"ns::user\", \"user[id]\", \"ns::user:first\")\n * @returns {Object} Segment object\n */\n _parseSegment(part) {\n const segment = { type: 'tag' };\n\n // NEW NAMESPACE SYNTAX (v2.0):\n // ============================\n // Namespace uses DOUBLE colon (::)\n // Position uses SINGLE colon (:)\n // \n // Examples:\n // \"user\" → tag\n // \"user:first\" → tag + position\n // \"user[id]\" → tag + attribute\n // \"user[id]:first\" → tag + attribute + position\n // \"ns::user\" → namespace + tag\n // \"ns::user:first\" → namespace + tag + position\n // \"ns::user[id]\" → namespace + tag + attribute\n // \"ns::user[id]:first\" → namespace + tag + attribute + position\n // \"ns::first\" → namespace + tag named \"first\" (NO ambiguity!)\n //\n // This eliminates all ambiguity:\n // :: = namespace separator\n // : = position selector\n // [] = attributes\n\n // Step 1: Extract brackets [attr] or [attr=value]\n let bracketContent = null;\n let withoutBrackets = part;\n\n const bracketMatch = part.match(/^([^\\[]+)(\\[[^\\]]*\\])(.*)$/);\n if (bracketMatch) {\n withoutBrackets = bracketMatch[1] + bracketMatch[3];\n if (bracketMatch[2]) {\n const content = bracketMatch[2].slice(1, -1);\n if (content) {\n bracketContent = content;\n }\n }\n }\n\n // Step 2: Check for namespace (double colon ::)\n let namespace = undefined;\n let tagAndPosition = withoutBrackets;\n\n if (withoutBrackets.includes('::')) {\n const nsIndex = withoutBrackets.indexOf('::');\n namespace = withoutBrackets.substring(0, nsIndex).trim();\n tagAndPosition = withoutBrackets.substring(nsIndex + 2).trim(); // Skip ::\n\n if (!namespace) {\n throw new Error(`Invalid namespace in pattern: ${part}`);\n }\n }\n\n // Step 3: Parse tag and position (single colon :)\n let tag = undefined;\n let positionMatch = null;\n\n if (tagAndPosition.includes(':')) {\n const colonIndex = tagAndPosition.lastIndexOf(':'); // Use last colon for position\n const tagPart = tagAndPosition.substring(0, colonIndex).trim();\n const posPart = tagAndPosition.substring(colonIndex + 1).trim();\n\n // Verify position is a valid keyword\n const isPositionKeyword = ['first', 'last', 'odd', 'even'].includes(posPart) ||\n /^nth\\(\\d+\\)$/.test(posPart);\n\n if (isPositionKeyword) {\n tag = tagPart;\n positionMatch = posPart;\n } else {\n // Not a valid position keyword, treat whole thing as tag\n tag = tagAndPosition;\n }\n } else {\n tag = tagAndPosition;\n }\n\n if (!tag) {\n throw new Error(`Invalid segment pattern: ${part}`);\n }\n\n segment.tag = tag;\n if (namespace) {\n segment.namespace = namespace;\n }\n\n // Step 4: Parse attributes\n if (bracketContent) {\n if (bracketContent.includes('=')) {\n const eqIndex = bracketContent.indexOf('=');\n segment.attrName = bracketContent.substring(0, eqIndex).trim();\n segment.attrValue = bracketContent.substring(eqIndex + 1).trim();\n } else {\n segment.attrName = bracketContent.trim();\n }\n }\n\n // Step 5: Parse position selector\n if (positionMatch) {\n const nthMatch = positionMatch.match(/^nth\\((\\d+)\\)$/);\n if (nthMatch) {\n segment.position = 'nth';\n segment.positionValue = parseInt(nthMatch[1], 10);\n } else {\n segment.position = positionMatch;\n }\n }\n\n return segment;\n }\n\n /**\n * Get the number of segments\n * @returns {number}\n */\n get length() {\n return this.segments.length;\n }\n\n /**\n * Check if expression contains deep wildcard\n * @returns {boolean}\n */\n hasDeepWildcard() {\n return this._hasDeepWildcard;\n }\n\n /**\n * Check if expression has attribute conditions\n * @returns {boolean}\n */\n hasAttributeCondition() {\n return this._hasAttributeCondition;\n }\n\n /**\n * Check if expression has position selectors\n * @returns {boolean}\n */\n hasPositionSelector() {\n return this._hasPositionSelector;\n }\n\n /**\n * Get string representation\n * @returns {string}\n */\n toString() {\n return this.pattern;\n }\n}","import ExpressionSet from \"./ExpressionSet.js\";\n/**\n * Matcher - Tracks current path in XML/JSON tree and matches against Expressions\n * \n * The matcher maintains a stack of nodes representing the current path from root to\n * current tag. It only stores attribute values for the current (top) node to minimize\n * memory usage. Sibling tracking is used to auto-calculate position and counter.\n * \n * @example\n * const matcher = new Matcher();\n * matcher.push(\"root\", {});\n * matcher.push(\"users\", {});\n * matcher.push(\"user\", { id: \"123\", type: \"admin\" });\n * \n * const expr = new Expression(\"root.users.user\");\n * matcher.matches(expr); // true\n */\n\n/**\n * Names of methods that mutate Matcher state.\n * Any attempt to call these on a read-only view throws a TypeError.\n * @type {Set<string>}\n */\nconst MUTATING_METHODS = new Set(['push', 'pop', 'reset', 'updateCurrent', 'restore']);\n\nexport default class Matcher {\n /**\n * Create a new Matcher\n * @param {Object} options - Configuration options\n * @param {string} options.separator - Default path separator (default: '.')\n */\n constructor(options = {}) {\n this.separator = options.separator || '.';\n this.path = [];\n this.siblingStacks = [];\n // Each path node: { tag: string, values: object, position: number, counter: number }\n // values only present for current (last) node\n // Each siblingStacks entry: Map<tagName, count> tracking occurrences at each level\n this._pathStringCache = null;\n this._frozenPathCache = null; // cache for readOnly().path\n this._frozenSiblingsCache = null; // cache for readOnly().siblingStacks\n }\n\n /**\n * Push a new tag onto the path\n * @param {string} tagName - Name of the tag\n * @param {Object} attrValues - Attribute key-value pairs for current node (optional)\n * @param {string} namespace - Namespace for the tag (optional)\n */\n push(tagName, attrValues = null, namespace = null) {\n //invalidate cache\n this._pathStringCache = null;\n this._frozenPathCache = null;\n this._frozenSiblingsCache = null;\n // Remove values from previous current node (now becoming ancestor)\n if (this.path.length > 0) {\n const prev = this.path[this.path.length - 1];\n prev.values = undefined;\n }\n\n // Get or create sibling tracking for current level\n const currentLevel = this.path.length;\n if (!this.siblingStacks[currentLevel]) {\n this.siblingStacks[currentLevel] = new Map();\n }\n\n const siblings = this.siblingStacks[currentLevel];\n\n // Create a unique key for sibling tracking that includes namespace\n const siblingKey = namespace ? `${namespace}:${tagName}` : tagName;\n\n // Calculate counter (how many times this tag appeared at this level)\n const counter = siblings.get(siblingKey) || 0;\n\n // Calculate position (total children at this level so far)\n let position = 0;\n for (const count of siblings.values()) {\n position += count;\n }\n\n // Update sibling count for this tag\n siblings.set(siblingKey, counter + 1);\n\n // Create new node\n const node = {\n tag: tagName,\n position: position,\n counter: counter\n };\n\n // Store namespace if provided\n if (namespace !== null && namespace !== undefined) {\n node.namespace = namespace;\n }\n\n // Store values only for current node\n if (attrValues !== null && attrValues !== undefined) {\n node.values = attrValues;\n }\n\n this.path.push(node);\n }\n\n /**\n * Pop the last tag from the path\n * @returns {Object|undefined} The popped node\n */\n pop() {\n if (this.path.length === 0) return undefined;\n //invalidate cache\n this._pathStringCache = null;\n this._frozenPathCache = null;\n this._frozenSiblingsCache = null;\n const node = this.path.pop();\n\n // Clean up sibling tracking for levels deeper than current\n // After pop, path.length is the new depth\n // We need to clean up siblingStacks[path.length + 1] and beyond\n if (this.siblingStacks.length > this.path.length + 1) {\n this.siblingStacks.length = this.path.length + 1;\n }\n\n return node;\n }\n\n /**\n * Update current node's attribute values\n * Useful when attributes are parsed after push\n * @param {Object} attrValues - Attribute values\n */\n updateCurrent(attrValues) {\n if (this.path.length > 0) {\n const current = this.path[this.path.length - 1];\n if (attrValues !== null && attrValues !== undefined) {\n current.values = attrValues;\n this._frozenPathCache = null;\n }\n }\n }\n\n /**\n * Get current tag name\n * @returns {string|undefined}\n */\n getCurrentTag() {\n return this.path.length > 0 ? this.path[this.path.length - 1].tag : undefined;\n }\n\n /**\n * Get current namespace\n * @returns {string|undefined}\n */\n getCurrentNamespace() {\n return this.path.length > 0 ? this.path[this.path.length - 1].namespace : undefined;\n }\n\n /**\n * Get current node's attribute value\n * @param {string} attrName - Attribute name\n * @returns {*} Attribute value or undefined\n */\n getAttrValue(attrName) {\n if (this.path.length === 0) return undefined;\n const current = this.path[this.path.length - 1];\n return current.values?.[attrName];\n }\n\n /**\n * Check if current node has an attribute\n * @param {string} attrName - Attribute name\n * @returns {boolean}\n */\n hasAttr(attrName) {\n if (this.path.length === 0) return false;\n const current = this.path[this.path.length - 1];\n return current.values !== undefined && attrName in current.values;\n }\n\n /**\n * Get current node's sibling position (child index in parent)\n * @returns {number}\n */\n getPosition() {\n if (this.path.length === 0) return -1;\n return this.path[this.path.length - 1].position ?? 0;\n }\n\n /**\n * Get current node's repeat counter (occurrence count of this tag name)\n * @returns {number}\n */\n getCounter() {\n if (this.path.length === 0) return -1;\n return this.path[this.path.length - 1].counter ?? 0;\n }\n\n /**\n * Get current node's sibling index (alias for getPosition for backward compatibility)\n * @returns {number}\n * @deprecated Use getPosition() or getCounter() instead\n */\n getIndex() {\n return this.getPosition();\n }\n\n /**\n * Get current path depth\n * @returns {number}\n */\n getDepth() {\n return this.path.length;\n }\n\n /**\n * Get path as string\n * @param {string} separator - Optional separator (uses default if not provided)\n * @param {boolean} includeNamespace - Whether to include namespace in output (default: true)\n * @returns {string}\n */\n toString(separator, includeNamespace = true) {\n const sep = separator || this.separator;\n const isDefault = (sep === this.separator && includeNamespace === true);\n\n if (isDefault) {\n if (this._pathStringCache !== null && this._pathStringCache !== undefined) {\n return this._pathStringCache;\n }\n const result = this.path.map(n =>\n (includeNamespace && n.namespace) ? `${n.namespace}:${n.tag}` : n.tag\n ).join(sep);\n this._pathStringCache = result;\n return result;\n }\n\n // Non-default separator or includeNamespace=false: don't cache (rare case)\n return this.path.map(n =>\n (includeNamespace && n.namespace) ? `${n.namespace}:${n.tag}` : n.tag\n ).join(sep);\n }\n\n /**\n * Get path as array of tag names\n * @returns {string[]}\n */\n toArray() {\n return this.path.map(n => n.tag);\n }\n\n /**\n * Reset the path to empty\n */\n reset() {\n //invalidate cache\n this._pathStringCache = null;\n this._frozenPathCache = null;\n this._frozenSiblingsCache = null;\n this.path = [];\n this.siblingStacks = [];\n }\n\n /**\n * Match current path against an Expression\n * @param {Expression} expression - The expression to match against\n * @returns {boolean} True if current path matches the expression\n */\n matches(expression) {\n const segments = expression.segments;\n\n if (segments.length === 0) {\n return false;\n }\n\n // Handle deep wildcard patterns\n if (expression.hasDeepWildcard()) {\n return this._matchWithDeepWildcard(segments);\n }\n\n // Simple path matching (no deep wildcards)\n return this._matchSimple(segments);\n }\n\n /**\n * Match simple path (no deep wildcards)\n * @private\n */\n _matchSimple(segments) {\n // Path must be same length as segments\n if (this.path.length !== segments.length) {\n return false;\n }\n\n // Match each segment bottom-to-top\n for (let i = 0; i < segments.length; i++) {\n const segment = segments[i];\n const node = this.path[i];\n const isCurrentNode = (i === this.path.length - 1);\n\n if (!this._matchSegment(segment, node, isCurrentNode)) {\n return false;\n }\n }\n\n return true;\n }\n\n /**\n * Match path with deep wildcards\n * @private\n */\n _matchWithDeepWildcard(segments) {\n let pathIdx = this.path.length - 1; // Start from current node (bottom)\n let segIdx = segments.length - 1; // Start from last segment\n\n while (segIdx >= 0 && pathIdx >= 0) {\n const segment = segments[segIdx];\n\n if (segment.type === 'deep-wildcard') {\n // \"..\" matches zero or more levels\n segIdx--;\n\n if (segIdx < 0) {\n // Pattern ends with \"..\", always matches\n return true;\n }\n\n // Find where next segment matches in the path\n const nextSeg = segments[segIdx];\n let found = false;\n\n for (let i = pathIdx; i >= 0; i--) {\n const isCurrentNode = (i === this.path.length - 1);\n if (this._matchSegment(nextSeg, this.path[i], isCurrentNode)) {\n pathIdx = i - 1;\n segIdx--;\n found = true;\n break;\n }\n }\n\n if (!found) {\n return false;\n }\n } else {\n // Regular segment\n const isCurrentNode = (pathIdx === this.path.length - 1);\n if (!this._matchSegment(segment, this.path[pathIdx], isCurrentNode)) {\n return false;\n }\n pathIdx--;\n segIdx--;\n }\n }\n\n // All segments must be consumed\n return segIdx < 0;\n }\n\n /**\n * Match a single segment against a node\n * @private\n * @param {Object} segment - Segment from Expression\n * @param {Object} node - Node from path\n * @param {boolean} isCurrentNode - Whether this is the current (last) node\n * @returns {boolean}\n */\n _matchSegment(segment, node, isCurrentNode) {\n // Match tag name (* is wildcard)\n if (segment.tag !== '*' && segment.tag !== node.tag) {\n return false;\n }\n\n // Match namespace if specified in segment\n if (segment.namespace !== undefined) {\n // Segment has namespace - node must match it\n if (segment.namespace !== '*' && segment.namespace !== node.namespace) {\n return false;\n }\n }\n // If segment has no namespace, it matches nodes with or without namespace\n\n // Match attribute name (check if node has this attribute)\n // Can only check for current node since ancestors don't have values\n if (segment.attrName !== undefined) {\n if (!isCurrentNode) {\n // Can't check attributes for ancestor nodes (values not stored)\n return false;\n }\n\n if (!node.values || !(segment.attrName in node.values)) {\n return false;\n }\n\n // Match attribute value (only possible for current node)\n if (segment.attrValue !== undefined) {\n const actualValue = node.values[segment.attrName];\n // Both should be strings\n if (String(actualValue) !== String(segment.attrValue)) {\n return false;\n }\n }\n }\n\n // Match position (only for current node)\n if (segment.position !== undefined) {\n if (!isCurrentNode) {\n // Can't check position for ancestor nodes\n return false;\n }\n\n const counter = node.counter ?? 0;\n\n if (segment.position === 'first' && counter !== 0) {\n return false;\n } else if (segment.position === 'odd' && counter % 2 !== 1) {\n return false;\n } else if (segment.position === 'even' && counter % 2 !== 0) {\n return false;\n } else if (segment.position === 'nth') {\n if (counter !== segment.positionValue) {\n return false;\n }\n }\n }\n\n return true;\n }\n\n /**\n * Match any expression in the given set against the current path.\n * @param {ExpressionSet} exprSet - The set of expressions to match against.\n * @returns {boolean} - True if any expression in the set matches the current path, false otherwise.\n */\n matchesAny(exprSet) {\n return exprSet.matchesAny(this);\n }\n\n /**\n * Create a snapshot of current state\n * @returns {Object} State snapshot\n */\n snapshot() {\n return {\n path: this.path.map(node => ({ ...node })),\n siblingStacks: this.siblingStacks.map(map => new Map(map))\n };\n }\n\n /**\n * Restore state from snapshot\n * @param {Object} snapshot - State snapshot\n */\n restore(snapshot) {\n //invalidate cache\n this._pathStringCache = null;\n this._frozenPathCache = null;\n this._frozenSiblingsCache = null;\n this.path = snapshot.path.map(node => ({ ...node }));\n this.siblingStacks = snapshot.siblingStacks.map(map => new Map(map));\n }\n\n /**\n * Return a read-only view of this matcher.\n *\n * The returned object exposes all query/inspection methods but throws a\n * TypeError if any state-mutating method is called (`push`, `pop`, `reset`,\n * `updateCurrent`, `restore`). Property reads (e.g. `.path`, `.separator`)\n * are allowed but the returned arrays/objects are frozen so callers cannot\n * mutate internal state through them either.\n *\n * @returns {ReadOnlyMatcher} A proxy that forwards read operations and blocks writes.\n *\n * @example\n * const matcher = new Matcher();\n * matcher.push(\"root\", {});\n *\n * const ro = matcher.readOnly();\n * ro.matches(expr); // ✓ works\n * ro.getCurrentTag(); // ✓ works\n * ro.push(\"child\", {}); // ✗ throws TypeError\n * ro.reset(); // ✗ throws TypeError\n */\n readOnly() {\n const self = this;\n\n return new Proxy(self, {\n get(target, prop, receiver) {\n // Block mutating methods\n if (MUTATING_METHODS.has(prop)) {\n return () => {\n throw new TypeError(\n `Cannot call '${prop}' on a read-only Matcher. ` +\n `Obtain a writable instance to mutate state.`\n );\n };\n }\n\n // Return cached frozen copy of path — rebuilt only after push/pop/updateCurrent/reset/restore\n if (prop === 'path') {\n if (target._frozenPathCache === null) {\n target._frozenPathCache = Object.freeze(\n target.path.map(node => Object.freeze({ ...node }))\n );\n }\n return target._frozenPathCache;\n }\n\n // Return cached frozen copy of siblingStacks — rebuilt only after push/pop/reset/restore\n if (prop === 'siblingStacks') {\n if (target._frozenSiblingsCache === null) {\n target._frozenSiblingsCache = Object.freeze(\n target.siblingStacks.map(map => Object.freeze(new Map(map)))\n );\n }\n return target._frozenSiblingsCache;\n }\n\n const value = Reflect.get(target, prop, receiver);\n\n // Bind methods so `this` inside them still refers to the real Matcher\n if (typeof value === 'function') {\n return value.bind(target);\n }\n\n return value;\n },\n\n // Prevent any property assignment on the read-only view\n set(_target, prop) {\n throw new TypeError(\n `Cannot set property '${String(prop)}' on a read-only Matcher.`\n );\n },\n\n // Prevent property deletion\n deleteProperty(_target, prop) {\n throw new TypeError(\n `Cannot delete property '${String(prop)}' from a read-only Matcher.`\n );\n }\n });\n }\n}","/**\n * ExpressionSet - An indexed collection of Expressions for efficient bulk matching\n *\n * Instead of iterating all expressions on every tag, ExpressionSet pre-indexes\n * them at insertion time by depth and terminal tag name. At match time, only\n * the relevant bucket is evaluated — typically reducing checks from O(E) to O(1)\n * lookup plus O(small bucket) matches.\n *\n * Three buckets are maintained:\n * - `_byDepthAndTag` — exact depth + exact tag name (tightest, used first)\n * - `_wildcardByDepth` — exact depth + wildcard tag `*` (depth-matched only)\n * - `_deepWildcards` — expressions containing `..` (cannot be depth-indexed)\n *\n * @example\n * import { Expression, ExpressionSet } from 'fast-xml-tagger';\n *\n * // Build once at config time\n * const stopNodes = new ExpressionSet();\n * stopNodes.add(new Expression('root.users.user'));\n * stopNodes.add(new Expression('root.config.setting'));\n * stopNodes.add(new Expression('..script'));\n *\n * // Query on every tag — hot path\n * if (stopNodes.matchesAny(matcher)) { ... }\n */\nexport default class ExpressionSet {\n constructor() {\n /** @type {Map<string, import('./Expression.js').default[]>} depth:tag → expressions */\n this._byDepthAndTag = new Map();\n\n /** @type {Map<number, import('./Expression.js').default[]>} depth → wildcard-tag expressions */\n this._wildcardByDepth = new Map();\n\n /** @type {import('./Expression.js').default[]} expressions containing deep wildcard (..) */\n this._deepWildcards = [];\n\n /** @type {Set<string>} pattern strings already added — used for deduplication */\n this._patterns = new Set();\n\n /** @type {boolean} whether the set is sealed against further additions */\n this._sealed = false;\n }\n\n /**\n * Add an Expression to the set.\n * Duplicate patterns (same pattern string) are silently ignored.\n *\n * @param {import('./Expression.js').default} expression - A pre-constructed Expression instance\n * @returns {this} for chaining\n * @throws {TypeError} if called after seal()\n *\n * @example\n * set.add(new Expression('root.users.user'));\n * set.add(new Expression('..script'));\n */\n add(expression) {\n if (this._sealed) {\n throw new TypeError(\n 'ExpressionSet is sealed. Create a new ExpressionSet to add more expressions.'\n );\n }\n\n // Deduplicate by pattern string\n if (this._patterns.has(expression.pattern)) return this;\n this._patterns.add(expression.pattern);\n\n if (expression.hasDeepWildcard()) {\n this._deepWildcards.push(expression);\n return this;\n }\n\n const depth = expression.length;\n const lastSeg = expression.segments[expression.segments.length - 1];\n const tag = lastSeg?.tag;\n\n if (!tag || tag === '*') {\n // Can index by depth but not by tag\n if (!this._wildcardByDepth.has(depth)) this._wildcardByDepth.set(depth, []);\n this._wildcardByDepth.get(depth).push(expression);\n } else {\n // Tightest bucket: depth + tag\n const key = `${depth}:${tag}`;\n if (!this._byDepthAndTag.has(key)) this._byDepthAndTag.set(key, []);\n this._byDepthAndTag.get(key).push(expression);\n }\n\n return this;\n }\n\n /**\n * Add multiple expressions at once.\n *\n * @param {import('./Expression.js').default[]} expressions - Array of Expression instances\n * @returns {this} for chaining\n *\n * @example\n * set.addAll([\n * new Expression('root.users.user'),\n * new Expression('root.config.setting'),\n * ]);\n */\n addAll(expressions) {\n for (const expr of expressions) this.add(expr);\n return this;\n }\n\n /**\n * Check whether a pattern string is already present in the set.\n *\n * @param {import('./Expression.js').default} expression\n * @returns {boolean}\n */\n has(expression) {\n return this._patterns.has(expression.pattern);\n }\n\n /**\n * Number of expressions in the set.\n * @type {number}\n */\n get size() {\n return this._patterns.size;\n }\n\n /**\n * Seal the set against further modifications.\n * Useful to prevent accidental mutations after config is built.\n * Calling add() or addAll() on a sealed set throws a TypeError.\n *\n * @returns {this}\n */\n seal() {\n this._sealed = true;\n return this;\n }\n\n /**\n * Whether the set has been sealed.\n * @type {boolean}\n */\n get isSealed() {\n return this._sealed;\n }\n\n /**\n * Test whether the matcher's current path matches any expression in the set.\n *\n * Evaluation order (cheapest → most expensive):\n * 1. Exact depth + tag bucket — O(1) lookup, typically 0–2 expressions\n * 2. Depth-only wildcard bucket — O(1) lookup, rare\n * 3. Deep-wildcard list — always checked, but usually small\n *\n * @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view)\n * @returns {boolean} true if any expression matches the current path\n *\n * @example\n * if (stopNodes.matchesAny(matcher)) {\n * // handle stop node\n * }\n */\n matchesAny(matcher) {\n return this.findMatch(matcher) !== null;\n }\n /**\n * Find and return the first Expression that matches the matcher's current path.\n *\n * Uses the same evaluation order as matchesAny (cheapest → most expensive):\n * 1. Exact depth + tag bucket\n * 2. Depth-only wildcard bucket\n * 3. Deep-wildcard list\n *\n * @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view)\n * @returns {import('./Expression.js').default | null} the first matching Expression, or null\n *\n * @example\n * const expr = stopNodes.findMatch(matcher);\n * if (expr) {\n * // access expr.config, expr.pattern, etc.\n * }\n */\n findMatch(matcher) {\n const depth = matcher.getDepth();\n const tag = matcher.getCurrentTag();\n\n // 1. Tightest bucket — most expressions live here\n const exactKey = `${depth}:${tag}`;\n const exactBucket = this._byDepthAndTag.get(exactKey);\n if (exactBucket) {\n for (let i = 0; i < exactBucket.length; i++) {\n if (matcher.matches(exactBucket[i])) return exactBucket[i];\n }\n }\n\n // 2. Depth-matched wildcard-tag expressions\n const wildcardBucket = this._wildcardByDepth.get(depth);\n if (wildcardBucket) {\n for (let i = 0; i < wildcardBucket.length; i++) {\n if (matcher.matches(wildcardBucket[i])) return wildcardBucket[i];\n }\n }\n\n // 3. Deep wildcards — cannot be pre-filtered by depth or tag\n for (let i = 0; i < this._deepWildcards.length; i++) {\n if (matcher.matches(this._deepWildcards[i])) return this._deepWildcards[i];\n }\n\n return null;\n }\n}\n","/**\n * fast-xml-tagger - XML/JSON path matching library\n * \n * Provides efficient path tracking and pattern matching for XML/JSON parsers.\n * \n * @example\n * import { Expression, Matcher } from 'fast-xml-tagger';\n * \n * // Create expression (parse once)\n * const expr = new Expression(\"root.users.user[id]\");\n * \n * // Create matcher (track path)\n * const matcher = new Matcher();\n * matcher.push(\"root\", [], {}, 0);\n * matcher.push(\"users\", [], {}, 0);\n * matcher.push(\"user\", [\"id\", \"type\"], { id: \"123\", type: \"admin\" }, 0);\n * \n * // Match\n * if (matcher.matches(expr)) {\n * console.log(\"Match found!\");\n * }\n */\n\nimport Expression from './Expression.js';\nimport Matcher from './Matcher.js';\nimport ExpressionSet from './ExpressionSet.js';\n\nexport { Expression, Matcher, ExpressionSet };\nexport default { Expression, Matcher, ExpressionSet };\n"],"names":["root","factory","exports","module","define","amd","this","__webpack_require__","definition","key","o","Object","defineProperty","enumerable","get","obj","prop","prototype","hasOwnProperty","call","Symbol","toStringTag","value","Expression","constructor","pattern","options","data","separator","segments","_parse","_hasDeepWildcard","some","seg","type","_hasAttributeCondition","undefined","attrName","_hasPositionSelector","position","i","currentPart","length","trim","push","_parseSegment","part","segment","bracketContent","withoutBrackets","bracketMatch","match","content","slice","namespace","tag","tagAndPosition","includes","nsIndex","indexOf","substring","Error","positionMatch","colonIndex","lastIndexOf","tagPart","posPart","test","eqIndex","attrValue","nthMatch","positionValue","parseInt","hasDeepWildcard","hasAttributeCondition","hasPositionSelector","toString","MUTATING_METHODS","Set","Matcher","path","siblingStacks","_pathStringCache","_frozenPathCache","_frozenSiblingsCache","tagName","attrValues","values","currentLevel","Map","siblings","siblingKey","counter","count","set","node","pop","updateCurrent","current","getCurrentTag","getCurrentNamespace","getAttrValue","hasAttr","getPosition","getCounter","getIndex","getDepth","includeNamespace","sep","result","map","n","join","toArray","reset","matches","expression","_matchWithDeepWildcard","_matchSimple","isCurrentNode","_matchSegment","pathIdx","segIdx","nextSeg","found","actualValue","String","matchesAny","exprSet","snapshot","restore","readOnly","Proxy","target","receiver","has","TypeError","freeze","Reflect","bind","_target","deleteProperty","ExpressionSet","_byDepthAndTag","_wildcardByDepth","_deepWildcards","_patterns","_sealed","add","depth","lastSeg","addAll","expressions","expr","size","seal","isSealed","matcher","findMatch","exactKey","exactBucket","wildcardBucket"],"sourceRoot":""}
|
package/package.json
CHANGED
package/src/Expression.js
CHANGED
|
@@ -15,11 +15,11 @@ export default class Expression {
|
|
|
15
15
|
* @param {Object} options - Configuration options
|
|
16
16
|
* @param {string} options.separator - Path separator (default: '.')
|
|
17
17
|
*/
|
|
18
|
-
constructor(pattern, options = {}) {
|
|
18
|
+
constructor(pattern, options = {}, data) {
|
|
19
19
|
this.pattern = pattern;
|
|
20
20
|
this.separator = options.separator || '.';
|
|
21
21
|
this.segments = this._parse(pattern);
|
|
22
|
-
|
|
22
|
+
this.data = data;
|
|
23
23
|
// Cache expensive checks for performance (O(1) instead of O(n))
|
|
24
24
|
this._hasDeepWildcard = this.segments.some(seg => seg.type === 'deep-wildcard');
|
|
25
25
|
this._hasAttributeCondition = this.segments.some(seg => seg.attrName !== undefined);
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* ExpressionSet - An indexed collection of Expressions for efficient bulk matching
|
|
3
|
+
*
|
|
4
|
+
* Instead of iterating all expressions on every tag, ExpressionSet pre-indexes
|
|
5
|
+
* them at insertion time by depth and terminal tag name. At match time, only
|
|
6
|
+
* the relevant bucket is evaluated — typically reducing checks from O(E) to O(1)
|
|
7
|
+
* lookup plus O(small bucket) matches.
|
|
8
|
+
*
|
|
9
|
+
* Three buckets are maintained:
|
|
10
|
+
* - `_byDepthAndTag` — exact depth + exact tag name (tightest, used first)
|
|
11
|
+
* - `_wildcardByDepth` — exact depth + wildcard tag `*` (depth-matched only)
|
|
12
|
+
* - `_deepWildcards` — expressions containing `..` (cannot be depth-indexed)
|
|
13
|
+
*
|
|
14
|
+
* @example
|
|
15
|
+
* import { Expression, ExpressionSet } from 'fast-xml-tagger';
|
|
16
|
+
*
|
|
17
|
+
* // Build once at config time
|
|
18
|
+
* const stopNodes = new ExpressionSet();
|
|
19
|
+
* stopNodes.add(new Expression('root.users.user'));
|
|
20
|
+
* stopNodes.add(new Expression('root.config.setting'));
|
|
21
|
+
* stopNodes.add(new Expression('..script'));
|
|
22
|
+
*
|
|
23
|
+
* // Query on every tag — hot path
|
|
24
|
+
* if (stopNodes.matchesAny(matcher)) { ... }
|
|
25
|
+
*/
|
|
26
|
+
export default class ExpressionSet {
|
|
27
|
+
constructor() {
|
|
28
|
+
/** @type {Map<string, import('./Expression.js').default[]>} depth:tag → expressions */
|
|
29
|
+
this._byDepthAndTag = new Map();
|
|
30
|
+
|
|
31
|
+
/** @type {Map<number, import('./Expression.js').default[]>} depth → wildcard-tag expressions */
|
|
32
|
+
this._wildcardByDepth = new Map();
|
|
33
|
+
|
|
34
|
+
/** @type {import('./Expression.js').default[]} expressions containing deep wildcard (..) */
|
|
35
|
+
this._deepWildcards = [];
|
|
36
|
+
|
|
37
|
+
/** @type {Set<string>} pattern strings already added — used for deduplication */
|
|
38
|
+
this._patterns = new Set();
|
|
39
|
+
|
|
40
|
+
/** @type {boolean} whether the set is sealed against further additions */
|
|
41
|
+
this._sealed = false;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Add an Expression to the set.
|
|
46
|
+
* Duplicate patterns (same pattern string) are silently ignored.
|
|
47
|
+
*
|
|
48
|
+
* @param {import('./Expression.js').default} expression - A pre-constructed Expression instance
|
|
49
|
+
* @returns {this} for chaining
|
|
50
|
+
* @throws {TypeError} if called after seal()
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* set.add(new Expression('root.users.user'));
|
|
54
|
+
* set.add(new Expression('..script'));
|
|
55
|
+
*/
|
|
56
|
+
add(expression) {
|
|
57
|
+
if (this._sealed) {
|
|
58
|
+
throw new TypeError(
|
|
59
|
+
'ExpressionSet is sealed. Create a new ExpressionSet to add more expressions.'
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Deduplicate by pattern string
|
|
64
|
+
if (this._patterns.has(expression.pattern)) return this;
|
|
65
|
+
this._patterns.add(expression.pattern);
|
|
66
|
+
|
|
67
|
+
if (expression.hasDeepWildcard()) {
|
|
68
|
+
this._deepWildcards.push(expression);
|
|
69
|
+
return this;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const depth = expression.length;
|
|
73
|
+
const lastSeg = expression.segments[expression.segments.length - 1];
|
|
74
|
+
const tag = lastSeg?.tag;
|
|
75
|
+
|
|
76
|
+
if (!tag || tag === '*') {
|
|
77
|
+
// Can index by depth but not by tag
|
|
78
|
+
if (!this._wildcardByDepth.has(depth)) this._wildcardByDepth.set(depth, []);
|
|
79
|
+
this._wildcardByDepth.get(depth).push(expression);
|
|
80
|
+
} else {
|
|
81
|
+
// Tightest bucket: depth + tag
|
|
82
|
+
const key = `${depth}:${tag}`;
|
|
83
|
+
if (!this._byDepthAndTag.has(key)) this._byDepthAndTag.set(key, []);
|
|
84
|
+
this._byDepthAndTag.get(key).push(expression);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return this;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Add multiple expressions at once.
|
|
92
|
+
*
|
|
93
|
+
* @param {import('./Expression.js').default[]} expressions - Array of Expression instances
|
|
94
|
+
* @returns {this} for chaining
|
|
95
|
+
*
|
|
96
|
+
* @example
|
|
97
|
+
* set.addAll([
|
|
98
|
+
* new Expression('root.users.user'),
|
|
99
|
+
* new Expression('root.config.setting'),
|
|
100
|
+
* ]);
|
|
101
|
+
*/
|
|
102
|
+
addAll(expressions) {
|
|
103
|
+
for (const expr of expressions) this.add(expr);
|
|
104
|
+
return this;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Check whether a pattern string is already present in the set.
|
|
109
|
+
*
|
|
110
|
+
* @param {import('./Expression.js').default} expression
|
|
111
|
+
* @returns {boolean}
|
|
112
|
+
*/
|
|
113
|
+
has(expression) {
|
|
114
|
+
return this._patterns.has(expression.pattern);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
/**
|
|
118
|
+
* Number of expressions in the set.
|
|
119
|
+
* @type {number}
|
|
120
|
+
*/
|
|
121
|
+
get size() {
|
|
122
|
+
return this._patterns.size;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
/**
|
|
126
|
+
* Seal the set against further modifications.
|
|
127
|
+
* Useful to prevent accidental mutations after config is built.
|
|
128
|
+
* Calling add() or addAll() on a sealed set throws a TypeError.
|
|
129
|
+
*
|
|
130
|
+
* @returns {this}
|
|
131
|
+
*/
|
|
132
|
+
seal() {
|
|
133
|
+
this._sealed = true;
|
|
134
|
+
return this;
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* Whether the set has been sealed.
|
|
139
|
+
* @type {boolean}
|
|
140
|
+
*/
|
|
141
|
+
get isSealed() {
|
|
142
|
+
return this._sealed;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Test whether the matcher's current path matches any expression in the set.
|
|
147
|
+
*
|
|
148
|
+
* Evaluation order (cheapest → most expensive):
|
|
149
|
+
* 1. Exact depth + tag bucket — O(1) lookup, typically 0–2 expressions
|
|
150
|
+
* 2. Depth-only wildcard bucket — O(1) lookup, rare
|
|
151
|
+
* 3. Deep-wildcard list — always checked, but usually small
|
|
152
|
+
*
|
|
153
|
+
* @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view)
|
|
154
|
+
* @returns {boolean} true if any expression matches the current path
|
|
155
|
+
*
|
|
156
|
+
* @example
|
|
157
|
+
* if (stopNodes.matchesAny(matcher)) {
|
|
158
|
+
* // handle stop node
|
|
159
|
+
* }
|
|
160
|
+
*/
|
|
161
|
+
matchesAny(matcher) {
|
|
162
|
+
return this.findMatch(matcher) !== null;
|
|
163
|
+
}
|
|
164
|
+
/**
|
|
165
|
+
* Find and return the first Expression that matches the matcher's current path.
|
|
166
|
+
*
|
|
167
|
+
* Uses the same evaluation order as matchesAny (cheapest → most expensive):
|
|
168
|
+
* 1. Exact depth + tag bucket
|
|
169
|
+
* 2. Depth-only wildcard bucket
|
|
170
|
+
* 3. Deep-wildcard list
|
|
171
|
+
*
|
|
172
|
+
* @param {import('./Matcher.js').default} matcher - Matcher instance (or readOnly view)
|
|
173
|
+
* @returns {import('./Expression.js').default | null} the first matching Expression, or null
|
|
174
|
+
*
|
|
175
|
+
* @example
|
|
176
|
+
* const expr = stopNodes.findMatch(matcher);
|
|
177
|
+
* if (expr) {
|
|
178
|
+
* // access expr.config, expr.pattern, etc.
|
|
179
|
+
* }
|
|
180
|
+
*/
|
|
181
|
+
findMatch(matcher) {
|
|
182
|
+
const depth = matcher.getDepth();
|
|
183
|
+
const tag = matcher.getCurrentTag();
|
|
184
|
+
|
|
185
|
+
// 1. Tightest bucket — most expressions live here
|
|
186
|
+
const exactKey = `${depth}:${tag}`;
|
|
187
|
+
const exactBucket = this._byDepthAndTag.get(exactKey);
|
|
188
|
+
if (exactBucket) {
|
|
189
|
+
for (let i = 0; i < exactBucket.length; i++) {
|
|
190
|
+
if (matcher.matches(exactBucket[i])) return exactBucket[i];
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
// 2. Depth-matched wildcard-tag expressions
|
|
195
|
+
const wildcardBucket = this._wildcardByDepth.get(depth);
|
|
196
|
+
if (wildcardBucket) {
|
|
197
|
+
for (let i = 0; i < wildcardBucket.length; i++) {
|
|
198
|
+
if (matcher.matches(wildcardBucket[i])) return wildcardBucket[i];
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
// 3. Deep wildcards — cannot be pre-filtered by depth or tag
|
|
203
|
+
for (let i = 0; i < this._deepWildcards.length; i++) {
|
|
204
|
+
if (matcher.matches(this._deepWildcards[i])) return this._deepWildcards[i];
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
return null;
|
|
208
|
+
}
|
|
209
|
+
}
|
package/src/Matcher.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import ExpressionSet from "./ExpressionSet.js";
|
|
1
2
|
/**
|
|
2
3
|
* Matcher - Tracks current path in XML/JSON tree and matches against Expressions
|
|
3
4
|
*
|
|
@@ -35,6 +36,9 @@ export default class Matcher {
|
|
|
35
36
|
// Each path node: { tag: string, values: object, position: number, counter: number }
|
|
36
37
|
// values only present for current (last) node
|
|
37
38
|
// Each siblingStacks entry: Map<tagName, count> tracking occurrences at each level
|
|
39
|
+
this._pathStringCache = null;
|
|
40
|
+
this._frozenPathCache = null; // cache for readOnly().path
|
|
41
|
+
this._frozenSiblingsCache = null; // cache for readOnly().siblingStacks
|
|
38
42
|
}
|
|
39
43
|
|
|
40
44
|
/**
|
|
@@ -44,7 +48,10 @@ export default class Matcher {
|
|
|
44
48
|
* @param {string} namespace - Namespace for the tag (optional)
|
|
45
49
|
*/
|
|
46
50
|
push(tagName, attrValues = null, namespace = null) {
|
|
47
|
-
|
|
51
|
+
//invalidate cache
|
|
52
|
+
this._pathStringCache = null;
|
|
53
|
+
this._frozenPathCache = null;
|
|
54
|
+
this._frozenSiblingsCache = null;
|
|
48
55
|
// Remove values from previous current node (now becoming ancestor)
|
|
49
56
|
if (this.path.length > 0) {
|
|
50
57
|
const prev = this.path[this.path.length - 1];
|
|
@@ -99,10 +106,11 @@ export default class Matcher {
|
|
|
99
106
|
* @returns {Object|undefined} The popped node
|
|
100
107
|
*/
|
|
101
108
|
pop() {
|
|
102
|
-
if (this.path.length === 0)
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
this.
|
|
109
|
+
if (this.path.length === 0) return undefined;
|
|
110
|
+
//invalidate cache
|
|
111
|
+
this._pathStringCache = null;
|
|
112
|
+
this._frozenPathCache = null;
|
|
113
|
+
this._frozenSiblingsCache = null;
|
|
106
114
|
const node = this.path.pop();
|
|
107
115
|
|
|
108
116
|
// Clean up sibling tracking for levels deeper than current
|
|
@@ -125,6 +133,7 @@ export default class Matcher {
|
|
|
125
133
|
const current = this.path[this.path.length - 1];
|
|
126
134
|
if (attrValues !== null && attrValues !== undefined) {
|
|
127
135
|
current.values = attrValues;
|
|
136
|
+
this._frozenPathCache = null;
|
|
128
137
|
}
|
|
129
138
|
}
|
|
130
139
|
}
|
|
@@ -241,7 +250,10 @@ export default class Matcher {
|
|
|
241
250
|
* Reset the path to empty
|
|
242
251
|
*/
|
|
243
252
|
reset() {
|
|
244
|
-
|
|
253
|
+
//invalidate cache
|
|
254
|
+
this._pathStringCache = null;
|
|
255
|
+
this._frozenPathCache = null;
|
|
256
|
+
this._frozenSiblingsCache = null;
|
|
245
257
|
this.path = [];
|
|
246
258
|
this.siblingStacks = [];
|
|
247
259
|
}
|
|
@@ -413,6 +425,15 @@ export default class Matcher {
|
|
|
413
425
|
return true;
|
|
414
426
|
}
|
|
415
427
|
|
|
428
|
+
/**
|
|
429
|
+
* Match any expression in the given set against the current path.
|
|
430
|
+
* @param {ExpressionSet} exprSet - The set of expressions to match against.
|
|
431
|
+
* @returns {boolean} - True if any expression in the set matches the current path, false otherwise.
|
|
432
|
+
*/
|
|
433
|
+
matchesAny(exprSet) {
|
|
434
|
+
return exprSet.matchesAny(this);
|
|
435
|
+
}
|
|
436
|
+
|
|
416
437
|
/**
|
|
417
438
|
* Create a snapshot of current state
|
|
418
439
|
* @returns {Object} State snapshot
|
|
@@ -429,7 +450,10 @@ export default class Matcher {
|
|
|
429
450
|
* @param {Object} snapshot - State snapshot
|
|
430
451
|
*/
|
|
431
452
|
restore(snapshot) {
|
|
432
|
-
|
|
453
|
+
//invalidate cache
|
|
454
|
+
this._pathStringCache = null;
|
|
455
|
+
this._frozenPathCache = null;
|
|
456
|
+
this._frozenSiblingsCache = null;
|
|
433
457
|
this.path = snapshot.path.map(node => ({ ...node }));
|
|
434
458
|
this.siblingStacks = snapshot.siblingStacks.map(map => new Map(map));
|
|
435
459
|
}
|
|
@@ -470,22 +494,28 @@ export default class Matcher {
|
|
|
470
494
|
};
|
|
471
495
|
}
|
|
472
496
|
|
|
473
|
-
|
|
497
|
+
// Return cached frozen copy of path — rebuilt only after push/pop/updateCurrent/reset/restore
|
|
498
|
+
if (prop === 'path') {
|
|
499
|
+
if (target._frozenPathCache === null) {
|
|
500
|
+
target._frozenPathCache = Object.freeze(
|
|
501
|
+
target.path.map(node => Object.freeze({ ...node }))
|
|
502
|
+
);
|
|
503
|
+
}
|
|
504
|
+
return target._frozenPathCache;
|
|
505
|
+
}
|
|
474
506
|
|
|
475
|
-
//
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
: Object.freeze({ ...item }) // freeze a copy of each node
|
|
484
|
-
)
|
|
485
|
-
: value
|
|
486
|
-
);
|
|
507
|
+
// Return cached frozen copy of siblingStacks — rebuilt only after push/pop/reset/restore
|
|
508
|
+
if (prop === 'siblingStacks') {
|
|
509
|
+
if (target._frozenSiblingsCache === null) {
|
|
510
|
+
target._frozenSiblingsCache = Object.freeze(
|
|
511
|
+
target.siblingStacks.map(map => Object.freeze(new Map(map)))
|
|
512
|
+
);
|
|
513
|
+
}
|
|
514
|
+
return target._frozenSiblingsCache;
|
|
487
515
|
}
|
|
488
516
|
|
|
517
|
+
const value = Reflect.get(target, prop, receiver);
|
|
518
|
+
|
|
489
519
|
// Bind methods so `this` inside them still refers to the real Matcher
|
|
490
520
|
if (typeof value === 'function') {
|
|
491
521
|
return value.bind(target);
|
package/src/index.d.ts
CHANGED
|
@@ -299,6 +299,14 @@ export interface ReadOnlyMatcher {
|
|
|
299
299
|
*/
|
|
300
300
|
matches(expression: Expression): boolean;
|
|
301
301
|
|
|
302
|
+
/**
|
|
303
|
+
* Test whether the matcher's current path matches **any** expression in the set.
|
|
304
|
+
*
|
|
305
|
+
* @param exprSet - A `ExpressionSet` instance
|
|
306
|
+
* @returns `true` if at least one expression matches the current path
|
|
307
|
+
*/
|
|
308
|
+
matchesAny(exprSet: ExpressionSet): boolean;
|
|
309
|
+
|
|
302
310
|
/**
|
|
303
311
|
* Create a snapshot of current state
|
|
304
312
|
* @returns State snapshot that can be restored later
|
|
@@ -489,6 +497,30 @@ export class Matcher {
|
|
|
489
497
|
*/
|
|
490
498
|
matches(expression: Expression): boolean;
|
|
491
499
|
|
|
500
|
+
/**
|
|
501
|
+
* Test whether the matcher's current path matches **any** expression in the set.
|
|
502
|
+
*
|
|
503
|
+
* Uses the pre-built index to evaluate only the relevant bucket(s):
|
|
504
|
+
* 1. Exact depth + tag — O(1) lookup
|
|
505
|
+
* 2. Depth-matched wildcard tag — O(1) lookup
|
|
506
|
+
* 3. Deep-wildcard expressions — always scanned (typically a small list)
|
|
507
|
+
*
|
|
508
|
+
* @param exprSet - A `ExpressionSet` instance
|
|
509
|
+
* @returns `true` if at least one expression matches the current path
|
|
510
|
+
*
|
|
511
|
+
* @example
|
|
512
|
+
* ```typescript
|
|
513
|
+
* // Replaces:
|
|
514
|
+
* // for (const expr of stopNodeExpressions) {
|
|
515
|
+
* // if (matcher.matches(expr)) return true;
|
|
516
|
+
* // }
|
|
517
|
+
*
|
|
518
|
+
* if (matcher.matchesAny(stopNodes)) {
|
|
519
|
+
* // current tag is a stop node
|
|
520
|
+
* }
|
|
521
|
+
* ```
|
|
522
|
+
*/
|
|
523
|
+
matchesAny(exprSet: ExpressionSet): boolean;
|
|
492
524
|
/**
|
|
493
525
|
* Create a snapshot of current state
|
|
494
526
|
* @returns State snapshot that can be restored later
|
|
@@ -508,11 +540,167 @@ export class Matcher {
|
|
|
508
540
|
}
|
|
509
541
|
|
|
510
542
|
/**
|
|
511
|
-
*
|
|
543
|
+
* ExpressionSet - An indexed collection of Expressions for efficient bulk matching
|
|
544
|
+
*
|
|
545
|
+
* Pre-indexes expressions at insertion time by depth and terminal tag name so
|
|
546
|
+
* that `matchesAny()` performs an O(1) bucket lookup rather than a full O(E)
|
|
547
|
+
* linear scan on every tag.
|
|
548
|
+
*
|
|
549
|
+
* Three internal buckets are maintained automatically:
|
|
550
|
+
* - **exact** — expressions with a fixed depth and a concrete terminal tag
|
|
551
|
+
* - **depth-wildcard** — fixed depth but terminal tag is `*`
|
|
552
|
+
* - **deep-wildcard** — expressions containing `..` (cannot be depth-indexed)
|
|
553
|
+
*
|
|
554
|
+
* @example
|
|
555
|
+
* ```typescript
|
|
556
|
+
* import { Expression, ExpressionSet, Matcher } from 'fast-xml-tagger';
|
|
557
|
+
*
|
|
558
|
+
* // Build once at config time
|
|
559
|
+
* const stopNodes = new ExpressionSet();
|
|
560
|
+
* stopNodes
|
|
561
|
+
* .add(new Expression('root.users.user'))
|
|
562
|
+
* .add(new Expression('root.config.*'))
|
|
563
|
+
* .add(new Expression('..script'))
|
|
564
|
+
* .seal(); // prevent accidental mutation during parsing
|
|
565
|
+
*
|
|
566
|
+
* // Query on every tag — hot path
|
|
567
|
+
* if (stopNodes.matchesAny(matcher)) {
|
|
568
|
+
* // handle stop node
|
|
569
|
+
* }
|
|
570
|
+
* ```
|
|
571
|
+
*/
|
|
572
|
+
export class ExpressionSet {
|
|
573
|
+
/**
|
|
574
|
+
* Create an empty ExpressionSet.
|
|
575
|
+
*/
|
|
576
|
+
constructor();
|
|
577
|
+
|
|
578
|
+
/**
|
|
579
|
+
* Number of expressions currently in the set.
|
|
580
|
+
*/
|
|
581
|
+
readonly size: number;
|
|
582
|
+
|
|
583
|
+
/**
|
|
584
|
+
* Whether the set has been sealed against further modifications.
|
|
585
|
+
*/
|
|
586
|
+
readonly isSealed: boolean;
|
|
587
|
+
|
|
588
|
+
/**
|
|
589
|
+
* Add a single Expression to the set.
|
|
590
|
+
*
|
|
591
|
+
* Duplicate patterns (same `expression.pattern` string) are silently ignored.
|
|
592
|
+
*
|
|
593
|
+
* @param expression - A pre-constructed Expression instance
|
|
594
|
+
* @returns `this` — for chaining
|
|
595
|
+
* @throws {TypeError} if the set has been sealed
|
|
596
|
+
*
|
|
597
|
+
* @example
|
|
598
|
+
* ```typescript
|
|
599
|
+
* set.add(new Expression('root.users.user'));
|
|
600
|
+
* set.add(new Expression('..script'));
|
|
601
|
+
* ```
|
|
602
|
+
*/
|
|
603
|
+
add(expression: Expression): this;
|
|
604
|
+
|
|
605
|
+
/**
|
|
606
|
+
* Add multiple expressions at once.
|
|
607
|
+
*
|
|
608
|
+
* @param expressions - Array of Expression instances
|
|
609
|
+
* @returns `this` — for chaining
|
|
610
|
+
* @throws {TypeError} if the set has been sealed
|
|
611
|
+
*
|
|
612
|
+
* @example
|
|
613
|
+
* ```typescript
|
|
614
|
+
* set.addAll([
|
|
615
|
+
* new Expression('root.users.user'),
|
|
616
|
+
* new Expression('root.config.setting'),
|
|
617
|
+
* ]);
|
|
618
|
+
* ```
|
|
619
|
+
*/
|
|
620
|
+
addAll(expressions: Expression[]): this;
|
|
621
|
+
|
|
622
|
+
/**
|
|
623
|
+
* Check whether an Expression with the same pattern is already present.
|
|
624
|
+
*
|
|
625
|
+
* @param expression - Expression to look up
|
|
626
|
+
* @returns `true` if the pattern was already added
|
|
627
|
+
*/
|
|
628
|
+
has(expression: Expression): boolean;
|
|
629
|
+
|
|
630
|
+
/**
|
|
631
|
+
* Seal the set against further modifications.
|
|
632
|
+
*
|
|
633
|
+
* After calling `seal()`, any call to `add()` or `addAll()` will throw a
|
|
634
|
+
* `TypeError`. This is useful to prevent accidental mutation once the config
|
|
635
|
+
* has been fully built and parsing has started.
|
|
636
|
+
*
|
|
637
|
+
* @returns `this` — for chaining
|
|
638
|
+
*
|
|
639
|
+
* @example
|
|
640
|
+
* ```typescript
|
|
641
|
+
* const stopNodes = new ExpressionSet();
|
|
642
|
+
* stopNodes.addAll(patterns.map(p => new Expression(p))).seal();
|
|
643
|
+
*
|
|
644
|
+
* // Later — safe: reads are still allowed
|
|
645
|
+
* stopNodes.matchesAny(matcher);
|
|
646
|
+
*
|
|
647
|
+
* // Later — throws TypeError: ExpressionSet is sealed
|
|
648
|
+
* stopNodes.add(new Expression('root.extra'));
|
|
649
|
+
* ```
|
|
650
|
+
*/
|
|
651
|
+
seal(): this;
|
|
652
|
+
|
|
653
|
+
/**
|
|
654
|
+
* Test whether the matcher's current path matches **any** expression in the set.
|
|
655
|
+
*
|
|
656
|
+
* Uses the pre-built index to evaluate only the relevant bucket(s):
|
|
657
|
+
* 1. Exact depth + tag — O(1) lookup
|
|
658
|
+
* 2. Depth-matched wildcard tag — O(1) lookup
|
|
659
|
+
* 3. Deep-wildcard expressions — always scanned (typically a small list)
|
|
660
|
+
*
|
|
661
|
+
* @param matcher - A `Matcher` instance or a `ReadOnlyMatcher` view
|
|
662
|
+
* @returns `true` if at least one expression matches the current path
|
|
663
|
+
*
|
|
664
|
+
* @example
|
|
665
|
+
* ```typescript
|
|
666
|
+
* // Replaces:
|
|
667
|
+
* // for (const expr of stopNodeExpressions) {
|
|
668
|
+
* // if (matcher.matches(expr)) return true;
|
|
669
|
+
* // }
|
|
670
|
+
*
|
|
671
|
+
* if (stopNodes.matchesAny(matcher)) {
|
|
672
|
+
* // current tag is a stop node
|
|
673
|
+
* }
|
|
674
|
+
* ```
|
|
675
|
+
*/
|
|
676
|
+
matchesAny(matcher: Matcher | ReadOnlyMatcher): boolean;
|
|
677
|
+
|
|
678
|
+
/**
|
|
679
|
+
* Find the first expression in the set that matches the matcher's current path.
|
|
680
|
+
*
|
|
681
|
+
* Uses the pre-built index to evaluate only the relevant bucket(s):
|
|
682
|
+
* 1. Exact depth + tag — O(1) lookup
|
|
683
|
+
* 2. Depth-matched wildcard tag — O(1) lookup
|
|
684
|
+
* 3. Deep-wildcard expressions — always scanned (typically a small list)
|
|
685
|
+
*
|
|
686
|
+
* @param matcher - A `Matcher` instance or a `ReadOnlyMatcher` view
|
|
687
|
+
* @returns Expression if at least one expression matches the current path
|
|
688
|
+
*
|
|
689
|
+
* @example
|
|
690
|
+
* ```typescript
|
|
691
|
+
* const node = stopNodes.findMatch(matcher);
|
|
692
|
+
* ```
|
|
693
|
+
*/
|
|
694
|
+
findMatch(matcher: Matcher | ReadOnlyMatcher): Expression;
|
|
695
|
+
}
|
|
696
|
+
|
|
697
|
+
/**
|
|
698
|
+
* Default export containing Expression, Matcher, and ExpressionSet
|
|
512
699
|
*/
|
|
513
700
|
declare const _default: {
|
|
514
701
|
Expression: typeof Expression;
|
|
515
702
|
Matcher: typeof Matcher;
|
|
703
|
+
ExpressionSet: typeof ExpressionSet;
|
|
516
704
|
};
|
|
517
705
|
|
|
518
706
|
export default _default;
|
package/src/index.js
CHANGED
|
@@ -23,6 +23,7 @@
|
|
|
23
23
|
|
|
24
24
|
import Expression from './Expression.js';
|
|
25
25
|
import Matcher from './Matcher.js';
|
|
26
|
+
import ExpressionSet from './ExpressionSet.js';
|
|
26
27
|
|
|
27
|
-
export { Expression, Matcher };
|
|
28
|
-
export default { Expression, Matcher };
|
|
28
|
+
export { Expression, Matcher, ExpressionSet };
|
|
29
|
+
export default { Expression, Matcher, ExpressionSet };
|