path-expression-matcher 1.1.2 → 1.2.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 CHANGED
@@ -346,6 +346,57 @@ Restore from a snapshot.
346
346
  matcher.restore(snapshot);
347
347
  ```
348
348
 
349
+ #### Read-Only Access
350
+
351
+ ##### `readOnly()`
352
+
353
+ Returns a **live, read-only proxy** of the matcher. All query and inspection methods work normally, but any attempt to call a state-mutating method (`push`, `pop`, `reset`, `updateCurrent`, `restore`) or to write/delete a property throws a `TypeError`.
354
+
355
+ This is the recommended way to share the matcher with external consumers — plugins, callbacks, event handlers — that only need to inspect the current path without being able to corrupt parser state.
356
+
357
+ ```javascript
358
+ const ro = matcher.readOnly();
359
+ ```
360
+
361
+ **What works on the read-only view:**
362
+
363
+ ```javascript
364
+ ro.matches(expr) // ✓ pattern matching
365
+ ro.getCurrentTag() // ✓ current tag name
366
+ ro.getCurrentNamespace() // ✓ current namespace
367
+ ro.getAttrValue("id") // ✓ attribute value
368
+ ro.hasAttr("id") // ✓ attribute presence check
369
+ ro.getPosition() // ✓ sibling position
370
+ ro.getCounter() // ✓ occurrence counter
371
+ ro.getDepth() // ✓ path depth
372
+ ro.toString() // ✓ path as string
373
+ ro.toArray() // ✓ path as array
374
+ ro.snapshot() // ✓ snapshot (can be used to restore the real matcher)
375
+ ```
376
+
377
+ **What throws a `TypeError`:**
378
+
379
+ ```javascript
380
+ ro.push("child", {}) // ✗ TypeError: Cannot call 'push' on a read-only Matcher
381
+ ro.pop() // ✗ TypeError: Cannot call 'pop' on a read-only Matcher
382
+ ro.reset() // ✗ TypeError: Cannot call 'reset' on a read-only Matcher
383
+ ro.updateCurrent({}) // ✗ TypeError: Cannot call 'updateCurrent' on a read-only Matcher
384
+ ro.restore(snapshot) // ✗ TypeError: Cannot call 'restore' on a read-only Matcher
385
+ ro.separator = '/' // ✗ TypeError: Cannot set property on a read-only Matcher
386
+ ```
387
+
388
+ **Important:** The read-only view is **live** — it always reflects the current state of the underlying matcher. If you need a frozen-in-time copy instead, use `snapshot()`.
389
+
390
+ ```javascript
391
+ const matcher = new Matcher();
392
+ const ro = matcher.readOnly();
393
+
394
+ matcher.push("root");
395
+ ro.getDepth(); // 1 — immediately reflects the push
396
+ matcher.push("users");
397
+ ro.getDepth(); // 2 — still live
398
+ ```
399
+
349
400
  ## 💡 Usage Examples
350
401
 
351
402
  ### Example 1: XML Parser with stopNodes
@@ -481,7 +532,53 @@ const expr = new Expression("root.item:first");
481
532
  console.log(matcher.matches(expr)); // false (counter=1, not 0)
482
533
  ```
483
534
 
