@btc-embedded/cdk-extensions 0.23.3 → 0.23.5

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.
Files changed (135) hide show
  1. package/.jsii +41 -41
  2. package/CHANGELOG.md +14 -0
  3. package/assets/cli/catnip.js +154 -166
  4. package/lib/constructs/EventPipe.js +1 -1
  5. package/lib/constructs/ExportedService.js +1 -1
  6. package/lib/constructs/S3Bucket.js +1 -1
  7. package/lib/constructs/SecureRestApi.js +1 -1
  8. package/lib/constructs/SecureRestApiV2.js +1 -1
  9. package/lib/constructs/api-keys/ApiKeyClientAuthorization.js +1 -1
  10. package/lib/constructs/api-keys/ApiKeyManagement.js +1 -1
  11. package/lib/constructs/api-keys/ApiKeyPreTokenHandler.js +1 -1
  12. package/lib/constructs/api-keys/ApiKeyStore.js +1 -1
  13. package/lib/extensions/ApiGatewayExtension.js +1 -1
  14. package/lib/extensions/ApplicationContainer.js +1 -1
  15. package/lib/extensions/ApplicationLoadBalancerExtension.js +1 -1
  16. package/lib/extensions/ApplicationLoadBalancerExtensionV2.js +1 -1
  17. package/lib/extensions/CloudMapExtension.js +1 -1
  18. package/lib/extensions/DeactivatableServiceExtension.js +1 -1
  19. package/lib/extensions/DeploymentConfigExtension.js +1 -1
  20. package/lib/extensions/DocumentDbAccessExtension.js +1 -1
  21. package/lib/extensions/DomainEventMessagingExtension.js +1 -1
  22. package/lib/extensions/EfsMountExtension.js +1 -1
  23. package/lib/extensions/ExtraContainerExtension.js +1 -1
  24. package/lib/extensions/HTTPApiExtension.js +1 -1
  25. package/lib/extensions/LogExtension.js +1 -1
  26. package/lib/extensions/ModifyContainerDefinitionExtension.js +1 -1
  27. package/lib/extensions/ModifyTaskDefinitionExtension.js +1 -1
  28. package/lib/extensions/OpenIdExtension.js +1 -1
  29. package/lib/extensions/OpenTelemetryExtension.js +1 -1
  30. package/lib/extensions/PostgresDbAccessExtension.js +1 -1
  31. package/lib/extensions/SharedVolumeExtension.js +1 -1
  32. package/lib/extensions/TcpKeepAliveExtension.js +1 -1
  33. package/lib/platform/ApiGateway.js +1 -1
  34. package/lib/platform/ApiGatewayVpcLink.js +2 -2
  35. package/lib/platform/ApplicationLoadBalancer.js +1 -1
  36. package/lib/platform/ApplicationLoadBalancerV2.d.ts +1 -0
  37. package/lib/platform/ApplicationLoadBalancerV2.js +10 -3
  38. package/lib/platform/AuroraPostgresDB.js +5 -4
  39. package/lib/platform/BTCLogGroup.js +1 -1
  40. package/lib/platform/CognitoUserPool.js +2 -2
  41. package/lib/platform/DefaultUserPoolClients.js +1 -1
  42. package/lib/platform/DocumentDB.js +2 -2
  43. package/lib/platform/EcsCluster.js +1 -1
  44. package/lib/platform/EfsFileSystem.js +1 -1
  45. package/lib/platform/HostedZone.js +1 -1
  46. package/lib/platform/PrivateDnsNamespace.js +1 -1
  47. package/lib/platform/ResourceServer.js +1 -1
  48. package/lib/platform/Vpc.js +1 -1
  49. package/lib/platform/VpcV2.js +1 -1
  50. package/lib/stacks/ApplicationStack.js +1 -1
  51. package/lib/utils/BasePlatformStackResolver.js +1 -1
  52. package/lib/utils/StackParameter.js +1 -1
  53. package/node_modules/@nodable/entities/README.md +41 -0
  54. package/node_modules/@nodable/entities/package.json +54 -0
  55. package/node_modules/@nodable/entities/src/EntityDecoder.js +543 -0
  56. package/node_modules/@nodable/entities/src/EntityEncoder.js +194 -0
  57. package/node_modules/@nodable/entities/src/entities.js +1177 -0
  58. package/node_modules/@nodable/entities/src/entityTries.js +49 -0
  59. package/node_modules/@nodable/entities/src/index.d.ts +264 -0
  60. package/node_modules/@nodable/entities/src/index.js +29 -0
  61. package/node_modules/fast-xml-builder/CHANGELOG.md +40 -0
  62. package/node_modules/fast-xml-builder/LICENSE +21 -0
  63. package/node_modules/fast-xml-builder/README.md +74 -0
  64. package/node_modules/fast-xml-builder/lib/fxb.cjs +1 -0
  65. package/node_modules/fast-xml-builder/lib/fxb.d.cts +270 -0
  66. package/node_modules/fast-xml-builder/lib/fxb.min.js +2 -0
  67. package/node_modules/fast-xml-builder/lib/fxb.min.js.map +1 -0
  68. package/node_modules/fast-xml-builder/package.json +81 -0
  69. package/node_modules/fast-xml-builder/src/fxb.d.ts +270 -0
  70. package/node_modules/fast-xml-builder/src/fxb.js +599 -0
  71. package/node_modules/fast-xml-builder/src/ignoreAttributes.js +18 -0
  72. package/node_modules/fast-xml-builder/src/orderedJs2Xml.js +359 -0
  73. package/node_modules/fast-xml-builder/src/util.js +16 -0
  74. package/node_modules/fast-xml-parser/CHANGELOG.md +165 -0
  75. package/node_modules/fast-xml-parser/README.md +21 -44
  76. package/node_modules/fast-xml-parser/lib/fxbuilder.min.js +1 -1
  77. package/node_modules/fast-xml-parser/lib/fxbuilder.min.js.map +1 -1
  78. package/node_modules/fast-xml-parser/lib/fxp.cjs +1 -1
  79. package/node_modules/fast-xml-parser/lib/fxp.d.cts +343 -31
  80. package/node_modules/fast-xml-parser/lib/fxp.min.js +1 -1
  81. package/node_modules/fast-xml-parser/lib/fxp.min.js.map +1 -1
  82. package/node_modules/fast-xml-parser/lib/fxparser.min.js +1 -1
  83. package/node_modules/fast-xml-parser/lib/fxparser.min.js.map +1 -1
  84. package/node_modules/fast-xml-parser/lib/fxvalidator.min.js +1 -1
  85. package/node_modules/fast-xml-parser/lib/fxvalidator.min.js.map +1 -1
  86. package/node_modules/fast-xml-parser/package.json +13 -8
  87. package/node_modules/fast-xml-parser/src/fxp.d.ts +335 -30
  88. package/node_modules/fast-xml-parser/src/fxp.js +1 -1
  89. package/node_modules/fast-xml-parser/src/util.js +18 -25
  90. package/node_modules/fast-xml-parser/src/v6/EntitiesParser.js +89 -87
  91. package/node_modules/fast-xml-parser/src/v6/OptionsBuilder.js +10 -10
  92. package/node_modules/fast-xml-parser/src/v6/OutputBuilders/BaseOutputBuilder.js +23 -23
  93. package/node_modules/fast-xml-parser/src/v6/OutputBuilders/JsArrBuilder.js +29 -29
  94. package/node_modules/fast-xml-parser/src/v6/OutputBuilders/JsMinArrBuilder.js +1 -1
  95. package/node_modules/fast-xml-parser/src/v6/OutputBuilders/JsObjBuilder.js +39 -39
  96. package/node_modules/fast-xml-parser/src/v6/OutputBuilders/ParserOptionsBuilder.js +21 -21
  97. package/node_modules/fast-xml-parser/src/v6/XMLParser.js +22 -22
  98. package/node_modules/fast-xml-parser/src/v6/valueParsers/EntitiesParser.js +85 -85
  99. package/node_modules/fast-xml-parser/src/validator.js +34 -34
  100. package/node_modules/fast-xml-parser/src/xmlbuilder/json2xml.js +5 -284
  101. package/node_modules/fast-xml-parser/src/xmlparser/DocTypeReader.js +335 -293
  102. package/node_modules/fast-xml-parser/src/xmlparser/OptionsBuilder.js +160 -43
  103. package/node_modules/fast-xml-parser/src/xmlparser/OrderedObjParser.js +540 -308
  104. package/node_modules/fast-xml-parser/src/xmlparser/XMLParser.js +26 -26
  105. package/node_modules/fast-xml-parser/src/xmlparser/node2json.js +99 -41
  106. package/node_modules/fast-xml-parser/src/xmlparser/xmlNode.js +10 -10
  107. package/node_modules/path-expression-matcher/LICENSE +21 -0
  108. package/node_modules/path-expression-matcher/README.md +872 -0
  109. package/node_modules/path-expression-matcher/lib/pem.cjs +1 -0
  110. package/node_modules/path-expression-matcher/lib/pem.d.cts +634 -0
  111. package/node_modules/path-expression-matcher/lib/pem.min.js +2 -0
  112. package/node_modules/path-expression-matcher/lib/pem.min.js.map +1 -0
  113. package/node_modules/path-expression-matcher/package.json +78 -0
  114. package/node_modules/path-expression-matcher/src/Expression.js +232 -0
  115. package/node_modules/path-expression-matcher/src/ExpressionSet.js +209 -0
  116. package/node_modules/path-expression-matcher/src/Matcher.js +570 -0
  117. package/node_modules/path-expression-matcher/src/index.d.ts +523 -0
  118. package/node_modules/path-expression-matcher/src/index.js +29 -0
  119. package/node_modules/strnum/CHANGELOG.md +12 -2
  120. package/node_modules/strnum/README.md +1 -0
  121. package/node_modules/strnum/package.json +5 -4
  122. package/node_modules/strnum/strnum.js +99 -65
  123. package/node_modules/xml-naming/README.md +189 -0
  124. package/node_modules/xml-naming/package.json +54 -0
  125. package/node_modules/xml-naming/src/index.d.ts +74 -0
  126. package/node_modules/xml-naming/src/index.js +270 -0
  127. package/package.json +3 -2
  128. package/renovate.json5 +1 -0
  129. package/node_modules/fast-xml-parser/src/xmlbuilder/orderedJs2Xml.js +0 -134
  130. package/node_modules/strnum/.github/SECURITY.md +0 -5
  131. package/node_modules/strnum/.vscode/launch.json +0 -25
  132. package/node_modules/strnum/algo.stflow +0 -84
  133. package/node_modules/strnum/strnum.test.js +0 -173
  134. package/node_modules/strnum/test.js +0 -9
  135. /package/node_modules/{fast-xml-parser/src/xmlbuilder → fast-xml-builder/src}/prettifyJs2Xml.js +0 -0
