path-expression-matcher 1.3.0 → 1.5.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 +52 -54
- package/lib/pem.cjs +1 -1
- package/lib/pem.d.cts +18 -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 +24 -4
- package/src/Matcher.js +223 -195
- package/src/index.d.ts +81 -246
package/src/index.d.ts
CHANGED
|
@@ -189,177 +189,81 @@ export interface MatcherSnapshot {
|
|
|
189
189
|
}
|
|
190
190
|
|
|
191
191
|
/**
|
|
192
|
-
*
|
|
192
|
+
* MatcherView - A lightweight read-only view over a {@link Matcher} instance.
|
|
193
193
|
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
196
|
-
*
|
|
197
|
-
*
|
|
194
|
+
* Created once by {@link Matcher} and reused across all callbacks — no allocation
|
|
195
|
+
* on every invocation. Holds a direct reference to the parent Matcher's internal
|
|
196
|
+
* state so it always reflects the current parser position with zero copying or
|
|
197
|
+
* freezing overhead.
|
|
198
198
|
*
|
|
199
|
-
*
|
|
200
|
-
* so
|
|
199
|
+
* Mutation methods (`push`, `pop`, `reset`, `updateCurrent`, `restore`) are simply
|
|
200
|
+
* absent from this class, so misuse is caught at compile time by TypeScript rather
|
|
201
|
+
* than at runtime.
|
|
202
|
+
*
|
|
203
|
+
* Obtain via {@link Matcher#readOnly} — the same instance is returned every time.
|
|
201
204
|
*
|
|
202
205
|
* @example
|
|
203
206
|
* ```typescript
|
|
204
207
|
* const matcher = new Matcher();
|
|
208
|
+
* const view: MatcherView = matcher.readOnly();
|
|
209
|
+
*
|
|
205
210
|
* matcher.push("root", {});
|
|
206
211
|
* matcher.push("users", {});
|
|
207
212
|
* matcher.push("user", { id: "123" });
|
|
208
213
|
*
|
|
209
|
-
*
|
|
210
|
-
*
|
|
211
|
-
*
|
|
212
|
-
*
|
|
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
|
|
214
|
+
* view.matches(expr); // ✓ true
|
|
215
|
+
* view.getCurrentTag(); // ✓ "user"
|
|
216
|
+
* view.getDepth(); // ✓ 3
|
|
217
|
+
* // view.push(...) // ✗ Property 'push' does not exist on type 'MatcherView'
|
|
216
218
|
* ```
|
|
217
219
|
*/
|
|
218
|
-
export
|
|
220
|
+
export class MatcherView {
|
|
219
221
|
/**
|
|
220
|
-
* Default path separator (read-only)
|
|
222
|
+
* Default path separator (read-only, delegates to parent Matcher)
|
|
221
223
|
*/
|
|
222
224
|
readonly separator: string;
|
|
223
225
|
|
|
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
226
|
getCurrentTag(): string | undefined;
|
|
236
|
-
|
|
237
|
-
/**
|
|
238
|
-
* Get current namespace
|
|
239
|
-
* @returns Current namespace or undefined if not present or path is empty
|
|
240
|
-
*/
|
|
241
227
|
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
228
|
getAttrValue(attrName: string): any;
|
|
249
|
-
|
|
250
|
-
/**
|
|
251
|
-
* Check if current node has an attribute
|
|
252
|
-
* @param attrName - Attribute name
|
|
253
|
-
*/
|
|
254
229
|
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
230
|
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
231
|
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
|
-
*/
|
|
232
|
+
/** @deprecated Use getPosition() or getCounter() instead */
|
|
273
233
|
getIndex(): number;
|
|
274
|
-
|
|
275
|
-
/**
|
|
276
|
-
* Get current path depth
|
|
277
|
-
* @returns Number of nodes in the path
|
|
278
|
-
*/
|
|
279
234
|
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
235
|
toString(separator?: string, includeNamespace?: boolean): string;
|
|
288
|
-
|
|
289
|
-
/**
|
|
290
|
-
* Get path as array of tag names
|
|
291
|
-
* @returns Array of tag names
|
|
292
|
-
*/
|
|
293
236
|
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
237
|
matches(expression: Expression): boolean;
|
|
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
238
|
matchesAny(exprSet: ExpressionSet): boolean;
|
|
309
|
-
|
|
310
|
-
/**
|
|
311
|
-
* Create a snapshot of current state
|
|
312
|
-
* @returns State snapshot that can be restored later
|
|
313
|
-
*/
|
|
314
|
-
snapshot(): MatcherSnapshot;
|
|
315
|
-
|
|
316
|
-
// ── Blocked mutating methods ────────────────────────────────────────────────
|
|
317
|
-
// These are present in the type so callers get a compile-time error with a
|
|
318
|
-
// helpful message instead of a silent "property does not exist" error.
|
|
319
|
-
|
|
320
|
-
/**
|
|
321
|
-
* @throws {TypeError} Always – mutation is not allowed on a read-only view.
|
|
322
|
-
*/
|
|
323
|
-
push(tagName: string, attrValues?: Record<string, any> | null, namespace?: string | null): never;
|
|
324
|
-
|
|
325
|
-
/**
|
|
326
|
-
* @throws {TypeError} Always – mutation is not allowed on a read-only view.
|
|
327
|
-
*/
|
|
328
|
-
pop(): never;
|
|
329
|
-
|
|
330
|
-
/**
|
|
331
|
-
* @throws {TypeError} Always – mutation is not allowed on a read-only view.
|
|
332
|
-
*/
|
|
333
|
-
updateCurrent(attrValues: Record<string, any>): never;
|
|
334
|
-
|
|
335
|
-
/**
|
|
336
|
-
* @throws {TypeError} Always – mutation is not allowed on a read-only view.
|
|
337
|
-
*/
|
|
338
|
-
reset(): never;
|
|
339
|
-
|
|
340
|
-
/**
|
|
341
|
-
* @throws {TypeError} Always – mutation is not allowed on a read-only view.
|
|
342
|
-
*/
|
|
343
|
-
restore(snapshot: MatcherSnapshot): never;
|
|
344
239
|
}
|
|
345
240
|
|
|
346
241
|
/**
|
|
347
|
-
*
|
|
348
|
-
*
|
|
242
|
+
* @deprecated Use {@link MatcherView} instead.
|
|
243
|
+
* Alias kept for backward compatibility with code that references `ReadOnlyMatcher`.
|
|
244
|
+
*/
|
|
245
|
+
export type ReadOnlyMatcher = MatcherView;
|
|
246
|
+
|
|
247
|
+
/**
|
|
248
|
+
* Matcher - Tracks current path in XML/JSON tree and matches against Expressions.
|
|
249
|
+
*
|
|
349
250
|
* The matcher maintains a stack of nodes representing the current path from root to
|
|
350
251
|
* current tag. It only stores attribute values for the current (top) node to minimize
|
|
351
252
|
* memory usage.
|
|
352
|
-
*
|
|
253
|
+
*
|
|
254
|
+
* Use {@link Matcher#readOnly} to obtain a {@link MatcherView} safe to pass to
|
|
255
|
+
* user callbacks — the same instance is reused on every call with no allocation overhead.
|
|
256
|
+
*
|
|
353
257
|
* @example
|
|
354
258
|
* ```typescript
|
|
355
259
|
* const matcher = new Matcher();
|
|
356
260
|
* matcher.push("root", {});
|
|
357
261
|
* matcher.push("users", {});
|
|
358
262
|
* matcher.push("user", { id: "123", type: "admin" });
|
|
359
|
-
*
|
|
263
|
+
*
|
|
360
264
|
* const expr = new Expression("root.users.user");
|
|
361
265
|
* matcher.matches(expr); // true
|
|
362
|
-
*
|
|
266
|
+
*
|
|
363
267
|
* matcher.pop();
|
|
364
268
|
* matcher.matches(expr); // false
|
|
365
269
|
* ```
|
|
@@ -370,11 +274,6 @@ export class Matcher {
|
|
|
370
274
|
*/
|
|
371
275
|
readonly separator: string;
|
|
372
276
|
|
|
373
|
-
/**
|
|
374
|
-
* Current path stack
|
|
375
|
-
*/
|
|
376
|
-
readonly path: PathNode[];
|
|
377
|
-
|
|
378
277
|
/**
|
|
379
278
|
* Create a new Matcher
|
|
380
279
|
* @param options - Configuration options
|
|
@@ -382,11 +281,11 @@ export class Matcher {
|
|
|
382
281
|
constructor(options?: MatcherOptions);
|
|
383
282
|
|
|
384
283
|
/**
|
|
385
|
-
* Push a new tag onto the path
|
|
284
|
+
* Push a new tag onto the path.
|
|
386
285
|
* @param tagName - Name of the tag
|
|
387
286
|
* @param attrValues - Attribute key-value pairs for current node (optional)
|
|
388
287
|
* @param namespace - Namespace for the tag (optional)
|
|
389
|
-
*
|
|
288
|
+
*
|
|
390
289
|
* @example
|
|
391
290
|
* ```typescript
|
|
392
291
|
* matcher.push("user", { id: "123", type: "admin" });
|
|
@@ -397,146 +296,63 @@ export class Matcher {
|
|
|
397
296
|
push(tagName: string, attrValues?: Record<string, any> | null, namespace?: string | null): void;
|
|
398
297
|
|
|
399
298
|
/**
|
|
400
|
-
* Pop the last tag from the path
|
|
299
|
+
* Pop the last tag from the path.
|
|
401
300
|
* @returns The popped node or undefined if path is empty
|
|
402
301
|
*/
|
|
403
302
|
pop(): PathNode | undefined;
|
|
404
303
|
|
|
405
304
|
/**
|
|
406
|
-
* Update current node's attribute values
|
|
407
|
-
* Useful when attributes are parsed after push
|
|
305
|
+
* Update current node's attribute values.
|
|
306
|
+
* Useful when attributes are parsed after push.
|
|
408
307
|
* @param attrValues - Attribute values
|
|
409
308
|
*/
|
|
410
309
|
updateCurrent(attrValues: Record<string, any>): void;
|
|
411
310
|
|
|
412
311
|
/**
|
|
413
|
-
*
|
|
414
|
-
* @returns Current tag name or undefined if path is empty
|
|
312
|
+
* Reset the path to empty.
|
|
415
313
|
*/
|
|
416
|
-
|
|
314
|
+
reset(): void;
|
|
417
315
|
|
|
418
316
|
/**
|
|
419
|
-
*
|
|
420
|
-
* @returns
|
|
317
|
+
* Create a snapshot of current state.
|
|
318
|
+
* @returns State snapshot that can be restored later
|
|
421
319
|
*/
|
|
422
|
-
|
|
320
|
+
snapshot(): MatcherSnapshot;
|
|
423
321
|
|
|
424
322
|
/**
|
|
425
|
-
*
|
|
426
|
-
* @param
|
|
427
|
-
* @returns Attribute value or undefined
|
|
323
|
+
* Restore state from snapshot.
|
|
324
|
+
* @param snapshot - State snapshot from previous snapshot() call
|
|
428
325
|
*/
|
|
429
|
-
|
|
326
|
+
restore(snapshot: MatcherSnapshot): void;
|
|
430
327
|
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
*/
|
|
328
|
+
getCurrentTag(): string | undefined;
|
|
329
|
+
getCurrentNamespace(): string | undefined;
|
|
330
|
+
getAttrValue(attrName: string): any;
|
|
435
331
|
hasAttr(attrName: string): boolean;
|
|
436
|
-
|
|
437
|
-
/**
|
|
438
|
-
* Get current node's sibling position (child index in parent)
|
|
439
|
-
* @returns Position index or -1 if path is empty
|
|
440
|
-
*/
|
|
441
332
|
getPosition(): number;
|
|
442
|
-
|
|
443
|
-
/**
|
|
444
|
-
* Get current node's repeat counter (occurrence count of this tag name)
|
|
445
|
-
* @returns Counter value or -1 if path is empty
|
|
446
|
-
*/
|
|
447
333
|
getCounter(): number;
|
|
448
|
-
|
|
449
|
-
/**
|
|
450
|
-
* Get current node's sibling index (alias for getPosition for backward compatibility)
|
|
451
|
-
* @returns Index or -1 if path is empty
|
|
452
|
-
* @deprecated Use getPosition() or getCounter() instead
|
|
453
|
-
*/
|
|
334
|
+
/** @deprecated Use getPosition() or getCounter() instead */
|
|
454
335
|
getIndex(): number;
|
|
455
|
-
|
|
456
|
-
/**
|
|
457
|
-
* Get current path depth
|
|
458
|
-
* @returns Number of nodes in the path
|
|
459
|
-
*/
|
|
460
336
|
getDepth(): number;
|
|
461
|
-
|
|
462
|
-
/**
|
|
463
|
-
* Get path as string
|
|
464
|
-
* @param separator - Optional separator (uses default if not provided)
|
|
465
|
-
* @param includeNamespace - Whether to include namespace in output
|
|
466
|
-
* @returns Path string (e.g., "root.users.user" or "ns:root.ns:users.user")
|
|
467
|
-
*/
|
|
468
337
|
toString(separator?: string, includeNamespace?: boolean): string;
|
|
469
|
-
|
|
470
|
-
/**
|
|
471
|
-
* Get path as array of tag names
|
|
472
|
-
* @returns Array of tag names
|
|
473
|
-
*/
|
|
474
338
|
toArray(): string[];
|
|
339
|
+
matches(expression: Expression): boolean;
|
|
340
|
+
matchesAny(exprSet: ExpressionSet): boolean;
|
|
475
341
|
|
|
476
342
|
/**
|
|
477
|
-
*
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
* Match current path against an Expression
|
|
483
|
-
* @param expression - The expression to match against
|
|
484
|
-
* @returns True if current path matches the expression
|
|
485
|
-
*
|
|
343
|
+
* Return the read-only {@link MatcherView} for this matcher.
|
|
344
|
+
*
|
|
345
|
+
* The same instance is returned on every call — no allocation occurs.
|
|
346
|
+
* Pass this to user callbacks; it always reflects current parser state.
|
|
347
|
+
*
|
|
486
348
|
* @example
|
|
487
349
|
* ```typescript
|
|
488
|
-
* const
|
|
489
|
-
*
|
|
490
|
-
*
|
|
491
|
-
* matcher.push("root");
|
|
492
|
-
* matcher.push("users");
|
|
493
|
-
* matcher.push("user", { id: "123" });
|
|
494
|
-
*
|
|
495
|
-
* matcher.matches(expr); // true
|
|
350
|
+
* const view = matcher.readOnly();
|
|
351
|
+
* // same reference every time — safe to cache
|
|
352
|
+
* view === matcher.readOnly(); // true
|
|
496
353
|
* ```
|
|
497
354
|
*/
|
|
498
|
-
|
|
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;
|
|
524
|
-
/**
|
|
525
|
-
* Create a snapshot of current state
|
|
526
|
-
* @returns State snapshot that can be restored later
|
|
527
|
-
*/
|
|
528
|
-
snapshot(): MatcherSnapshot;
|
|
529
|
-
|
|
530
|
-
/**
|
|
531
|
-
* Restore state from snapshot
|
|
532
|
-
* @param snapshot - State snapshot from previous snapshot() call
|
|
533
|
-
*/
|
|
534
|
-
restore(snapshot: MatcherSnapshot): void;
|
|
535
|
-
|
|
536
|
-
/**
|
|
537
|
-
* Return a read-only view of this matcher.
|
|
538
|
-
*/
|
|
539
|
-
readOnly(): ReadOnlyMatcher;
|
|
355
|
+
readOnly(): MatcherView;
|
|
540
356
|
}
|
|
541
357
|
|
|
542
358
|
/**
|
|
@@ -673,7 +489,25 @@ export class ExpressionSet {
|
|
|
673
489
|
* }
|
|
674
490
|
* ```
|
|
675
491
|
*/
|
|
676
|
-
matchesAny(matcher: Matcher |
|
|
492
|
+
matchesAny(matcher: Matcher | MatcherView): boolean;
|
|
493
|
+
|
|
494
|
+
/**
|
|
495
|
+
* Find the first expression in the set that matches the matcher's current path.
|
|
496
|
+
*
|
|
497
|
+
* Uses the pre-built index to evaluate only the relevant bucket(s):
|
|
498
|
+
* 1. Exact depth + tag — O(1) lookup
|
|
499
|
+
* 2. Depth-matched wildcard tag — O(1) lookup
|
|
500
|
+
* 3. Deep-wildcard expressions — always scanned (typically a small list)
|
|
501
|
+
*
|
|
502
|
+
* @param matcher - A `Matcher` instance or a `ReadOnlyMatcher` view
|
|
503
|
+
* @returns Expression if at least one expression matches the current path
|
|
504
|
+
*
|
|
505
|
+
* @example
|
|
506
|
+
* ```typescript
|
|
507
|
+
* const node = stopNodes.findMatch(matcher);
|
|
508
|
+
* ```
|
|
509
|
+
*/
|
|
510
|
+
findMatch(matcher: Matcher | MatcherView): Expression;
|
|
677
511
|
}
|
|
678
512
|
|
|
679
513
|
/**
|
|
@@ -682,6 +516,7 @@ export class ExpressionSet {
|
|
|
682
516
|
declare const _default: {
|
|
683
517
|
Expression: typeof Expression;
|
|
684
518
|
Matcher: typeof Matcher;
|
|
519
|
+
MatcherView: typeof MatcherView;
|
|
685
520
|
ExpressionSet: typeof ExpressionSet;
|
|
686
521
|
};
|
|
687
522
|
|