484
- ### Example 7: Namespace Support (XML/SOAP)
535
+ ### Example 8: Passing a Read-Only Matcher to External Consumers
536
+
537
+ When passing the matcher into callbacks, plugins, or other code you don't control, use `readOnly()` to prevent accidental state corruption.
538
+
539
+ ```javascript
540
+ import { Expression, Matcher } from 'path-expression-matcher';
541
+
542
+ const matcher = new Matcher();
543
+
544
+ const adminExpr = new Expression("..user[type=admin]");
545
+
546
+ function parseTag(tagName, attrs, onTag) {
547
+ matcher.push(tagName, attrs);
548
+
549
+ // Pass a read-only view — consumer can inspect but not mutate
550
+ onTag(matcher.readOnly());
551
+
552
+ matcher.pop();
553
+ }
554
+
555
+ // Safe consumer — can only read
556
+ function myPlugin(ro) {
557
+ if (ro.matches(adminExpr)) {
558
+ console.log("Admin at path:", ro.toString());
559
+ console.log("Depth:", ro.getDepth());
560
+ console.log("ID:", ro.getAttrValue("id"));
561
+ }
562
+ }
563
+
564
+ // ro.push(...) or ro.reset() here would throw TypeError,
565
+ // so the parser's state is always safe.
566
+ parseTag("user", { id: "1", type: "admin" }, myPlugin);
567
+ ```
568
+
569
+ **Combining with `snapshot()`:** A snapshot taken via the read-only view can still be used to restore the real matcher.
570
+
571
+ ```javascript
572
+ const matcher = new Matcher();
573
+ matcher.push("root");
574
+ matcher.push("users");
575
+
576
+ const ro = matcher.readOnly();
577
+ const snap = ro.snapshot(); // ✓ snapshot works on read-only view
578
+
579
+ matcher.push("user"); // continue parsing...
580
+ matcher.restore(snap); // restore to "root.users" using the snapshot
581
+ ```
485
582
 
486
583
  ```javascript
487
584
  const matcher = new Matcher();
@@ -614,18 +711,6 @@ const parser = new XMLParser({
614
711
  });
615
712
  ```
616
713
 
617
- ## 🧪 Testing
618
-
619
- ```bash
620
- npm test
621
- ```
622
-
623
- All 77 tests covering:
624
- - Pattern parsing (exact, wildcards, attributes, position)
625
- - Path tracking (push, pop, update)
626
- - Pattern matching (all combinations)
627
- - Edge cases and error conditions
628
-
629
714
  ## 📄 License
630
715
 
631
716
  MIT