@@ -0,0 +1,872 @@
1
+ # path-expression-matcher
2
+
3
+ Efficient path tracking and pattern matching for XML, JSON, YAML or any other parsers.
4
+
5
+ ## 🎯 Purpose
6
+
7
+ `path-expression-matcher` provides three core classes for tracking and matching paths:
8
+
9
+ - **`Expression`**: Parses and stores pattern expressions (e.g., `"root.users.user[id]"`)
10
+ - **`Matcher`**: Tracks current path during parsing and matches against expressions
11
+ - **`MatcherView`**: A lightweight read-only view of a `Matcher`, safe to pass to callbacks
12
+
13
+ Compatible with [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and similar tools.
14
+
15
+ ## 📦 Installation
16
+
17
+ ```bash
18
+ npm install path-expression-matcher
19
+ ```
20
+
21
+ ## 🚀 Quick Start
22
+
23
+ ```javascript
24
+ import { Expression, Matcher } from 'path-expression-matcher';
25
+
26
+ // Create expression (parse once, reuse many times)
27
+ const expr = new Expression("root.users.user");
28
+
29
+ // Create matcher (tracks current path)
30
+ const matcher = new Matcher();
31
+
32
+ matcher.push("root");
33
+ matcher.push("users");
34
+ matcher.push("user", { id: "123" });
35
+
36
+ // Match current path against expression
37
+ if (matcher.matches(expr)) {
38
+ console.log("Match found!");
39
+ console.log("Current path:", matcher.toString()); // "root.users.user"
40
+ }
41
+
42
+ // Namespace support
43
+ const nsExpr = new Expression("soap::Envelope.soap::Body..ns::UserId");
44
+ matcher.push("Envelope", null, "soap");
45
+ matcher.push("Body", null, "soap");
46
+ matcher.push("UserId", null, "ns");
47
+ console.log(matcher.toString()); // "soap:Envelope.soap:Body.ns:UserId"
48
+ ```
49
+
50
+ ## 📖 Pattern Syntax
51
+
52
+ ### Basic Paths
53
+
54
+ ```javascript
55
+ "root.users.user" // Exact path match
56
+ "*.users.user" // Wildcard: any parent
57
+ "root.*.user" // Wildcard: any middle
58
+ "root.users.*" // Wildcard: any child
59
+ ```
60
+
61
+ ### Deep Wildcard
62
+
63
+ ```javascript
64
+ "..user" // user anywhere in tree
65
+ "root..user" // user anywhere under root
66
+ "..users..user" // users somewhere, then user below it
67
+ ```
68
+
69
+ ### Attribute Matching
70
+
71
+ ```javascript
72
+ "user[id]" // user with "id" attribute
73
+ "user[type=admin]" // user with type="admin" (current node only)
74
+ "root[lang]..user" // user under root that has "lang" attribute
75
+ ```
76
+
77
+ ### Position Selectors
78
+
79
+ ```javascript
80
+ "user:first" // First user (counter=0)
81
+ "user:nth(2)" // Third user (counter=2, zero-based)
82
+ "user:odd" // Odd-numbered users (counter=1,3,5...)
83
+ "user:even" // Even-numbered users (counter=0,2,4...)
84
+ "root.users.user:first" // First user under users
85
+ ```
86
+
87
+ **Note:** Position selectors use the **counter** (occurrence count of the tag name), not the position (child index). For example, in `<root><a/><b/><a/></root>`, the second `<a/>` has position=2 but counter=1.
88
+
89
+ ### Namespaces
90
+
91
+ ```javascript
92
+ "ns::user" // user with namespace "ns"
93
+ "soap::Envelope" // Envelope with namespace "soap"
94
+ "ns::user[id]" // user with namespace "ns" and "id" attribute
95
+ "ns::user:first" // First user with namespace "ns"
96
+ "*::user" // user with any namespace
97
+ "..ns::item" // item with namespace "ns" anywhere in tree
98
+ "soap::Envelope.soap::Body" // Nested namespaced elements
99
+ "ns::first" // Tag named "first" with namespace "ns" (NO ambiguity!)
100
+ ```
101
+
102
+ **Namespace syntax:**
103
+ - Use **double colon (::)** for namespace: `ns::tag`
104
+ - Use **single colon (:)** for position: `tag:first`
105
+ - Combined: `ns::tag:first` (namespace + tag + position)
106
+
107
+ **Namespace matching rules:**
108
+ - Pattern `ns::user` matches only nodes with namespace "ns" and tag "user"
109
+ - Pattern `user` (no namespace) matches nodes with tag "user" regardless of namespace
110
+ - Pattern `*::user` matches tag "user" with any namespace (wildcard namespace)
111
+ - Namespaces are tracked separately for counter/position (e.g., `ns1::item` and `ns2::item` have independent counters)
112
+
113
+ ### Wildcard Differences
114
+
115
+ **Single wildcard (`*`)** - Matches exactly ONE level:
116
+ - `"*.fix1"` matches `root.fix1` (2 levels) ✅
117
+ - `"*.fix1"` does NOT match `root.another.fix1` (3 levels) ❌
118
+ - Path depth MUST equal pattern depth
119
+
120
+ **Deep wildcard (`..`)** - Matches ZERO or MORE levels:
121
+ - `"..fix1"` matches `root.fix1` ✅
122
+ - `"..fix1"` matches `root.another.fix1` ✅
123
+ - `"..fix1"` matches `a.b.c.d.fix1` ✅
124
+ - Works at any depth
125
+
126
+ ### Combined Patterns
127
+
128
+ ```javascript
129
+ "..user[id]:first" // First user with id, anywhere
130
+ "root..user[type=admin]" // Admin user under root
131
+ "ns::user[id]:first" // First namespaced user with id
132
+ "soap::Envelope..ns::UserId" // UserId with namespace ns under SOAP envelope
133
+ ```
134
+
135
+ ## 🔧 API Reference
136
+
137
+ ### Expression
138
+
139
+ #### Constructor
140
+
141
+ ```javascript
142
+ new Expression(pattern, options = {}, data)
143
+ ```
144
+
145
+ **Parameters:**
146
+ - `pattern` (string): Pattern to parse
147
+ - `options.separator` (string): Path separator (default: `'.'`)
148
+
149
+ **Example:**
150
+ ```javascript
151
+ const expr1 = new Expression("root.users.user");
152
+ const expr2 = new Expression("root/users/user", { separator: '/' });
153
+ const expr3 = new Expression("root/users/user", { separator: '/' }, { extra: "data"});
154
+ console.log(expr3.data) // { extra: "data" }
155
+ ```
156
+
157
+ #### Methods
158
+
159
+ - `hasDeepWildcard()` → boolean
160
+ - `hasAttributeCondition()` → boolean
161
+ - `hasPositionSelector()` → boolean
162
+ - `toString()` → string
163
+
164
+ ### Matcher
165
+
166
+ #### Constructor
167
+
168
+ ```javascript
169
+ new Matcher(options)
170
+ ```
171
+
172
+ **Parameters:**
173
+ - `options.separator` (string): Default path separator (default: `'.'`)
174
+
175
+ #### Path Tracking Methods
176
+
177
+ ##### `push(tagName, attrValues, namespace)`
178
+
179
+ Add a tag to the current path. Position and counter are automatically calculated.
180
+
181
+ **Parameters:**
182
+ - `tagName` (string): Tag name
183
+ - `attrValues` (object, optional): Attribute key-value pairs (current node only)
184
+ - `namespace` (string, optional): Namespace for the tag
185
+
186
+ **Example:**
187
+ ```javascript
188
+ matcher.push("user", { id: "123", type: "admin" });
189
+ matcher.push("item"); // No attributes
190
+ matcher.push("Envelope", null, "soap"); // With namespace
191
+ matcher.push("Body", { version: "1.1" }, "soap"); // With both
192
+ ```
193
+
194
+ **Position vs Counter:**
195
+ - **Position**: The child index in the parent (0, 1, 2, 3...)
196
+ - **Counter**: How many times this tag name appeared at this level (0, 1, 2...)
197
+
198
+ Example:
199
+ ```xml
200
+ <root>
201
+ <a/> <!-- position=0, counter=0 -->
202
+ <b/> <!-- position=1, counter=0 -->
203
+ <a/> <!-- position=2, counter=1 -->
204
+ </root>
205
+ ```
206
+
207
+ ##### `pop()`
208
+
209
+ Remove the last tag from the path.
210
+
211
+ ```javascript
212
+ matcher.pop();
213
+ ```
214
+
215
+ ##### `updateCurrent(attrValues)`
216
+
217
+ Update current node's attributes (useful when attributes are parsed after push).
218
+
219
+ ```javascript
220
+ matcher.push("user"); // Don't know values yet
221
+ // ... parse attributes ...
222
+ matcher.updateCurrent({ id: "123" });
223
+ ```
224
+
225
+ ##### `reset()`
226
+
227
+ Clear the entire path.
228
+
229
+ ```javascript
230
+ matcher.reset();
231
+ ```
232
+
233
+ #### Query Methods
234
+
235
+ ##### `matches(expression)`
236
+
237
+ Check if current path matches an Expression.
238
+
239
+ ```javascript
240
+ const expr = new Expression("root.users.user");
241
+ if (matcher.matches(expr)) {
242
+ // Current path matches
243
+ }
244
+ ```
245
+
246
+ #### `matchesAny(exprSet)` → `boolean`
247
+
248
+ Please check `ExpressionSet` class for more details.
249
+
250
+ ```javascript
251
+ const matcher = new Matcher();
252
+ const exprSet = new ExpressionSet();
253
+ exprSet.add(new Expression("root.users.user"));
254
+ exprSet.add(new Expression("root.config.*"));
255
+ exprSet.seal();
256
+
257
+ if (matcher.matchesAny(exprSet)) {
258
+ // Current path matches any expression in the set
259
+ }
260
+ ```
261
+
262
+ ##### `getCurrentTag()`
263
+
264
+ Get current tag name.
265
+
266
+ ```javascript
267
+ const tag = matcher.getCurrentTag(); // "user"
268
+ ```
269
+
270
+ ##### `getCurrentNamespace()`
271
+
272
+ Get current namespace.
273
+
274
+ ```javascript
275
+ const ns = matcher.getCurrentNamespace(); // "soap" or undefined
276
+ ```
277
+
278
+ ##### `getAttrValue(attrName)`
279
+
280
+ Get attribute value of current node.
281
+
282
+ ```javascript
283
+ const id = matcher.getAttrValue("id"); // "123"
284
+ ```
285
+
286
+ ##### `hasAttr(attrName)`
287
+
288
+ Check if current node has an attribute.
289
+
290
+ ```javascript
291
+ if (matcher.hasAttr("id")) {
292
+ // Current node has "id" attribute
293
+ }
294
+ ```
295
+
296
+ ##### `getPosition()`
297
+
298
+ Get sibling position of current node (child index in parent).
299
+
300
+ ```javascript
301
+ const position = matcher.getPosition(); // 0, 1, 2, ...
302
+ ```
303
+
304
+ ##### `getCounter()`
305
+
306
+ Get repeat counter of current node (occurrence count of this tag name).
307
+
308
+ ```javascript
309
+ const counter = matcher.getCounter(); // 0, 1, 2, ...
310
+ ```
311
+
312
+ ##### `getIndex()` (deprecated)
313
+
314
+ Alias for `getPosition()`. Use `getPosition()` or `getCounter()` instead for clarity.
315
+
316
+ ```javascript
317
+ const index = matcher.getIndex(); // Same as getPosition()
318
+ ```
319
+
320
+ ##### `getDepth()`
321
+
322
+ Get current path depth.
323
+
324
+ ```javascript
325
+ const depth = matcher.getDepth(); // 3 for "root.users.user"
326
+ ```
327
+
328
+ ##### `toString(separator?, includeNamespace?)`
329
+
330
+ Get path as string.
331
+
332
+ **Parameters:**
333
+ - `separator` (string, optional): Path separator (uses default if not provided)
334
+ - `includeNamespace` (boolean, optional): Whether to include namespaces (default: true)
335
+
336
+ ```javascript
337
+ const path = matcher.toString(); // "root.ns:user.item"
338
+ const path2 = matcher.toString('/'); // "root/ns:user/item"
339
+ const path3 = matcher.toString('.', false); // "root.user.item" (no namespaces)
340
+ ```
341
+
342
+ ##### `toArray()`
343
+
344
+ Get path as array.
345
+
346
+ ```javascript
347
+ const arr = matcher.toArray(); // ["root", "users", "user"]
348
+ ```
349
+
350
+ #### State Management
351
+
352
+ ##### `snapshot()`
353
+
354
+ Create a snapshot of current state.
355
+
356
+ ```javascript
357
+ const snapshot = matcher.snapshot();
358
+ ```
359
+
360
+ ##### `restore(snapshot)`
361
+
362
+ Restore from a snapshot.
363
+
364
+ ```javascript
365
+ matcher.restore(snapshot);
366
+ ```
367
+
368
+ #### Read-Only Access
369
+
370
+ ##### `readOnly()`
371
+
372
+ Returns a **`MatcherView`** — a lightweight, live read-only view of the matcher. All query and inspection methods work normally and always reflect the current state of the underlying matcher. Mutation methods (`push`, `pop`, `reset`, `updateCurrent`, `restore`) simply don't exist on `MatcherView`, so misuse is caught at **compile time** by TypeScript rather than at runtime.
373
+
374
+ The **same instance** is returned on every call — no allocation occurs per invocation. This is the recommended way to share the matcher with callbacks, plugins, or any external code that only needs to inspect the current path.
375
+
376
+ ```javascript
377
+ const view = matcher.readOnly();
378
+ // Same reference every time — safe to cache
379
+ view === matcher.readOnly(); // true
380
+ ```
381
+
382
+ **What works on the view:**
383
+
384
+ ```javascript
385
+ view.matches(expr) // ✓ pattern matching
386
+ view.getCurrentTag() // ✓ current tag name
387
+ view.getCurrentNamespace() // ✓ current namespace
388
+ view.getAttrValue("id") // ✓ attribute value
389
+ view.hasAttr("id") // ✓ attribute presence check
390
+ view.getPosition() // ✓ sibling position
391
+ view.getCounter() // ✓ occurrence counter
392
+ view.getDepth() // ✓ path depth
393
+ view.toString() // ✓ path as string
394
+ view.toArray() // ✓ path as array
395
+ ```
396
+
397
+ **What doesn't exist (compile-time error in TypeScript):**
398
+
399
+ ```javascript
400
+ view.push("child", {}) // ✗ Property 'push' does not exist on type 'MatcherView'
401
+ view.pop() // ✗ Property 'pop' does not exist on type 'MatcherView'
402
+ view.reset() // ✗ Property 'reset' does not exist on type 'MatcherView'
403
+ view.updateCurrent({}) // ✗ Property 'updateCurrent' does not exist on type 'MatcherView'
404
+ view.restore(snapshot) // ✗ Property 'restore' does not exist on type 'MatcherView'
405
+ ```
406
+
407
+ **The view is live** — it always reflects the current state of the underlying matcher.
408
+
409
+ ```javascript
410
+ const matcher = new Matcher();
411
+ const view = matcher.readOnly();
412
+
413
+ matcher.push("root");
414
+ view.getDepth(); // 1 — immediately reflects the push
415
+ matcher.push("users");
416
+ view.getDepth(); // 2 — still live
417
+ ```
418
+
419
+ ## 💡 Usage Examples
420
+
421
+ ### Example 1: XML Parser with stopNodes
422
+
423
+ ```javascript
424
+ import { XMLParser } from 'fast-xml-parser';
425
+ import { Expression, Matcher } from 'path-expression-matcher';
426
+
427
+ class MyParser {
428
+ constructor() {
429
+ this.matcher = new Matcher();
430
+
431
+ // Pre-compile stop node patterns
432
+ this.stopNodeExpressions = [
433
+ new Expression("html.body.script"),
434
+ new Expression("html.body.style"),
435
+ new Expression("..svg"),
436
+ ];
437
+ }
438
+
439
+ parseTag(tagName, attrs) {
440
+ this.matcher.push(tagName, attrs);
441
+
442
+ // Check if this is a stop node
443
+ for (const expr of this.stopNodeExpressions) {
444
+ if (this.matcher.matches(expr)) {
445
+ // Don't parse children, read as raw text
446
+ return this.readRawContent();
447
+ }
448
+ }
449
+
450
+ // Continue normal parsing
451
+ this.parseChildren();
452
+
453
+ this.matcher.pop();
454
+ }
455
+ }
456
+ ```
457
+
458
+ ### Example 2: Conditional Processing
459
+
460
+ ```javascript
461
+ const matcher = new Matcher();
462
+ const userExpr = new Expression("..user[type=admin]");
463
+ const firstItemExpr = new Expression("..item:first");
464
+
465
+ function processTag(tagName, value, attrs) {
466
+ matcher.push(tagName, attrs);
467
+
468
+ if (matcher.matches(userExpr)) {
469
+ value = enhanceAdminUser(value);
470
+ }
471
+
472
+ if (matcher.matches(firstItemExpr)) {
473
+ value = markAsFirst(value);
474
+ }
475
+
476
+ matcher.pop();
477
+ return value;
478
+ }
479
+ ```
480
+
481
+ ### Example 3: Path-based Filtering
482
+
483
+ ```javascript
484
+ const patterns = [
485
+ new Expression("data.users.user"),
486
+ new Expression("data.posts.post"),
487
+ new Expression("..comment[approved=true]"),
488
+ ];
489
+
490
+ function shouldInclude(matcher) {
491
+ return patterns.some(expr => matcher.matches(expr));
492
+ }
493
+ ```
494
+
495
+ ### Example 4: Custom Separator
496
+
497
+ ```javascript
498
+ const matcher = new Matcher({ separator: '/' });
499
+ const expr = new Expression("root/config/database", { separator: '/' });
500
+
501
+ matcher.push("root");
502
+ matcher.push("config");
503
+ matcher.push("database");
504
+
505
+ console.log(matcher.toString()); // "root/config/database"
506
+ console.log(matcher.matches(expr)); // true
507
+ ```
508
+
509
+ ### Example 5: Attribute Checking
510
+
511
+ ```javascript
512
+ const matcher = new Matcher();
513
+ matcher.push("root");
514
+ matcher.push("user", { id: "123", type: "admin", status: "active" });
515
+
516
+ // Check attribute existence (current node only)
517
+ console.log(matcher.hasAttr("id")); // true
518
+ console.log(matcher.hasAttr("email")); // false
519
+
520
+ // Get attribute value (current node only)
521
+ console.log(matcher.getAttrValue("type")); // "admin"
522
+
523
+ // Match by attribute
524
+ const expr1 = new Expression("user[id]");
525
+ console.log(matcher.matches(expr1)); // true
526
+
527
+ const expr2 = new Expression("user[type=admin]");
528
+ console.log(matcher.matches(expr2)); // true
529
+ ```
530
+
531
+ ### Example 6: Position vs Counter
532
+
533
+ ```javascript
534
+ const matcher = new Matcher();
535
+ matcher.push("root");
536
+
537
+ // Mixed tags at same level
538
+ matcher.push("item"); // position=0, counter=0 (first item)
539
+ matcher.pop();
540
+
541
+ matcher.push("div"); // position=1, counter=0 (first div)
542
+ matcher.pop();
543
+
544
+ matcher.push("item"); // position=2, counter=1 (second item)
545
+
546
+ console.log(matcher.getPosition()); // 2 (third child overall)
547
+ console.log(matcher.getCounter()); // 1 (second "item" specifically)
548
+
549
+ // :first uses counter, not position
550
+ const expr = new Expression("root.item:first");
551
+ console.log(matcher.matches(expr)); // false (counter=1, not 0)
552
+ ```
553
+
554
+ ### Example 8: Passing a Read-Only View to External Consumers
555
+
556
+ When passing the matcher into callbacks, plugins, or other code you don't control, use `readOnly()` to get a `MatcherView` — it can inspect but never mutate parser state.
557
+
558
+ ```javascript
559
+ import { Expression, Matcher } from 'path-expression-matcher';
560
+
561
+ const matcher = new Matcher();
562
+
563
+ const adminExpr = new Expression("..user[type=admin]");
564
+
565
+ function parseTag(tagName, attrs, onTag) {
566
+ matcher.push(tagName, attrs);
567
+
568
+ // Pass MatcherView — consumer can inspect but not mutate
569
+ onTag(matcher.readOnly());
570
+
571
+ matcher.pop();
572
+ }
573
+
574
+ // Safe consumer — can only read
575
+ function myPlugin(view) {
576
+ if (view.matches(adminExpr)) {
577
+ console.log("Admin at path:", view.toString());
578
+ console.log("Depth:", view.getDepth());
579
+ console.log("ID:", view.getAttrValue("id"));
580
+ }
581
+ }
582
+
583
+ // view.push(...) or view.reset() don't exist on MatcherView —
584
+ // TypeScript catches misuse at compile time.
585
+ parseTag("user", { id: "1", type: "admin" }, myPlugin);
586
+ ```
587
+
588
+ ```javascript
589
+ const matcher = new Matcher();
590
+ const soapExpr = new Expression("soap::Envelope.soap::Body..ns::UserId");
591
+
592
+ // Parse SOAP document
593
+ matcher.push("Envelope", { xmlns: "..." }, "soap");
594
+ matcher.push("Body", null, "soap");
595
+ matcher.push("GetUserRequest", null, "ns");
596
+ matcher.push("UserId", null, "ns");
597
+
598
+ // Match namespaced pattern
599
+ if (matcher.matches(soapExpr)) {
600
+ console.log("Found UserId in SOAP body");
601
+ console.log(matcher.toString()); // "soap:Envelope.soap:Body.ns:GetUserRequest.ns:UserId"
602
+ }
603
+
604
+ // Namespace-specific counters
605
+ matcher.reset();
606
+ matcher.push("root");
607
+ matcher.push("item", null, "ns1"); // ns1::item counter=0
608
+ matcher.pop();
609
+ matcher.push("item", null, "ns2"); // ns2::item counter=0 (different namespace)
610
+ matcher.pop();
611
+ matcher.push("item", null, "ns1"); // ns1::item counter=1
612
+
613
+ const firstNs1Item = new Expression("root.ns1::item:first");
614
+ console.log(matcher.matches(firstNs1Item)); // false (counter=1)
615
+
616
+ const secondNs1Item = new Expression("root.ns1::item:nth(1)");
617
+ console.log(matcher.matches(secondNs1Item)); // true
618
+
619
+ // NO AMBIGUITY: Tags named after position keywords
620
+ matcher.reset();
621
+ matcher.push("root");
622
+ matcher.push("first", null, "ns"); // Tag named "first" with namespace
623
+
624
+ const expr = new Expression("root.ns::first");
625
+ console.log(matcher.matches(expr)); // true - matches namespace "ns", tag "first"
626
+ ```
627
+
628
+ ## 🏗️ Architecture
629
+
630
+ ### Data Storage Strategy
631
+
632
+ **Ancestor nodes:** Store only tag name, position, and counter (minimal memory)
633
+ **Current node:** Store tag name, position, counter, and attribute values
634
+
635
+ This design minimizes memory usage:
636
+ - No attribute names stored (derived from values object when needed)
637
+ - Attribute values only for current node, not ancestors
638
+ - Attribute checking for ancestors is not supported (acceptable trade-off)
639
+ - For 1M nodes with 3 attributes each, saves ~50MB vs storing attribute names
640
+
641
+ ### Matching Strategy
642
+
643
+ Matching is performed **bottom-to-top** (from current node toward root):
644
+ 1. Start at current node
645
+ 2. Match segments from pattern end to start
646
+ 3. Attribute checking only works for current node (ancestors have no attribute data)
647
+ 4. Position selectors use **counter** (occurrence count), not position (child index)
648
+
649
+ ### Performance
650
+
651
+ - **Expression parsing:** One-time cost when Expression is created
652
+ - **Expression analysis:** Cached (hasDeepWildcard, hasAttributeCondition, hasPositionSelector)
653
+ - **Path tracking:** O(1) for push/pop operations
654
+ - **Pattern matching:** O(n*m) where n = path depth, m = pattern segments
655
+ - **Memory per ancestor node:** ~40-60 bytes (tag, position, counter only)
656
+ - **Memory per current node:** ~80-120 bytes (adds attribute values)
657
+
658
+ ## 🎓 Design Patterns
659
+
660
+ ### Pre-compile Patterns (Recommended)
661
+
662
+ ```javascript
663
+ // ✅ GOOD: Parse once, reuse many times
664
+ const expr = new Expression("..user[id]");
665
+
666
+ for (let i = 0; i < 1000; i++) {
667
+ if (matcher.matches(expr)) {
668
+ // ...
669
+ }
670
+ }
671
+ ```
672
+
673
+ ```javascript
674
+ // ❌ BAD: Parse on every iteration
675
+ for (let i = 0; i < 1000; i++) {
676
+ if (matcher.matches(new Expression("..user[id]"))) {
677
+ // ...
678
+ }
679
+ }
680
+ ```
681
+
682
+ ### Batch Pattern Checking with ExpressionSet (Recommended)
683
+
684
+ For checking multiple patterns on every tag, use `ExpressionSet` instead of a manual loop.
685
+ It pre-indexes expressions at build time so each call to `matchesAny()` does an O(1) bucket
686
+ lookup rather than a full O(N) scan:
687
+
688
+ ```javascript
689
+ import { Expression, ExpressionSet, Matcher } from 'path-expression-matcher';
690
+
691
+ // Build once at config/startup time
692
+ const stopNodes = new ExpressionSet();
693
+ stopNodes
694
+ .add(new Expression('root.users.user'))
695
+ .add(new Expression('root.config.*'))
696
+ .add(new Expression('..script'))
697
+ .seal(); // prevent accidental mutation during parsing
698
+
699
+ // Per-tag — hot path
700
+ if (stopNodes.matchesAny(matcher)) {
701
+ // handle stop node
702
+ }
703
+ ```
704
+
705
+ This replaces the manual loop pattern:
706
+
707
+ ```javascript
708
+ // ❌ Before — O(N) per tag
709
+ function isStopNode(expressions, matcher) {
710
+ for (let i = 0; i < expressions.length; i++) {
711
+ if (matcher.matches(expressions[i])) return true;
712
+ }
713
+ return false;
714
+ }
715
+
716
+ // ✅ After — O(1) lookup per tag
717
+ const stopNodes = new ExpressionSet();
718
+ stopNodes.addAll(expressions);
719
+ stopNodes.matchesAny(matcher);
720
+ //or matcher.matchesAny(stopNodes)
721
+ ```
722
+
723
+ ---
724
+
725
+ ## 📦 ExpressionSet API
726
+
727
+ `ExpressionSet` is an indexed collection of `Expression` objects designed for efficient
728
+ bulk matching. Build it once from your config, then call `matchesAny()` on every tag.
729
+
730
+ ### Constructor
731
+
732
+ ```javascript
733
+ const set = new ExpressionSet();
734
+ ```
735
+
736
+ ### `add(expression)` → `this`
737
+
738
+ Add a single `Expression`. Duplicate patterns (same pattern string) are silently ignored.
739
+ Returns `this` for chaining. Throws `TypeError` if the set is sealed.
740
+
741
+ ```javascript
742
+ set.add(new Expression('root.users.user'));
743
+ set.add(new Expression('..script'));
744
+ ```
745
+
746
+ ### `addAll(expressions)` → `this`
747
+
748
+ Add an array of `Expression` objects at once. Returns `this` for chaining.
749
+
750
+ ```javascript
751
+ set.addAll(config.stopNodes.map(p => new Expression(p)));
752
+ ```
753
+
754
+ ### `has(expression)` → `boolean`
755
+
756
+ Check whether an expression with the same pattern is already present.
757
+
758
+ ```javascript
759
+ set.has(new Expression('root.users.user')); // true / false
760
+ ```
761
+
762
+ ### `seal()` → `this`
763
+
764
+ Prevent further additions. Any subsequent call to `add()` or `addAll()` throws a `TypeError`.
765
+ Useful to guard against accidental mutation once parsing has started.
766
+
767
+ ```javascript
768
+ const stopNodes = new ExpressionSet();
769
+ stopNodes.addAll(patterns).seal();
770
+
771
+ stopNodes.add(new Expression('root.extra')); // ❌ TypeError: ExpressionSet is sealed
772
+ ```
773
+
774
+ ### `size` → `number`
775
+
776
+ Number of distinct expressions in the set.
777
+
778
+ ```javascript
779
+ set.size; // 3
780
+ ```
781
+
782
+ ### `isSealed` → `boolean`
783
+
784
+ Whether `seal()` has been called.
785
+
786
+ ### `matchesAny(matcher)` → `boolean`
787
+
788
+ Returns `true` if the matcher's current path matches **any** expression in the set.
789
+ Accepts both a `Matcher` instance and a `MatcherView`.
790
+
791
+ ```javascript
792
+ if (stopNodes.matchesAny(matcher)) { /* ... */ }
793
+ if (stopNodes.matchesAny(matcher.readOnly())) { /* ... */ } // also works
794
+ ```
795
+
796
+ **How indexing works:** expressions are bucketed at `add()` time, not at match time.
797
+
798
+ | Expression type | Bucket | Lookup cost |
799
+ |---|---|---|
800
+ | Fixed path, concrete tag (`root.users.user`) | `depth:tag` map | O(1) |
801
+ | Fixed path, wildcard tag (`root.config.*`) | `depth` map | O(1) |
802
+ | Deep wildcard (`..script`) | flat list | O(D) — always scanned |
803
+
804
+ In practice, deep-wildcard expressions are rare in configs, so the list stays small.
805
+
806
+ ### `findMatch(matcher)` → `Expression`
807
+
808
+ Returns the Expression instance that matched the current path. Accepts both a `Matcher` instance and a `MatcherView`.
809
+
810
+ ```javascript
811
+ const node = stopNodes.findMatch(matcher);
812
+ ```
813
+
814
+
815
+ ### Example 7: ExpressionSet in a real parser loop
816
+
817
+ ```javascript
818
+ import { XMLParser } from 'fast-xml-parser';
819
+ import { Expression, ExpressionSet, Matcher } from 'path-expression-matcher';
820
+
821
+ // Config-time setup
822
+ const stopNodes = new ExpressionSet();
823
+ stopNodes
824
+ .addAll(['script', 'style'].map(t => new Expression(`..${t}`)))
825
+ .seal();
826
+
827
+ const matcher = new Matcher();
828
+
829
+ const parser = new XMLParser({
830
+ onOpenTag(tagName, attrs) {
831
+ matcher.push(tagName, attrs);
832
+ if (stopNodes.matchesAny(matcher)) {
833
+ // treat as stop node
834
+ }
835
+ },
836
+ onCloseTag() {
837
+ matcher.pop();
838
+ },
839
+ });
840
+ ```
841
+
842
+
843
+
844
+ ## 🔗 Integration with fast-xml-parser
845
+
846
+ **Basic integration:**
847
+
848
+ ```javascript
849
+ import { XMLParser } from 'fast-xml-parser';
850
+ import { Expression, Matcher } from 'path-expression-matcher';
851
+
852
+ const parser = new XMLParser({
853
+ // Custom options using path-expression-matcher
854
+ stopNodes: ["script", "style"].map(tag => new Expression(`..${tag}`)),
855
+
856
+ tagValueProcessor: (tagName, value, jPath, hasAttrs, isLeaf, matcher) => {
857
+ // matcher is available in callbacks
858
+ if (matcher.matches(new Expression("..user[type=admin]"))) {
859
+ return enhanceValue(value);
860
+ }
861
+ return value;
862
+ }
863
+ });
864
+ ```
865
+
866
+ ## 📄 License
867
+
868
+ MIT
869
+
870
+ ## 🤝 Contributing
871
+
872
+ Issues and PRs welcome! This package is designed to be used by XML/JSON parsers like fast-xml-parser. But can be used with any formar parser.