{"_id":"path-expression-matcher","_rev":"5-f486272bbf5a2c05965af07e9849b8dd","name":"path-expression-matcher","dist-tags":{"latest":"1.1.3"},"versions":{"1.0.0":{"name":"path-expression-matcher","version":"1.0.0","keywords":["xml","json","yaml","path","matcher","pattern","xpath","selector","parser","fast-xml-parser","fast-xml-builder"],"author":{"url":"https://solothought.com","name":"Amit Gupta"},"license":"MIT","_id":"path-expression-matcher@1.0.0","maintainers":[{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"}],"homepage":"https://github.com/NaturalIntelligence/path-expression-matcher#readme","bugs":{"url":"https://github.com/NaturalIntelligence/path-expression-matcher/issues"},"dist":{"shasum":"9f61d5411f33d46b063fd4ae53737121d0c0b940","tarball":"https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.0.0.tgz","fileCount":6,"integrity":"sha512-6SYE3bPfULK4jZ7veRtG1fmrsGV36+XcvZQxrjnp32Xv1gC1de5Jih9kvb6dZThRXEdfZgTEIXHRMQnO8E5xXg==","signatures":[{"sig":"MEUCIHAtNmoJYRIbBJVK8CfaWpMhwR0sR1rgBvoK+Lu54WBUAiEA2H0IXswTUIbbUn0FWgLpiODruXvhnI1pdLdCJN0Ida0=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":30351},"main":"src/index.js","type":"module","engines":{"node":">=14.0.0"},"exports":{".":"./src/index.js","./Matcher":"./src/Matcher.js","./Expression":"./src/Expression.js"},"funding":[{"url":"https://github.com/sponsors/NaturalIntelligence","type":"github"}],"gitHead":"9d44b9ca749a44a142aca2ed18f0b2d85e299bbf","scripts":{"test":"node test/test.js"},"_npmUser":{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"},"repository":{"url":"git+https://github.com/NaturalIntelligence/path-expression-matcher.git","type":"git"},"_npmVersion":"11.11.0","description":"Efficient path tracking and pattern matching for XML/JSON parsers","directories":{},"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/path-expression-matcher_1.0.0_1772803815285_0.5097220061166832","host":"s3://npm-registry-packages-npm-production"}},"1.1.0":{"name":"path-expression-matcher","version":"1.1.0","keywords":["xml","json","yaml","path","matcher","pattern","xpath","selector","parser","fast-xml-parser","fast-xml-builder"],"author":{"url":"https://solothought.com","name":"Amit Gupta"},"license":"MIT","_id":"path-expression-matcher@1.1.0","maintainers":[{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"}],"homepage":"https://github.com/NaturalIntelligence/path-expression-matcher#readme","bugs":{"url":"https://github.com/NaturalIntelligence/path-expression-matcher/issues"},"dist":{"shasum":"9a85509a5a5224bedc7792dba3e6375954105979","tarball":"https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.0.tgz","fileCount":6,"integrity":"sha512-dQleLfQnm6qDIV4jYwJINXlWXIA++oi+ZlXDx5UyksP2AICml71s7yKKfpJ7wk51/70spXcnM6C9S1YfoPxYPA==","signatures":[{"sig":"MEUCIEtSOlAQe7T8OGVRMLLhKXs2UnuPHBjePIni/hKOKW3iAiEAkiIxAH0tfPAsWUE2wG/wwHAIBYmgwdk9XRZ1XKWDyjc=","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37316},"main":"src/index.js","type":"module","engines":{"node":">=14.0.0"},"exports":{".":"./src/index.js","./Matcher":"./src/Matcher.js","./Expression":"./src/Expression.js"},"funding":[{"url":"https://github.com/sponsors/NaturalIntelligence","type":"github"}],"gitHead":"7a69dc95231939bd677485fb9c98b85a36bb6888","scripts":{"test":"node test/namespace_test.js && node test/test.js"},"_npmUser":{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"},"repository":{"url":"git+https://github.com/NaturalIntelligence/path-expression-matcher.git","type":"git"},"_npmVersion":"11.11.0","description":"Efficient path tracking and pattern matching for XML/JSON parsers","directories":{},"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/path-expression-matcher_1.1.0_1773098062011_0.3291288493025948","host":"s3://npm-registry-packages-npm-production"}},"1.1.1":{"name":"path-expression-matcher","version":"1.1.1","keywords":["xml","json","yaml","path","matcher","pattern","xpath","selector","parser","fast-xml-parser","fast-xml-builder"],"author":{"url":"https://solothought.com","name":"Amit Gupta"},"license":"MIT","_id":"path-expression-matcher@1.1.1","maintainers":[{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"}],"homepage":"https://github.com/NaturalIntelligence/path-expression-matcher#readme","bugs":{"url":"https://github.com/NaturalIntelligence/path-expression-matcher/issues"},"dist":{"shasum":"c1fd8583ecedb74694b886c5e1715be053c05541","tarball":"https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.1.tgz","fileCount":6,"integrity":"sha512-NC1UVEpK8euainhgyX0UTmM0mSkWsEctNd39cjOAa8Tx424ZUpoPKcSPssPrCVDlhyjIYw7j6v36bqHxct9Xzg==","signatures":[{"sig":"MEQCIFMGq1J/rLHQayaGJMAUzv4wu3IR/S/8etA5j4pYWgj7AiBv16XNyaALz4Wxcq2D79upJVwCymb6O7rIUacLQsEI0g==","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37891},"main":"src/index.js","type":"module","engines":{"node":">=14.0.0"},"exports":{".":"./src/index.js","./Matcher":"./src/Matcher.js","./Expression":"./src/Expression.js"},"funding":[{"url":"https://github.com/sponsors/NaturalIntelligence","type":"github"}],"gitHead":"2742694640f7831baa76fedac22262b480467fd8","scripts":{"test":"node test/namespace_test.js && node test/test.js"},"_npmUser":{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"},"repository":{"url":"git+https://github.com/NaturalIntelligence/path-expression-matcher.git","type":"git"},"_npmVersion":"11.11.0","description":"Efficient path tracking and pattern matching for XML/JSON parsers","directories":{},"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/path-expression-matcher_1.1.1_1773099565101_0.5644758911509864","host":"s3://npm-registry-packages-npm-production"}},"1.1.2":{"name":"path-expression-matcher","version":"1.1.2","keywords":["xml","json","yaml","path","matcher","pattern","xpath","selector","parser","fast-xml-parser","fast-xml-builder"],"author":{"url":"https://solothought.com","name":"Amit Gupta"},"license":"MIT","_id":"path-expression-matcher@1.1.2","maintainers":[{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"}],"homepage":"https://github.com/NaturalIntelligence/path-expression-matcher#readme","bugs":{"url":"https://github.com/NaturalIntelligence/path-expression-matcher/issues"},"dist":{"shasum":"0102f74415abf183857532acd78bb7ad6ff6fa00","tarball":"https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.2.tgz","fileCount":6,"integrity":"sha512-LXWqJmcpp2BKOEmgt4CyuESFmBfPuhJlAHKJsFzuJU6CxErWk75BrO+Ni77M9OxHN6dCYKM4vj+21Z6cOL96YQ==","signatures":[{"sig":"MEYCIQCsYYnMPUa72H60Ihdd+Am+0pFJ4eRkzgfqO7M6g1KWxgIhALk5LAd/vSK5EXjZ7lmTWwgnFVU1YWcYo5gs2LfH57VN","keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U"}],"unpackedSize":37747},"main":"src/index.js","type":"module","engines":{"node":">=14.0.0"},"exports":{".":"./src/index.js","./Matcher":"./src/Matcher.js","./Expression":"./src/Expression.js"},"funding":[{"url":"https://github.com/sponsors/NaturalIntelligence","type":"github"}],"gitHead":"c2ee2c56b603f3ad7c141c0e51501002c4bdfaaa","scripts":{"test":"node test/namespace_test.js && node test/test.js"},"_npmUser":{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"},"repository":{"url":"git+https://github.com/NaturalIntelligence/path-expression-matcher.git","type":"git"},"_npmVersion":"11.11.0","description":"Efficient path tracking and pattern matching for XML/JSON parsers","directories":{},"_nodeVersion":"22.14.0","_hasShrinkwrap":false,"_npmOperationalInternal":{"tmp":"tmp/path-expression-matcher_1.1.2_1773111051697_0.7567646605025329","host":"s3://npm-registry-packages-npm-production"}},"1.1.3":{"name":"path-expression-matcher","version":"1.1.3","description":"Efficient path tracking and pattern matching for XML/JSON parsers","main":"./lib/pem.cjs","type":"module","sideEffects":false,"module":"./src/index.js","types":"./src/index.d.ts","exports":{".":{"import":{"types":"./src/index.d.ts","default":"./src/index.js"},"require":{"types":"./lib/pem.d.cts","default":"./lib/pem.cjs"}}},"scripts":{"test":"c8 --reporter=lcov --reporter=text node test/namespace_test.js && c8 --reporter=lcov --reporter=text node test/test.js","bundle":"webpack --config webpack.cjs.config.js"},"keywords":["xml","json","yaml","path","matcher","pattern","xpath","selector","parser","fast-xml-parser","fast-xml-builder"],"author":{"name":"Amit Gupta","url":"https://solothought.com"},"license":"MIT","repository":{"type":"git","url":"git+https://github.com/NaturalIntelligence/path-expression-matcher.git"},"bugs":{"url":"https://github.com/NaturalIntelligence/path-expression-matcher/issues"},"homepage":"https://github.com/NaturalIntelligence/path-expression-matcher#readme","engines":{"node":">=14.0.0"},"devDependencies":{"@babel/core":"^7.13.10","@babel/plugin-transform-runtime":"^7.13.10","@babel/preset-env":"^7.13.10","@babel/register":"^7.13.8","@types/node":"20","babel-loader":"^8.2.2","c8":"^10.1.3","eslint":"^8.3.0","prettier":"^3.5.1","typescript":"5","webpack":"^5.64.4","webpack-cli":"^4.9.1"},"funding":[{"type":"github","url":"https://github.com/sponsors/NaturalIntelligence"}],"gitHead":"bbf713e3a97d8137e1565259d5ada6dd67e6ea69","_id":"path-expression-matcher@1.1.3","_nodeVersion":"22.14.0","_npmVersion":"11.11.0","dist":{"integrity":"sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==","shasum":"8bf7c629dc1b114e42b633c071f06d14625b4e0d","tarball":"https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz","fileCount":11,"unpackedSize":94470,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIBqGd3SOWaIUW+y2cP8OHccawmUyuMl+9jkYCcIV3IJQAiEA6nJkT7Kep27vpigKrf50+/RqkOJ2ErrenAZsJ2O6TG4="}]},"_npmUser":{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"},"directories":{},"maintainers":[{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/path-expression-matcher_1.1.3_1773227651949_0.4204014454598046"},"_hasShrinkwrap":false}},"time":{"created":"2026-03-06T13:30:15.285Z","modified":"2026-03-11T11:14:12.231Z","1.0.0":"2026-03-06T13:30:15.454Z","1.1.0":"2026-03-09T23:14:22.159Z","1.1.1":"2026-03-09T23:39:25.280Z","1.1.2":"2026-03-10T02:50:51.859Z","1.1.3":"2026-03-11T11:14:12.123Z"},"bugs":{"url":"https://github.com/NaturalIntelligence/path-expression-matcher/issues"},"author":{"name":"Amit Gupta","url":"https://solothought.com"},"license":"MIT","homepage":"https://github.com/NaturalIntelligence/path-expression-matcher#readme","keywords":["xml","json","yaml","path","matcher","pattern","xpath","selector","parser","fast-xml-parser","fast-xml-builder"],"repository":{"type":"git","url":"git+https://github.com/NaturalIntelligence/path-expression-matcher.git"},"description":"Efficient path tracking and pattern matching for XML/JSON parsers","maintainers":[{"name":"amitgupta","email":"amitgupta.gwl@gmail.com"}],"readme":"# path-expression-matcher\n\nEfficient path tracking and pattern matching for XML, JSON, YAML or any other parsers.\n\n## ๐ฏ Purpose\n\n`path-expression-matcher` provides two core classes for tracking and matching paths:\n\n- **`Expression`**: Parses and stores pattern expressions (e.g., `\"root.users.user[id]\"`)\n- **`Matcher`**: Tracks current path during parsing and matches against expressions\n\nCompatible with [fast-xml-parser](https://github.com/NaturalIntelligence/fast-xml-parser) and similar tools.\n\n## ๐ฆ Installation\n\n```bash\nnpm install path-expression-matcher\n```\n\n## ๐ Quick Start\n\n```javascript\nimport { Expression, Matcher } from 'path-expression-matcher';\n\n// Create expression (parse once, reuse many times)\nconst expr = new Expression(\"root.users.user\");\n\n// Create matcher (tracks current path)\nconst matcher = new Matcher();\n\nmatcher.push(\"root\");\nmatcher.push(\"users\");\nmatcher.push(\"user\", { id: \"123\" });\n\n// Match current path against expression\nif (matcher.matches(expr)) {\n console.log(\"Match found!\");\n console.log(\"Current path:\", matcher.toString()); // \"root.users.user\"\n}\n\n// Namespace support\nconst nsExpr = new Expression(\"soap::Envelope.soap::Body..ns::UserId\");\nmatcher.push(\"Envelope\", null, \"soap\");\nmatcher.push(\"Body\", null, \"soap\");\nmatcher.push(\"UserId\", null, \"ns\");\nconsole.log(matcher.toString()); // \"soap:Envelope.soap:Body.ns:UserId\"\n```\n\n## ๐ Pattern Syntax\n\n### Basic Paths\n\n```javascript\n\"root.users.user\" // Exact path match\n\"*.users.user\" // Wildcard: any parent\n\"root.*.user\" // Wildcard: any middle\n\"root.users.*\" // Wildcard: any child\n```\n\n### Deep Wildcard\n\n```javascript\n\"..user\" // user anywhere in tree\n\"root..user\" // user anywhere under root\n\"..users..user\" // users somewhere, then user below it\n```\n\n### Attribute Matching\n\n```javascript\n\"user[id]\" // user with \"id\" attribute\n\"user[type=admin]\" // user with type=\"admin\" (current node only)\n\"root[lang]..user\" // user under root that has \"lang\" attribute\n```\n\n### Position Selectors\n\n```javascript\n\"user:first\" // First user (counter=0)\n\"user:nth(2)\" // Third user (counter=2, zero-based)\n\"user:odd\" // Odd-numbered users (counter=1,3,5...)\n\"user:even\" // Even-numbered users (counter=0,2,4...)\n\"root.users.user:first\" // First user under users\n```\n\n**Note:** Position selectors use the **counter** (occurrence count of the tag name), not the position (child index). For example, in ``, the second `` has position=2 but counter=1.\n\n### Namespaces\n\n```javascript\n\"ns::user\" // user with namespace \"ns\"\n\"soap::Envelope\" // Envelope with namespace \"soap\"\n\"ns::user[id]\" // user with namespace \"ns\" and \"id\" attribute\n\"ns::user:first\" // First user with namespace \"ns\"\n\"*::user\" // user with any namespace\n\"..ns::item\" // item with namespace \"ns\" anywhere in tree\n\"soap::Envelope.soap::Body\" // Nested namespaced elements\n\"ns::first\" // Tag named \"first\" with namespace \"ns\" (NO ambiguity!)\n```\n\n**Namespace syntax:**\n- Use **double colon (::)** for namespace: `ns::tag`\n- Use **single colon (:)** for position: `tag:first`\n- Combined: `ns::tag:first` (namespace + tag + position)\n\n**Namespace matching rules:**\n- Pattern `ns::user` matches only nodes with namespace \"ns\" and tag \"user\"\n- Pattern `user` (no namespace) matches nodes with tag \"user\" regardless of namespace\n- Pattern `*::user` matches tag \"user\" with any namespace (wildcard namespace)\n- Namespaces are tracked separately for counter/position (e.g., `ns1::item` and `ns2::item` have independent counters)\n\n### Wildcard Differences\n\n**Single wildcard (`*`)** - Matches exactly ONE level:\n- `\"*.fix1\"` matches `root.fix1` (2 levels) โ
\n- `\"*.fix1\"` does NOT match `root.another.fix1` (3 levels) โ\n- Path depth MUST equal pattern depth\n\n**Deep wildcard (`..`)** - Matches ZERO or MORE levels:\n- `\"..fix1\"` matches `root.fix1` โ
\n- `\"..fix1\"` matches `root.another.fix1` โ
\n- `\"..fix1\"` matches `a.b.c.d.fix1` โ
\n- Works at any depth\n\n### Combined Patterns\n\n```javascript\n\"..user[id]:first\" // First user with id, anywhere\n\"root..user[type=admin]\" // Admin user under root\n\"ns::user[id]:first\" // First namespaced user with id\n\"soap::Envelope..ns::UserId\" // UserId with namespace ns under SOAP envelope\n```\n\n## ๐ง API Reference\n\n### Expression\n\n#### Constructor\n\n```javascript\nnew Expression(pattern, options)\n```\n\n**Parameters:**\n- `pattern` (string): Pattern to parse\n- `options.separator` (string): Path separator (default: `'.'`)\n\n**Example:**\n```javascript\nconst expr1 = new Expression(\"root.users.user\");\nconst expr2 = new Expression(\"root/users/user\", { separator: '/' });\n```\n\n#### Methods\n\n- `hasDeepWildcard()` โ boolean\n- `hasAttributeCondition()` โ boolean\n- `hasPositionSelector()` โ boolean\n- `toString()` โ string\n\n### Matcher\n\n#### Constructor\n\n```javascript\nnew Matcher(options)\n```\n\n**Parameters:**\n- `options.separator` (string): Default path separator (default: `'.'`)\n\n#### Path Tracking Methods\n\n##### `push(tagName, attrValues, namespace)`\n\nAdd a tag to the current path. Position and counter are automatically calculated.\n\n**Parameters:**\n- `tagName` (string): Tag name\n- `attrValues` (object, optional): Attribute key-value pairs (current node only)\n- `namespace` (string, optional): Namespace for the tag\n\n**Example:**\n```javascript\nmatcher.push(\"user\", { id: \"123\", type: \"admin\" });\nmatcher.push(\"item\"); // No attributes\nmatcher.push(\"Envelope\", null, \"soap\"); // With namespace\nmatcher.push(\"Body\", { version: \"1.1\" }, \"soap\"); // With both\n```\n\n**Position vs Counter:**\n- **Position**: The child index in the parent (0, 1, 2, 3...)\n- **Counter**: How many times this tag name appeared at this level (0, 1, 2...)\n\nExample:\n```xml\n\n \n \n \n\n```\n\n##### `pop()`\n\nRemove the last tag from the path.\n\n```javascript\nmatcher.pop();\n```\n\n##### `updateCurrent(attrValues)`\n\nUpdate current node's attributes (useful when attributes are parsed after push).\n\n```javascript\nmatcher.push(\"user\"); // Don't know values yet\n// ... parse attributes ...\nmatcher.updateCurrent({ id: \"123\" });\n```\n\n##### `reset()`\n\nClear the entire path.\n\n```javascript\nmatcher.reset();\n```\n\n#### Query Methods\n\n##### `matches(expression)`\n\nCheck if current path matches an Expression.\n\n```javascript\nconst expr = new Expression(\"root.users.user\");\nif (matcher.matches(expr)) {\n // Current path matches\n}\n```\n\n##### `getCurrentTag()`\n\nGet current tag name.\n\n```javascript\nconst tag = matcher.getCurrentTag(); // \"user\"\n```\n\n##### `getCurrentNamespace()`\n\nGet current namespace.\n\n```javascript\nconst ns = matcher.getCurrentNamespace(); // \"soap\" or undefined\n```\n\n##### `getAttrValue(attrName)`\n\nGet attribute value of current node.\n\n```javascript\nconst id = matcher.getAttrValue(\"id\"); // \"123\"\n```\n\n##### `hasAttr(attrName)`\n\nCheck if current node has an attribute.\n\n```javascript\nif (matcher.hasAttr(\"id\")) {\n // Current node has \"id\" attribute\n}\n```\n\n##### `getPosition()`\n\nGet sibling position of current node (child index in parent).\n\n```javascript\nconst position = matcher.getPosition(); // 0, 1, 2, ...\n```\n\n##### `getCounter()`\n\nGet repeat counter of current node (occurrence count of this tag name).\n\n```javascript\nconst counter = matcher.getCounter(); // 0, 1, 2, ...\n```\n\n##### `getIndex()` (deprecated)\n\nAlias for `getPosition()`. Use `getPosition()` or `getCounter()` instead for clarity.\n\n```javascript\nconst index = matcher.getIndex(); // Same as getPosition()\n```\n\n##### `getDepth()`\n\nGet current path depth.\n\n```javascript\nconst depth = matcher.getDepth(); // 3 for \"root.users.user\"\n```\n\n##### `toString(separator?, includeNamespace?)`\n\nGet path as string.\n\n**Parameters:**\n- `separator` (string, optional): Path separator (uses default if not provided)\n- `includeNamespace` (boolean, optional): Whether to include namespaces (default: true)\n\n```javascript\nconst path = matcher.toString(); // \"root.ns:user.item\"\nconst path2 = matcher.toString('/'); // \"root/ns:user/item\"\nconst path3 = matcher.toString('.', false); // \"root.user.item\" (no namespaces)\n```\n\n##### `toArray()`\n\nGet path as array.\n\n```javascript\nconst arr = matcher.toArray(); // [\"root\", \"users\", \"user\"]\n```\n\n#### State Management\n\n##### `snapshot()`\n\nCreate a snapshot of current state.\n\n```javascript\nconst snapshot = matcher.snapshot();\n```\n\n##### `restore(snapshot)`\n\nRestore from a snapshot.\n\n```javascript\nmatcher.restore(snapshot);\n```\n\n## ๐ก Usage Examples\n\n### Example 1: XML Parser with stopNodes\n\n```javascript\nimport { XMLParser } from 'fast-xml-parser';\nimport { Expression, Matcher } from 'path-expression-matcher';\n\nclass MyParser {\n constructor() {\n this.matcher = new Matcher();\n \n // Pre-compile stop node patterns\n this.stopNodeExpressions = [\n new Expression(\"html.body.script\"),\n new Expression(\"html.body.style\"),\n new Expression(\"..svg\"),\n ];\n }\n \n parseTag(tagName, attrs) {\n this.matcher.push(tagName, attrs);\n \n // Check if this is a stop node\n for (const expr of this.stopNodeExpressions) {\n if (this.matcher.matches(expr)) {\n // Don't parse children, read as raw text\n return this.readRawContent();\n }\n }\n \n // Continue normal parsing\n this.parseChildren();\n \n this.matcher.pop();\n }\n}\n```\n\n### Example 2: Conditional Processing\n\n```javascript\nconst matcher = new Matcher();\nconst userExpr = new Expression(\"..user[type=admin]\");\nconst firstItemExpr = new Expression(\"..item:first\");\n\nfunction processTag(tagName, value, attrs) {\n matcher.push(tagName, attrs);\n \n if (matcher.matches(userExpr)) {\n value = enhanceAdminUser(value);\n }\n \n if (matcher.matches(firstItemExpr)) {\n value = markAsFirst(value);\n }\n \n matcher.pop();\n return value;\n}\n```\n\n### Example 3: Path-based Filtering\n\n```javascript\nconst patterns = [\n new Expression(\"data.users.user\"),\n new Expression(\"data.posts.post\"),\n new Expression(\"..comment[approved=true]\"),\n];\n\nfunction shouldInclude(matcher) {\n return patterns.some(expr => matcher.matches(expr));\n}\n```\n\n### Example 4: Custom Separator\n\n```javascript\nconst matcher = new Matcher({ separator: '/' });\nconst expr = new Expression(\"root/config/database\", { separator: '/' });\n\nmatcher.push(\"root\");\nmatcher.push(\"config\");\nmatcher.push(\"database\");\n\nconsole.log(matcher.toString()); // \"root/config/database\"\nconsole.log(matcher.matches(expr)); // true\n```\n\n### Example 5: Attribute Checking\n\n```javascript\nconst matcher = new Matcher();\nmatcher.push(\"root\");\nmatcher.push(\"user\", { id: \"123\", type: \"admin\", status: \"active\" });\n\n// Check attribute existence (current node only)\nconsole.log(matcher.hasAttr(\"id\")); // true\nconsole.log(matcher.hasAttr(\"email\")); // false\n\n// Get attribute value (current node only)\nconsole.log(matcher.getAttrValue(\"type\")); // \"admin\"\n\n// Match by attribute\nconst expr1 = new Expression(\"user[id]\");\nconsole.log(matcher.matches(expr1)); // true\n\nconst expr2 = new Expression(\"user[type=admin]\");\nconsole.log(matcher.matches(expr2)); // true\n```\n\n### Example 6: Position vs Counter\n\n```javascript\nconst matcher = new Matcher();\nmatcher.push(\"root\");\n\n// Mixed tags at same level\nmatcher.push(\"item\"); // position=0, counter=0 (first item)\nmatcher.pop();\n\nmatcher.push(\"div\"); // position=1, counter=0 (first div)\nmatcher.pop();\n\nmatcher.push(\"item\"); // position=2, counter=1 (second item)\n\nconsole.log(matcher.getPosition()); // 2 (third child overall)\nconsole.log(matcher.getCounter()); // 1 (second \"item\" specifically)\n\n// :first uses counter, not position\nconst expr = new Expression(\"root.item:first\");\nconsole.log(matcher.matches(expr)); // false (counter=1, not 0)\n```\n\n### Example 7: Namespace Support (XML/SOAP)\n\n```javascript\nconst matcher = new Matcher();\nconst soapExpr = new Expression(\"soap::Envelope.soap::Body..ns::UserId\");\n\n// Parse SOAP document\nmatcher.push(\"Envelope\", { xmlns: \"...\" }, \"soap\");\nmatcher.push(\"Body\", null, \"soap\");\nmatcher.push(\"GetUserRequest\", null, \"ns\");\nmatcher.push(\"UserId\", null, \"ns\");\n\n// Match namespaced pattern\nif (matcher.matches(soapExpr)) {\n console.log(\"Found UserId in SOAP body\");\n console.log(matcher.toString()); // \"soap:Envelope.soap:Body.ns:GetUserRequest.ns:UserId\"\n}\n\n// Namespace-specific counters\nmatcher.reset();\nmatcher.push(\"root\");\nmatcher.push(\"item\", null, \"ns1\"); // ns1::item counter=0\nmatcher.pop();\nmatcher.push(\"item\", null, \"ns2\"); // ns2::item counter=0 (different namespace)\nmatcher.pop();\nmatcher.push(\"item\", null, \"ns1\"); // ns1::item counter=1\n\nconst firstNs1Item = new Expression(\"root.ns1::item:first\");\nconsole.log(matcher.matches(firstNs1Item)); // false (counter=1)\n\nconst secondNs1Item = new Expression(\"root.ns1::item:nth(1)\");\nconsole.log(matcher.matches(secondNs1Item)); // true\n\n// NO AMBIGUITY: Tags named after position keywords\nmatcher.reset();\nmatcher.push(\"root\");\nmatcher.push(\"first\", null, \"ns\"); // Tag named \"first\" with namespace\n\nconst expr = new Expression(\"root.ns::first\");\nconsole.log(matcher.matches(expr)); // true - matches namespace \"ns\", tag \"first\"\n```\n\n## ๐๏ธ Architecture\n\n### Data Storage Strategy\n\n**Ancestor nodes:** Store only tag name, position, and counter (minimal memory)\n**Current node:** Store tag name, position, counter, and attribute values\n\nThis design minimizes memory usage:\n- No attribute names stored (derived from values object when needed)\n- Attribute values only for current node, not ancestors\n- Attribute checking for ancestors is not supported (acceptable trade-off)\n- For 1M nodes with 3 attributes each, saves ~50MB vs storing attribute names\n\n### Matching Strategy\n\nMatching is performed **bottom-to-top** (from current node toward root):\n1. Start at current node\n2. Match segments from pattern end to start\n3. Attribute checking only works for current node (ancestors have no attribute data)\n4. Position selectors use **counter** (occurrence count), not position (child index)\n\n### Performance\n\n- **Expression parsing:** One-time cost when Expression is created\n- **Expression analysis:** Cached (hasDeepWildcard, hasAttributeCondition, hasPositionSelector)\n- **Path tracking:** O(1) for push/pop operations\n- **Pattern matching:** O(n*m) where n = path depth, m = pattern segments\n- **Memory per ancestor node:** ~40-60 bytes (tag, position, counter only)\n- **Memory per current node:** ~80-120 bytes (adds attribute values)\n\n## ๐ Design Patterns\n\n### Pre-compile Patterns (Recommended)\n\n```javascript\n// โ
GOOD: Parse once, reuse many times\nconst expr = new Expression(\"..user[id]\");\n\nfor (let i = 0; i < 1000; i++) {\n if (matcher.matches(expr)) {\n // ...\n }\n}\n```\n\n```javascript\n// โ BAD: Parse on every iteration\nfor (let i = 0; i < 1000; i++) {\n if (matcher.matches(new Expression(\"..user[id]\"))) {\n // ...\n }\n}\n```\n\n### Batch Pattern Checking\n\n```javascript\n// For multiple patterns, check all at once\nconst patterns = [\n new Expression(\"..user\"),\n new Expression(\"..post\"),\n new Expression(\"..comment\"),\n];\n\nfunction matchesAny(matcher, patterns) {\n return patterns.some(expr => matcher.matches(expr));\n}\n```\n\n## ๐ Integration with fast-xml-parser\n\n**Basic integration:**\n\n```javascript\nimport { XMLParser } from 'fast-xml-parser';\nimport { Expression, Matcher } from 'path-expression-matcher';\n\nconst parser = new XMLParser({\n // Custom options using path-expression-matcher\n stopNodes: [\"script\", \"style\"].map(tag => new Expression(`..${tag}`)),\n \n tagValueProcessor: (tagName, value, jPath, hasAttrs, isLeaf, matcher) => {\n // matcher is available in callbacks\n if (matcher.matches(new Expression(\"..user[type=admin]\"))) {\n return enhanceValue(value);\n }\n return value;\n }\n});\n```\n\n## ๐งช Testing\n\n```bash\nnpm test\n```\n\nAll 77 tests covering:\n- Pattern parsing (exact, wildcards, attributes, position)\n- Path tracking (push, pop, update)\n- Pattern matching (all combinations)\n- Edge cases and error conditions\n\n## ๐ License\n\nMIT\n\n## ๐ค Contributing\n\nIssues and PRs welcome! This package is designed to be used by XML/JSON parsers like fast-xml-parser.","readmeFilename":"README.md"}