package/lib/pem.cjs ADDED
@@ -0,0 +1 @@
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,Matcher:()=>i,default:()=>r});class s{constructor(t,e={}){this.pattern=t,this.separator=e.separator||".",this.segments=this._parse(t),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=[]}push(t,e=null,s=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;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)}}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;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.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}snapshot(){return{path:this.path.map(t=>({...t})),siblingStacks:this.siblingStacks.map(t=>new Map(t))}}restore(t){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.`)};const i=Reflect.get(t,e,s);return"path"===e||"siblingStacks"===e?Object.freeze(Array.isArray(i)?i.map(t=>t instanceof Map?Object.freeze(new Map(t)):Object.freeze({...t})):i):"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.`)}})}}const r={Expression:s,Matcher:i};module.exports=e})();
package/lib/pem.d.cts ADDED
@@ -0,0 +1,523 @@
1
+ /**
2
+ * TypeScript definitions for path-expression-matcher (CommonJS)
3
+ */
4
+
5
+ /**
6
+ * Options for creating an Expression
7
+ */
8
+ declare interface ExpressionOptions {
9
+ /**
10
+ * Path separator character
11
+ * @default '.'
12
+ */
13
+ separator?: string;
14
+ }
15
+
16
+ /**
17
+ * Parsed segment from an expression pattern
18
+ */
19
+ declare interface Segment {
20
+ /**
21
+ * Type of segment
22
+ */
23
+ type: 'tag' | 'deep-wildcard';
24
+
25
+ /**
26
+ * Tag name (e.g., "user", "*" for wildcard)
27
+ * Only present when type is 'tag'
28
+ */
29
+ tag?: string;
30
+
31
+ /**
32
+ * Namespace prefix (e.g., "ns" in "ns::user")
33
+ * Only present when namespace is specified
34
+ */
35
+ namespace?: string;
36
+
37
+ /**
38
+ * Attribute name to match (e.g., "id" in "user[id]")
39
+ * Only present when attribute condition exists
40
+ */
41
+ attrName?: string;
42
+
43
+ /**
44
+ * Attribute value to match (e.g., "123" in "user[id=123]")
45
+ * Only present when attribute value is specified
46
+ */
47
+ attrValue?: string;
48
+
49
+ /**
50
+ * Position selector type
51
+ * Only present when position selector exists
52
+ */
53
+ position?: 'first' | 'last' | 'odd' | 'even' | 'nth';
54
+
55
+ /**
56
+ * Numeric value for nth() selector
57
+ * Only present when position is 'nth'
58
+ */
59
+ positionValue?: number;
60
+ }
61
+
62
+ /**
63
+ * Expression - Parses and stores a tag pattern expression
64
+ *
65
+ * Patterns are parsed once and stored in an optimized structure for fast matching.
66
+ *
67
+ * @example
68
+ * ```javascript
69
+ * const { Expression } = require('path-expression-matcher');
70
+ * const expr = new Expression("root.users.user");
71
+ * const expr2 = new Expression("..user[id]:first");
72
+ * const expr3 = new Expression("root/users/user", { separator: '/' });
73
+ * ```
74
+ *
75
+ * Pattern Syntax:
76
+ * - `root.users.user` - Match exact path
77
+ * - `..user` - Match "user" at any depth (deep wildcard)
78
+ * - `user[id]` - Match user tag with "id" attribute
79
+ * - `user[id=123]` - Match user tag where id="123"
80
+ * - `user:first` - Match first occurrence of user tag
81
+ * - `ns::user` - Match user tag with namespace "ns"
82
+ * - `ns::user[id]:first` - Combine namespace, attribute, and position
83
+ * ```
84
+ */
85
+ declare class Expression {
86
+ /**
87
+ * Original pattern string
88
+ */
89
+ readonly pattern: string;
90
+
91
+ /**
92
+ * Path separator character
93
+ */
94
+ readonly separator: string;
95
+
96
+ /**
97
+ * Parsed segments
98
+ */
99
+ readonly segments: Segment[];
100
+
101
+ /**
102
+ * Create a new Expression
103
+ * @param pattern - Pattern string (e.g., "root.users.user", "..user[id]")
104
+ * @param options - Configuration options
105
+ */
106
+ constructor(pattern: string, options?: ExpressionOptions);
107
+
108
+ /**
109
+ * Get the number of segments
110
+ */
111
+ get length(): number;
112
+
113
+ /**
114
+ * Check if expression contains deep wildcard (..)
115
+ */
116
+ hasDeepWildcard(): boolean;
117
+
118
+ /**
119
+ * Check if expression has attribute conditions
120
+ */
121
+ hasAttributeCondition(): boolean;
122
+
123
+ /**
124
+ * Check if expression has position selectors
125
+ */
126
+ hasPositionSelector(): boolean;
127
+
128
+ /**
129
+ * Get string representation
130
+ */
131
+ toString(): string;
132
+ }
133
+
134
+ /**
135
+ * Options for creating a Matcher
136
+ */
137
+ declare interface MatcherOptions {
138
+ /**
139
+ * Default path separator
140
+ * @default '.'
141
+ */
142
+ separator?: string;
143
+ }
144
+
145
+ /**
146
+ * Internal node structure in the path stack
147
+ */
148
+ declare interface PathNode {
149
+ /**
150
+ * Tag name
151
+ */
152
+ tag: string;
153
+
154
+ /**
155
+ * Namespace (if present)
156
+ */
157
+ namespace?: string;
158
+
159
+ /**
160
+ * Position in sibling list (child index in parent)
161
+ */
162
+ position: number;
163
+
164
+ /**
165
+ * Counter (occurrence count of this tag name)
166
+ */
167
+ counter: number;
168
+
169
+ /**
170
+ * Attribute key-value pairs
171
+ * Only present for the current (last) node in path
172
+ */
173
+ values?: Record<string, any>;
174
+ }
175
+
176
+ /**
177
+ * Snapshot of matcher state
178
+ */
179
+ declare interface MatcherSnapshot {
180
+ /**
181
+ * Copy of the path stack
182
+ */
183
+ path: PathNode[];
184
+
185
+ /**
186
+ * Copy of sibling tracking maps
187
+ */
188
+ siblingStacks: Map<string, number>[];
189
+ }
190
+
191
+ /**
192
+ * ReadOnlyMatcher - A safe, read-only view over a {@link Matcher} instance.
193
+ *
194
+ * Returned by {@link Matcher.readOnly}. Exposes all query and inspection
195
+ * methods but **throws a `TypeError`** if any state-mutating method is called
196
+ * (`push`, `pop`, `reset`, `updateCurrent`, `restore`). Direct property
197
+ * writes are also blocked.
198
+ *
199
+ * Pass this to consumers that only need to inspect or match the current path
200
+ * so they cannot accidentally corrupt the parser state.
201
+ *
202
+ * @example
203
+ * ```javascript
204
+ * const matcher = new Matcher();
205
+ * matcher.push("root", {});
206
+ * matcher.push("users", {});
207
+ * matcher.push("user", { id: "123" });
208
+ *
209
+ * const ro: ReadOnlyMatcher = matcher.readOnly();
210
+ *
211
+ * ro.matches(expr); // ✓ works
212
+ * ro.getCurrentTag(); // ✓ "user"
213
+ * ro.getDepth(); // ✓ 3
214
+ * ro.push("child", {}); // ✗ TypeError: Cannot call 'push' on a read-only Matcher
215
+ * ro.reset(); // ✗ TypeError: Cannot call 'reset' on a read-only Matcher
216
+ * ```
217
+ */
218
+ declare interface ReadOnlyMatcher {
219
+ /**
220
+ * Default path separator (read-only)
221
+ */
222
+ readonly separator: string;
223
+
224
+ /**
225
+ * Current path stack (each node is a frozen copy)
226
+ */
227
+ readonly path: ReadonlyArray<Readonly<PathNode>>;
228
+
229
+ // ── Query methods ───────────────────────────────────────────────────────────
230
+
231
+ /**
232
+ * Get current tag name
233
+ * @returns Current tag name or undefined if path is empty
234
+ */
235
+ getCurrentTag(): string | undefined;
236
+
237
+ /**
238
+ * Get current namespace
239
+ * @returns Current namespace or undefined if not present or path is empty
240
+ */
241
+ getCurrentNamespace(): string | undefined;
242
+
243
+ /**
244
+ * Get current node's attribute value
245
+ * @param attrName - Attribute name
246
+ * @returns Attribute value or undefined
247
+ */
248
+ getAttrValue(attrName: string): any;
249
+
250
+ /**
251
+ * Check if current node has an attribute
252
+ * @param attrName - Attribute name
253
+ */
254
+ hasAttr(attrName: string): boolean;
255
+
256
+ /**
257
+ * Get current node's sibling position (child index in parent)
258
+ * @returns Position index or -1 if path is empty
259
+ */
260
+ getPosition(): number;
261
+
262
+ /**
263
+ * Get current node's repeat counter (occurrence count of this tag name)
264
+ * @returns Counter value or -1 if path is empty
265
+ */
266
+ getCounter(): number;
267
+
268
+ /**
269
+ * Get current node's sibling index (alias for getPosition for backward compatibility)
270
+ * @returns Index or -1 if path is empty
271
+ * @deprecated Use getPosition() or getCounter() instead
272
+ */
273
+ getIndex(): number;
274
+
275
+ /**
276
+ * Get current path depth
277
+ * @returns Number of nodes in the path
278
+ */
279
+ getDepth(): number;
280
+
281
+ /**
282
+ * Get path as string
283
+ * @param separator - Optional separator (uses default if not provided)
284
+ * @param includeNamespace - Whether to include namespace in output
285
+ * @returns Path string (e.g., "root.users.user" or "ns:root.ns:users.user")
286
+ */
287
+ toString(separator?: string, includeNamespace?: boolean): string;
288
+
289
+ /**
290
+ * Get path as array of tag names
291
+ * @returns Array of tag names
292
+ */
293
+ toArray(): string[];
294
+
295
+ /**
296
+ * Match current path against an Expression
297
+ * @param expression - The expression to match against
298
+ * @returns True if current path matches the expression
299
+ */
300
+ matches(expression: Expression): boolean;
301
+
302
+ /**
303
+ * Create a snapshot of current state
304
+ * @returns State snapshot that can be restored later
305
+ */
306
+ snapshot(): MatcherSnapshot;
307
+
308
+ // ── Blocked mutating methods ────────────────────────────────────────────────
309
+ // These are present in the type so callers get a compile-time error with a
310
+ // helpful message instead of a silent "property does not exist" error.
311
+
312
+ /**
313
+ * @throws {TypeError} Always – mutation is not allowed on a read-only view.
314
+ */
315
+ push(tagName: string, attrValues?: Record<string, any> | null, namespace?: string | null): never;
316
+
317
+ /**
318
+ * @throws {TypeError} Always – mutation is not allowed on a read-only view.
319
+ */
320
+ pop(): never;
321
+
322
+ /**
323
+ * @throws {TypeError} Always – mutation is not allowed on a read-only view.
324
+ */
325
+ updateCurrent(attrValues: Record<string, any>): never;
326
+
327
+ /**
328
+ * @throws {TypeError} Always – mutation is not allowed on a read-only view.
329
+ */
330
+ reset(): never;
331
+
332
+ /**
333
+ * @throws {TypeError} Always – mutation is not allowed on a read-only view.
334
+ */
335
+ restore(snapshot: MatcherSnapshot): never;
336
+ }
337
+
338
+ /**
339
+ * Matcher - Tracks current path in XML/JSON tree and matches against Expressions
340
+ *
341
+ * The matcher maintains a stack of nodes representing the current path from root to
342
+ * current tag. It only stores attribute values for the current (top) node to minimize
343
+ * memory usage.
344
+ *
345
+ * @example
346
+ * ```javascript
347
+ * const { Matcher } = require('path-expression-matcher');
348
+ * const matcher = new Matcher();
349
+ * matcher.push("root", {});
350
+ * matcher.push("users", {});
351
+ * matcher.push("user", { id: "123", type: "admin" });
352
+ *
353
+ * const expr = new Expression("root.users.user");
354
+ * matcher.matches(expr); // true
355
+ *
356
+ * matcher.pop();
357
+ * matcher.matches(expr); // false
358
+ * ```
359
+ */
360
+ declare class Matcher {
361
+ /**
362
+ * Default path separator
363
+ */
364
+ readonly separator: string;
365
+
366
+ /**
367
+ * Current path stack
368
+ */
369
+ readonly path: PathNode[];
370
+
371
+ /**
372
+ * Create a new Matcher
373
+ * @param options - Configuration options
374
+ */
375
+ constructor(options?: MatcherOptions);
376
+
377
+ /**
378
+ * Push a new tag onto the path
379
+ * @param tagName - Name of the tag
380
+ * @param attrValues - Attribute key-value pairs for current node (optional)
381
+ * @param namespace - Namespace for the tag (optional)
382
+ *
383
+ * @example
384
+ * ```javascript
385
+ * matcher.push("user", { id: "123", type: "admin" });
386
+ * matcher.push("user", { id: "456" }, "ns");
387
+ * matcher.push("container", null);
388
+ * ```
389
+ */
390
+ push(tagName: string, attrValues?: Record<string, any> | null, namespace?: string | null): void;
391
+
392
+ /**
393
+ * Pop the last tag from the path
394
+ * @returns The popped node or undefined if path is empty
395
+ */
396
+ pop(): PathNode | undefined;
397
+
398
+ /**
399
+ * Update current node's attribute values
400
+ * Useful when attributes are parsed after push
401
+ * @param attrValues - Attribute values
402
+ */
403
+ updateCurrent(attrValues: Record<string, any>): void;
404
+
405
+ /**
406
+ * Get current tag name
407
+ * @returns Current tag name or undefined if path is empty
408
+ */
409
+ getCurrentTag(): string | undefined;
410
+
411
+ /**
412
+ * Get current namespace
413
+ * @returns Current namespace or undefined if not present or path is empty
414
+ */
415
+ getCurrentNamespace(): string | undefined;
416
+
417
+ /**
418
+ * Get current node's attribute value
419
+ * @param attrName - Attribute name
420
+ * @returns Attribute value or undefined
421
+ */
422
+ getAttrValue(attrName: string): any;
423
+
424
+ /**
425
+ * Check if current node has an attribute
426
+ * @param attrName - Attribute name
427
+ */
428
+ hasAttr(attrName: string): boolean;
429
+
430
+ /**
431
+ * Get current node's sibling position (child index in parent)
432
+ * @returns Position index or -1 if path is empty
433
+ */
434
+ getPosition(): number;
435
+
436
+ /**
437
+ * Get current node's repeat counter (occurrence count of this tag name)
438
+ * @returns Counter value or -1 if path is empty
439
+ */
440
+ getCounter(): number;
441
+
442
+ /**
443
+ * Get current node's sibling index (alias for getPosition for backward compatibility)
444
+ * @returns Index or -1 if path is empty
445
+ * @deprecated Use getPosition() or getCounter() instead
446
+ */
447
+ getIndex(): number;
448
+
449
+ /**
450
+ * Get current path depth
451
+ * @returns Number of nodes in the path
452
+ */
453
+ getDepth(): number;
454
+
455
+ /**
456
+ * Get path as string
457
+ * @param separator - Optional separator (uses default if not provided)
458
+ * @param includeNamespace - Whether to include namespace in output
459
+ * @returns Path string (e.g., "root.users.user" or "ns:root.ns:users.user")
460
+ */
461
+ toString(separator?: string, includeNamespace?: boolean): string;
462
+
463
+ /**
464
+ * Get path as array of tag names
465
+ * @returns Array of tag names
466
+ */
467
+ toArray(): string[];
468
+
469
+ /**
470
+ * Reset the path to empty
471
+ */
472
+ reset(): void;
473
+
474
+ /**
475
+ * Match current path against an Expression
476
+ * @param expression - The expression to match against
477
+ * @returns True if current path matches the expression
478
+ *
479
+ * @example
480
+ * ```javascript
481
+ * const expr = new Expression("root.users.user[id]");
482
+ * const matcher = new Matcher();
483
+ *
484
+ * matcher.push("root");
485
+ * matcher.push("users");
486
+ * matcher.push("user", { id: "123" });
487
+ *
488
+ * matcher.matches(expr); // true
489
+ * ```
490
+ */
491
+ matches(expression: Expression): boolean;
492
+
493
+ /**
494
+ * Create a snapshot of current state
495
+ * @returns State snapshot that can be restored later
496
+ */
497
+ snapshot(): MatcherSnapshot;
498
+
499
+ /**
500
+ * Restore state from snapshot
501
+ * @param snapshot - State snapshot from previous snapshot() call
502
+ */
503
+ restore(snapshot: MatcherSnapshot): void;
504
+
505
+ /**
506
+ * Return a read-only view of this matcher.
507
+ */
508
+ readOnly(): ReadOnlyMatcher;
509
+ }
510
+
511
+ declare namespace pathExpressionMatcher {
512
+ export {
513
+ Expression,
514
+ Matcher,
515
+ ExpressionOptions,
516
+ MatcherOptions,
517
+ Segment,
518
+ PathNode,
519
+ MatcherSnapshot,
520
+ };
521
+ }
522
+
523
+ export = pathExpressionMatcher;
package/lib/pem.min.js ADDED
@@ -0,0 +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:()=>r});class s{constructor(t,e={}){this.pattern=t,this.separator=e.separator||".",this.segments=this._parse(t),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=[]}push(t,e=null,s=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;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)}}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;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.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}snapshot(){return{path:this.path.map(t=>({...t})),siblingStacks:this.siblingStacks.map(t=>new Map(t))}}restore(t){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.`)};const i=Reflect.get(t,e,s);return"path"===e||"siblingStacks"===e?Object.freeze(Array.isArray(i)?i.map(t=>t instanceof Map?Object.freeze(new Map(t)):Object.freeze({...t})):i):"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.`)}})}}const r={Expression:s,Matcher:i};return e})());
2
+ //# sourceMappingURL=pem.min.js.map