@jjrawlins/cdk-diff-pr-github-action 1.9.12 → 1.9.14

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 (88) hide show
  1. package/.jsii +3 -3
  2. package/cdkdiffprgithubaction/go.mod +1 -1
  3. package/cdkdiffprgithubaction/jsii/jsii.go +2 -2
  4. package/cdkdiffprgithubaction/version +1 -1
  5. package/lib/CdkDiffIamTemplate.js +2 -2
  6. package/lib/CdkDiffIamTemplateStackSet.js +2 -2
  7. package/lib/CdkDiffStackWorkflow.js +1 -1
  8. package/lib/CdkDriftDetectionWorkflow.js +1 -1
  9. package/lib/CdkDriftIamTemplate.js +2 -2
  10. package/node_modules/@aws-sdk/client-cloudformation/package.json +13 -13
  11. package/node_modules/@aws-sdk/core/package.json +4 -4
  12. package/node_modules/@aws-sdk/credential-provider-env/package.json +2 -2
  13. package/node_modules/@aws-sdk/credential-provider-http/package.json +5 -5
  14. package/node_modules/@aws-sdk/credential-provider-ini/package.json +9 -9
  15. package/node_modules/@aws-sdk/credential-provider-login/package.json +3 -3
  16. package/node_modules/@aws-sdk/credential-provider-node/package.json +7 -7
  17. package/node_modules/@aws-sdk/credential-provider-process/package.json +2 -2
  18. package/node_modules/@aws-sdk/credential-provider-sso/package.json +4 -4
  19. package/node_modules/@aws-sdk/credential-provider-web-identity/package.json +3 -3
  20. package/node_modules/@aws-sdk/middleware-user-agent/package.json +3 -3
  21. package/node_modules/@aws-sdk/nested-clients/dist-es/submodules/cognito-identity/CognitoIdentity.js +1 -1
  22. package/node_modules/@aws-sdk/nested-clients/dist-types/submodules/cognito-identity/CognitoIdentity.d.ts +2 -2
  23. package/node_modules/@aws-sdk/nested-clients/package.json +12 -12
  24. package/node_modules/@aws-sdk/token-providers/package.json +3 -3
  25. package/node_modules/@aws-sdk/util-user-agent-node/package.json +2 -2
  26. package/node_modules/@aws-sdk/xml-builder/package.json +2 -2
  27. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/CHANGELOG.md +33 -4
  28. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/README.md +8 -7
  29. package/node_modules/fast-xml-parser/lib/fxbuilder.min.js +2 -0
  30. package/node_modules/fast-xml-parser/lib/fxbuilder.min.js.map +1 -0
  31. package/node_modules/fast-xml-parser/lib/fxp.cjs +1 -0
  32. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/lib/fxp.d.cts +73 -19
  33. package/node_modules/fast-xml-parser/lib/fxp.min.js +2 -0
  34. package/node_modules/fast-xml-parser/lib/fxp.min.js.map +1 -0
  35. package/node_modules/fast-xml-parser/lib/fxparser.min.js +2 -0
  36. package/node_modules/fast-xml-parser/lib/fxparser.min.js.map +1 -0
  37. package/node_modules/fast-xml-parser/lib/fxvalidator.min.js.map +1 -0
  38. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/package.json +3 -2
  39. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/fxp.d.ts +73 -19
  40. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/util.js +18 -0
  41. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/xmlparser/DocTypeReader.js +12 -2
  42. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/xmlparser/OptionsBuilder.js +71 -0
  43. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/xmlparser/OrderedObjParser.js +291 -115
  44. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/xmlparser/XMLParser.js +1 -1
  45. package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/xmlparser/node2json.js +65 -14
  46. package/package.json +2 -2
  47. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxbuilder.min.js +0 -2
  48. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxbuilder.min.js.map +0 -1
  49. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxp.cjs +0 -1
  50. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxp.min.js +0 -2
  51. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxp.min.js.map +0 -1
  52. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxparser.min.js +0 -2
  53. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxparser.min.js.map +0 -1
  54. package/node_modules/@aws-sdk/xml-builder/node_modules/fast-xml-parser/lib/fxvalidator.min.js.map +0 -1
  55. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/LICENSE +0 -0
  56. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/lib/fxvalidator.min.js +0 -0
  57. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/cli/cli.js +0 -0
  58. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/cli/man.js +0 -0
  59. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/cli/read.js +0 -0
  60. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/fxp.js +0 -0
  61. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/ignoreAttributes.js +0 -0
  62. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/CharsSymbol.js +0 -0
  63. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/EntitiesParser.js +0 -0
  64. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/OptionsBuilder.js +0 -0
  65. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/OutputBuilders/BaseOutputBuilder.js +0 -0
  66. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/OutputBuilders/JsArrBuilder.js +0 -0
  67. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/OutputBuilders/JsMinArrBuilder.js +0 -0
  68. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/OutputBuilders/JsObjBuilder.js +0 -0
  69. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/OutputBuilders/ParserOptionsBuilder.js +0 -0
  70. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/Report.js +0 -0
  71. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/TagPath.js +0 -0
  72. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/TagPathMatcher.js +0 -0
  73. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/XMLParser.js +0 -0
  74. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/Xml2JsParser.js +0 -0
  75. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/XmlPartReader.js +0 -0
  76. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/XmlSpecialTagsReader.js +0 -0
  77. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/inputSource/BufferSource.js +0 -0
  78. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/inputSource/StringSource.js +0 -0
  79. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/valueParsers/EntitiesParser.js +0 -0
  80. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/valueParsers/booleanParser.js +0 -0
  81. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/valueParsers/booleanParserExt.js +0 -0
  82. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/valueParsers/currency.js +0 -0
  83. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/valueParsers/join.js +0 -0
  84. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/valueParsers/number.js +0 -0
  85. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/v6/valueParsers/trim.js +0 -0
  86. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/validator.js +0 -0
  87. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/xmlbuilder/json2xml.js +0 -0
  88. /package/node_modules/{@aws-sdk/xml-builder/node_modules/fast-xml-parser → fast-xml-parser}/src/xmlparser/xmlNode.js +0 -0
@@ -1,11 +1,12 @@
1
1
  'use strict';
2
2
  ///@ts-check
3
3
 
4
- import { getAllMatches, isExist } from '../util.js';
4
+ import { getAllMatches, isExist, DANGEROUS_PROPERTY_NAMES, criticalProperties } from '../util.js';
5
5
  import xmlNode from './xmlNode.js';
6
6
  import DocTypeReader from './DocTypeReader.js';
7
7
  import toNumber from "strnum";
8
8
  import getIgnoreAttributesFn from "../ignoreAttributes.js";
9
+ import { Expression, Matcher } from 'path-expression-matcher';
9
10
 
10
11
  // const regx =
11
12
  // '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
@@ -14,6 +15,57 @@ import getIgnoreAttributesFn from "../ignoreAttributes.js";
14
15
  //const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
15
16
  //const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
16
17
 
18
+ // Helper functions for attribute and namespace handling
19
+
20
+ /**
21
+ * Extract raw attributes (without prefix) from prefixed attribute map
22
+ * @param {object} prefixedAttrs - Attributes with prefix from buildAttributesMap
23
+ * @param {object} options - Parser options containing attributeNamePrefix
24
+ * @returns {object} Raw attributes for matcher
25
+ */
26
+ function extractRawAttributes(prefixedAttrs, options) {
27
+ if (!prefixedAttrs) return {};
28
+
29
+ // Handle attributesGroupName option
30
+ const attrs = options.attributesGroupName
31
+ ? prefixedAttrs[options.attributesGroupName]
32
+ : prefixedAttrs;
33
+
34
+ if (!attrs) return {};
35
+
36
+ const rawAttrs = {};
37
+ for (const key in attrs) {
38
+ // Remove the attribute prefix to get raw name
39
+ if (key.startsWith(options.attributeNamePrefix)) {
40
+ const rawName = key.substring(options.attributeNamePrefix.length);
41
+ rawAttrs[rawName] = attrs[key];
42
+ } else {
43
+ // Attribute without prefix (shouldn't normally happen, but be safe)
44
+ rawAttrs[key] = attrs[key];
45
+ }
46
+ }
47
+ return rawAttrs;
48
+ }
49
+
50
+ /**
51
+ * Extract namespace from raw tag name
52
+ * @param {string} rawTagName - Tag name possibly with namespace (e.g., "soap:Envelope")
53
+ * @returns {string|undefined} Namespace or undefined
54
+ */
55
+ function extractNamespace(rawTagName) {
56
+ if (!rawTagName || typeof rawTagName !== 'string') return undefined;
57
+
58
+ const colonIndex = rawTagName.indexOf(':');
59
+ if (colonIndex !== -1 && colonIndex > 0) {
60
+ const ns = rawTagName.substring(0, colonIndex);
61
+ // Don't treat xmlns as a namespace
62
+ if (ns !== 'xmlns') {
63
+ return ns;
64
+ }
65
+ }
66
+ return undefined;
67
+ }
68
+
17
69
  export default class OrderedObjParser {
18
70
  constructor(options) {
19
71
  this.options = options;
@@ -58,16 +110,23 @@ export default class OrderedObjParser {
58
110
  this.entityExpansionCount = 0;
59
111
  this.currentExpandedLength = 0;
60
112
 
113
+ // Initialize path matcher for path-expression-matcher
114
+ this.matcher = new Matcher();
115
+
116
+ // Flag to track if current node is a stop node (optimization)
117
+ this.isCurrentNodeStopNode = false;
118
+
119
+ // Pre-compile stopNodes expressions
61
120
  if (this.options.stopNodes && this.options.stopNodes.length > 0) {
62
- this.stopNodesExact = new Set();
63
- this.stopNodesWildcard = new Set();
121
+ this.stopNodeExpressions = [];
64
122
  for (let i = 0; i < this.options.stopNodes.length; i++) {
65
123
  const stopNodeExp = this.options.stopNodes[i];
66
- if (typeof stopNodeExp !== 'string') continue;
67
- if (stopNodeExp.startsWith("*.")) {
68
- this.stopNodesWildcard.add(stopNodeExp.substring(2));
69
- } else {
70
- this.stopNodesExact.add(stopNodeExp);
124
+ if (typeof stopNodeExp === 'string') {
125
+ // Convert string to Expression object
126
+ this.stopNodeExpressions.push(new Expression(stopNodeExp));
127
+ } else if (stopNodeExp instanceof Expression) {
128
+ // Already an Expression object
129
+ this.stopNodeExpressions.push(stopNodeExp);
71
130
  }
72
131
  }
73
132
  }
@@ -90,7 +149,7 @@ function addExternalEntities(externalEntities) {
90
149
  /**
91
150
  * @param {string} val
92
151
  * @param {string} tagName
93
- * @param {string} jPath
152
+ * @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
94
153
  * @param {boolean} dontTrim
95
154
  * @param {boolean} hasAttributes
96
155
  * @param {boolean} isLeafNode
@@ -104,7 +163,9 @@ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode,
104
163
  if (val.length > 0) {
105
164
  if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath);
106
165
 
107
- const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
166
+ // Pass jPath string or matcher based on options.jPath setting
167
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
168
+ const newval = this.options.tagValueProcessor(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode);
108
169
  if (newval === null || newval === undefined) {
109
170
  //don't parse
110
171
  return val;
@@ -151,25 +212,58 @@ function buildAttributesMap(attrStr, jPath, tagName) {
151
212
  const matches = getAllMatches(attrStr, attrsRegx);
152
213
  const len = matches.length; //don't make it inline
153
214
  const attrs = {};
215
+
216
+ // First pass: parse all attributes and update matcher with raw values
217
+ // This ensures the matcher has all attribute values when processors run
218
+ const rawAttrsForMatcher = {};
154
219
  for (let i = 0; i < len; i++) {
155
220
  const attrName = this.resolveNameSpace(matches[i][1]);
156
- if (this.ignoreAttributesFn(attrName, jPath)) {
221
+ const oldVal = matches[i][4];
222
+
223
+ if (attrName.length && oldVal !== undefined) {
224
+ let parsedVal = oldVal;
225
+ if (this.options.trimValues) {
226
+ parsedVal = parsedVal.trim();
227
+ }
228
+ parsedVal = this.replaceEntitiesValue(parsedVal, tagName, jPath);
229
+ rawAttrsForMatcher[attrName] = parsedVal;
230
+ }
231
+ }
232
+
233
+ // Update matcher with raw attribute values BEFORE running processors
234
+ if (Object.keys(rawAttrsForMatcher).length > 0 && typeof jPath === 'object' && jPath.updateCurrent) {
235
+ jPath.updateCurrent(rawAttrsForMatcher);
236
+ }
237
+
238
+ // Second pass: now process attributes with matcher having full attribute context
239
+ for (let i = 0; i < len; i++) {
240
+ const attrName = this.resolveNameSpace(matches[i][1]);
241
+
242
+ // Convert jPath to string if needed for ignoreAttributesFn
243
+ const jPathStr = this.options.jPath ? jPath.toString() : jPath;
244
+ if (this.ignoreAttributesFn(attrName, jPathStr)) {
157
245
  continue
158
246
  }
247
+
159
248
  let oldVal = matches[i][4];
160
249
  let aName = this.options.attributeNamePrefix + attrName;
250
+
161
251
  if (attrName.length) {
162
252
  if (this.options.transformAttributeName) {
163
253
  aName = this.options.transformAttributeName(aName);
164
254
  }
165
- if (aName === "__proto__") aName = "#__proto__";
255
+ //if (aName === "__proto__") aName = "#__proto__";
256
+ aName = sanitizeName(aName, this.options);
166
257
 
167
258
  if (oldVal !== undefined) {
168
259
  if (this.options.trimValues) {
169
260
  oldVal = oldVal.trim();
170
261
  }
171
262
  oldVal = this.replaceEntitiesValue(oldVal, tagName, jPath);
172
- const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
263
+
264
+ // Pass jPath string or matcher based on options.jPath setting
265
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
266
+ const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPathOrMatcher);
173
267
  if (newVal === null || newVal === undefined) {
174
268
  //don't parse
175
269
  attrs[aName] = oldVal;
@@ -189,6 +283,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
189
283
  }
190
284
  }
191
285
  }
286
+
192
287
  if (!Object.keys(attrs).length) {
193
288
  return;
194
289
  }
@@ -206,7 +301,9 @@ const parseXml = function (xmlData) {
206
301
  const xmlObj = new xmlNode('!xml');
207
302
  let currentNode = xmlObj;
208
303
  let textData = "";
209
- let jPath = "";
304
+
305
+ // Reset matcher for new document
306
+ this.matcher.reset();
210
307
 
211
308
  // Reset entity expansion counters for this document
212
309
  this.entityExpansionCount = 0;
@@ -229,27 +326,25 @@ const parseXml = function (xmlData) {
229
326
  }
230
327
  }
231
328
 
232
- if (this.options.transformTagName) {
233
- tagName = this.options.transformTagName(tagName);
234
- }
329
+ tagName = transformTagName(this.options.transformTagName, tagName, "", this.options).tagName;
235
330
 
236
331
  if (currentNode) {
237
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
332
+ textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
238
333
  }
239
334
 
240
335
  //check if last tag of nested tag was unpaired tag
241
- const lastTagName = jPath.substring(jPath.lastIndexOf(".") + 1);
336
+ const lastTagName = this.matcher.getCurrentTag();
242
337
  if (tagName && this.options.unpairedTags.indexOf(tagName) !== -1) {
243
338
  throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
244
339
  }
245
- let propIndex = 0
246
340
  if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) {
247
- propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.') - 1)
341
+ // Pop the unpaired tag
342
+ this.matcher.pop();
248
343
  this.tagsNodeStack.pop();
249
- } else {
250
- propIndex = jPath.lastIndexOf(".");
251
344
  }
252
- jPath = jPath.substring(0, propIndex);
345
+ // Pop the closing tag
346
+ this.matcher.pop();
347
+ this.isCurrentNodeStopNode = false; // Reset flag when closing tag
253
348
 
254
349
  currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
255
350
  textData = "";
@@ -259,7 +354,7 @@ const parseXml = function (xmlData) {
259
354
  let tagData = readTagExp(xmlData, i, false, "?>");
260
355
  if (!tagData) throw new Error("Pi Tag is not closed.");
261
356
 
262
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
357
+ textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
263
358
  if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) {
264
359
  //do nothing
265
360
  } else {
@@ -268,9 +363,9 @@ const parseXml = function (xmlData) {
268
363
  childNode.add(this.options.textNodeName, "");
269
364
 
270
365
  if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
271
- childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
366
+ childNode[":@"] = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName);
272
367
  }
273
- this.addChild(currentNode, childNode, jPath, i);
368
+ this.addChild(currentNode, childNode, this.matcher, i);
274
369
  }
275
370
 
276
371
 
@@ -280,7 +375,7 @@ const parseXml = function (xmlData) {
280
375
  if (this.options.commentPropName) {
281
376
  const comment = xmlData.substring(i + 4, endIndex - 2);
282
377
 
283
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
378
+ textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
284
379
 
285
380
  currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
286
381
  }
@@ -293,9 +388,9 @@ const parseXml = function (xmlData) {
293
388
  const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
294
389
  const tagExp = xmlData.substring(i + 9, closeIndex);
295
390
 
296
- textData = this.saveTextToParentTag(textData, currentNode, jPath);
391
+ textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
297
392
 
298
- let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
393
+ let val = this.parseTextData(tagExp, currentNode.tagname, this.matcher, true, false, true, true);
299
394
  if (val == undefined) val = "";
300
395
 
301
396
  //cdata should be set even if it is 0 length string
@@ -308,20 +403,21 @@ const parseXml = function (xmlData) {
308
403
  i = closeIndex + 2;
309
404
  } else {//Opening tag
310
405
  let result = readTagExp(xmlData, i, this.options.removeNSPrefix);
406
+
407
+ // Safety check: readTagExp can return undefined
408
+ if (!result) {
409
+ // Log context for debugging
410
+ const context = xmlData.substring(Math.max(0, i - 50), Math.min(xmlData.length, i + 50));
411
+ throw new Error(`readTagExp returned undefined at position ${i}. Context: "${context}"`);
412
+ }
413
+
311
414
  let tagName = result.tagName;
312
415
  const rawTagName = result.rawTagName;
313
416
  let tagExp = result.tagExp;
314
417
  let attrExpPresent = result.attrExpPresent;
315
418
  let closeIndex = result.closeIndex;
316
419
 
317
- if (this.options.transformTagName) {
318
- //console.log(tagExp, tagName)
319
- const newTagName = this.options.transformTagName(tagName);
320
- if (tagExp === tagName) {
321
- tagExp = newTagName
322
- }
323
- tagName = newTagName;
324
- }
420
+ ({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options));
325
421
 
326
422
  if (this.options.strictReservedNames &&
327
423
  (tagName === this.options.commentPropName
@@ -334,7 +430,7 @@ const parseXml = function (xmlData) {
334
430
  if (currentNode && textData) {
335
431
  if (currentNode.tagname !== '!xml') {
336
432
  //when nested tag is found
337
- textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
433
+ textData = this.saveTextToParentTag(textData, currentNode, this.matcher, false);
338
434
  }
339
435
  }
340
436
 
@@ -342,28 +438,65 @@ const parseXml = function (xmlData) {
342
438
  const lastTag = currentNode;
343
439
  if (lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) {
344
440
  currentNode = this.tagsNodeStack.pop();
345
- jPath = jPath.substring(0, jPath.lastIndexOf("."));
441
+ this.matcher.pop();
442
+ }
443
+
444
+ // Clean up self-closing syntax BEFORE processing attributes
445
+ // This is where tagExp gets the trailing / removed
446
+ let isSelfClosing = false;
447
+ if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
448
+ isSelfClosing = true;
449
+ if (tagName[tagName.length - 1] === "/") {
450
+ tagName = tagName.substr(0, tagName.length - 1);
451
+ tagExp = tagName;
452
+ } else {
453
+ tagExp = tagExp.substr(0, tagExp.length - 1);
454
+ }
455
+
456
+ // Re-check attrExpPresent after cleaning
457
+ attrExpPresent = (tagName !== tagExp);
458
+ }
459
+
460
+ // Now process attributes with CLEAN tagExp (no trailing /)
461
+ let prefixedAttrs = null;
462
+ let rawAttrs = {};
463
+ let namespace = undefined;
464
+
465
+ // Extract namespace from rawTagName
466
+ namespace = extractNamespace(rawTagName);
467
+
468
+ // Push tag to matcher FIRST (with empty attrs for now) so callbacks see correct path
469
+ if (tagName !== xmlObj.tagname) {
470
+ this.matcher.push(tagName, {}, namespace);
346
471
  }
472
+
473
+ // Now build attributes - callbacks will see correct matcher state
474
+ if (tagName !== tagExp && attrExpPresent) {
475
+ // Build attributes (returns prefixed attributes for the tree)
476
+ // Note: buildAttributesMap now internally updates the matcher with raw attributes
477
+ prefixedAttrs = this.buildAttributesMap(tagExp, this.matcher, tagName);
478
+
479
+ if (prefixedAttrs) {
480
+ // Extract raw attributes (without prefix) for our use
481
+ rawAttrs = extractRawAttributes(prefixedAttrs, this.options);
482
+ }
483
+ }
484
+
485
+ // Now check if this is a stop node (after attributes are set)
347
486
  if (tagName !== xmlObj.tagname) {
348
- jPath += jPath ? "." + tagName : tagName;
487
+ this.isCurrentNodeStopNode = this.isItStopNode(this.stopNodeExpressions, this.matcher);
349
488
  }
489
+
350
490
  const startIndex = i;
351
- if (this.isItStopNode(this.stopNodesExact, this.stopNodesWildcard, jPath, tagName)) {
491
+ if (this.isCurrentNodeStopNode) {
352
492
  let tagContent = "";
353
- //self-closing tag
354
- if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
355
- if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
356
- tagName = tagName.substr(0, tagName.length - 1);
357
- jPath = jPath.substr(0, jPath.length - 1);
358
- tagExp = tagName;
359
- } else {
360
- tagExp = tagExp.substr(0, tagExp.length - 1);
361
- }
493
+
494
+ // For self-closing tags, content is empty
495
+ if (isSelfClosing) {
362
496
  i = result.closeIndex;
363
497
  }
364
498
  //unpaired tag
365
499
  else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
366
-
367
500
  i = result.closeIndex;
368
501
  }
369
502
  //normal tag
@@ -377,50 +510,38 @@ const parseXml = function (xmlData) {
377
510
 
378
511
  const childNode = new xmlNode(tagName);
379
512
 
380
- if (tagName !== tagExp && attrExpPresent) {
381
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
382
- }
383
- if (tagContent) {
384
- tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
513
+ if (prefixedAttrs) {
514
+ childNode[":@"] = prefixedAttrs;
385
515
  }
386
516
 
387
- jPath = jPath.substr(0, jPath.lastIndexOf("."));
517
+ // For stop nodes, store raw content as-is without any processing
388
518
  childNode.add(this.options.textNodeName, tagContent);
389
519
 
390
- this.addChild(currentNode, childNode, jPath, startIndex);
520
+ this.matcher.pop(); // Pop the stop node tag
521
+ this.isCurrentNodeStopNode = false; // Reset flag
522
+
523
+ this.addChild(currentNode, childNode, this.matcher, startIndex);
391
524
  } else {
392
525
  //selfClosing tag
393
- if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
394
- if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
395
- tagName = tagName.substr(0, tagName.length - 1);
396
- jPath = jPath.substr(0, jPath.length - 1);
397
- tagExp = tagName;
398
- } else {
399
- tagExp = tagExp.substr(0, tagExp.length - 1);
400
- }
401
-
402
- if (this.options.transformTagName) {
403
- const newTagName = this.options.transformTagName(tagName);
404
- if (tagExp === tagName) {
405
- tagExp = newTagName
406
- }
407
- tagName = newTagName;
408
- }
526
+ if (isSelfClosing) {
527
+ ({ tagName, tagExp } = transformTagName(this.options.transformTagName, tagName, tagExp, this.options));
409
528
 
410
529
  const childNode = new xmlNode(tagName);
411
- if (tagName !== tagExp && attrExpPresent) {
412
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
530
+ if (prefixedAttrs) {
531
+ childNode[":@"] = prefixedAttrs;
413
532
  }
414
- this.addChild(currentNode, childNode, jPath, startIndex);
415
- jPath = jPath.substr(0, jPath.lastIndexOf("."));
533
+ this.addChild(currentNode, childNode, this.matcher, startIndex);
534
+ this.matcher.pop(); // Pop self-closing tag
535
+ this.isCurrentNodeStopNode = false; // Reset flag
416
536
  }
417
- else if(this.options.unpairedTags.indexOf(tagName) !== -1){//unpaired tag
537
+ else if (this.options.unpairedTags.indexOf(tagName) !== -1) {//unpaired tag
418
538
  const childNode = new xmlNode(tagName);
419
- if(tagName !== tagExp && attrExpPresent){
420
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
539
+ if (prefixedAttrs) {
540
+ childNode[":@"] = prefixedAttrs;
421
541
  }
422
- this.addChild(currentNode, childNode, jPath, startIndex);
423
- jPath = jPath.substr(0, jPath.lastIndexOf("."));
542
+ this.addChild(currentNode, childNode, this.matcher, startIndex);
543
+ this.matcher.pop(); // Pop unpaired tag
544
+ this.isCurrentNodeStopNode = false; // Reset flag
424
545
  i = result.closeIndex;
425
546
  // Continue to next iteration without changing currentNode
426
547
  continue;
@@ -433,10 +554,10 @@ const parseXml = function (xmlData) {
433
554
  }
434
555
  this.tagsNodeStack.push(currentNode);
435
556
 
436
- if (tagName !== tagExp && attrExpPresent) {
437
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
557
+ if (prefixedAttrs) {
558
+ childNode[":@"] = prefixedAttrs;
438
559
  }
439
- this.addChild(currentNode, childNode, jPath, startIndex);
560
+ this.addChild(currentNode, childNode, this.matcher, startIndex);
440
561
  currentNode = childNode;
441
562
  }
442
563
  textData = "";
@@ -450,10 +571,13 @@ const parseXml = function (xmlData) {
450
571
  return xmlObj.child;
451
572
  }
452
573
 
453
- function addChild(currentNode, childNode, jPath, startIndex) {
574
+ function addChild(currentNode, childNode, matcher, startIndex) {
454
575
  // unset startIndex if not requested
455
576
  if (!this.options.captureMetaData) startIndex = undefined;
456
- const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
577
+
578
+ // Pass jPath string or matcher based on options.jPath setting
579
+ const jPathOrMatcher = this.options.jPath ? matcher.toString() : matcher;
580
+ const result = this.options.updateTag(childNode.tagname, jPathOrMatcher, childNode[":@"])
457
581
  if (result === false) {
458
582
  //do nothing
459
583
  } else if (typeof result === "string") {
@@ -464,33 +588,40 @@ function addChild(currentNode, childNode, jPath, startIndex) {
464
588
  }
465
589
  }
466
590
 
467
- const replaceEntitiesValue = function (val, tagName, jPath) {
468
- // Performance optimization: Early return if no entities to replace
469
- if (val.indexOf('&') === -1) {
470
- return val;
471
- }
472
-
591
+ /**
592
+ * @param {object} val - Entity object with regex and val properties
593
+ * @param {string} tagName - Tag name
594
+ * @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
595
+ */
596
+ function replaceEntitiesValue(val, tagName, jPath) {
473
597
  const entityConfig = this.options.processEntities;
474
598
 
475
- if (!entityConfig.enabled) {
599
+ if (!entityConfig || !entityConfig.enabled) {
476
600
  return val;
477
601
  }
478
602
 
479
- // Check tag-specific filtering
603
+ // Check if tag is allowed to contain entities
480
604
  if (entityConfig.allowedTags) {
481
- if (!entityConfig.allowedTags.includes(tagName)) {
482
- return val; // Skip entity replacement for current tag as not set
605
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
606
+ const allowed = Array.isArray(entityConfig.allowedTags)
607
+ ? entityConfig.allowedTags.includes(tagName)
608
+ : entityConfig.allowedTags(tagName, jPathOrMatcher);
609
+
610
+ if (!allowed) {
611
+ return val;
483
612
  }
484
613
  }
485
614
 
615
+ // Apply custom tag filter if provided
486
616
  if (entityConfig.tagFilter) {
487
- if (!entityConfig.tagFilter(tagName, jPath)) {
617
+ const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
618
+ if (!entityConfig.tagFilter(tagName, jPathOrMatcher)) {
488
619
  return val; // Skip based on custom filter
489
620
  }
490
621
  }
491
622
 
492
623
  // Replace DOCTYPE entities
493
- for (let entityName in this.docTypeEntities) {
624
+ for (const entityName of Object.keys(this.docTypeEntities)) {
494
625
  const entity = this.docTypeEntities[entityName];
495
626
  const matches = val.match(entity.regx);
496
627
 
@@ -522,19 +653,38 @@ const replaceEntitiesValue = function (val, tagName, jPath) {
522
653
  }
523
654
  }
524
655
  }
525
- if (val.indexOf('&') === -1) return val; // Early exit
526
-
527
656
  // Replace standard entities
528
- for (let entityName in this.lastEntities) {
657
+ for (const entityName of Object.keys(this.lastEntities)) {
529
658
  const entity = this.lastEntities[entityName];
659
+ const matches = val.match(entity.regex);
660
+ if (matches) {
661
+ this.entityExpansionCount += matches.length;
662
+ if (entityConfig.maxTotalExpansions &&
663
+ this.entityExpansionCount > entityConfig.maxTotalExpansions) {
664
+ throw new Error(
665
+ `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
666
+ );
667
+ }
668
+ }
530
669
  val = val.replace(entity.regex, entity.val);
531
670
  }
532
- if (val.indexOf('&') === -1) return val; // Early exit
671
+ if (val.indexOf('&') === -1) return val;
533
672
 
534
673
  // Replace HTML entities if enabled
535
674
  if (this.options.htmlEntities) {
536
- for (let entityName in this.htmlEntities) {
675
+ for (const entityName of Object.keys(this.htmlEntities)) {
537
676
  const entity = this.htmlEntities[entityName];
677
+ const matches = val.match(entity.regex);
678
+ if (matches) {
679
+ //console.log(matches);
680
+ this.entityExpansionCount += matches.length;
681
+ if (entityConfig.maxTotalExpansions &&
682
+ this.entityExpansionCount > entityConfig.maxTotalExpansions) {
683
+ throw new Error(
684
+ `Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
685
+ );
686
+ }
687
+ }
538
688
  val = val.replace(entity.regex, entity.val);
539
689
  }
540
690
  }
@@ -546,13 +696,13 @@ const replaceEntitiesValue = function (val, tagName, jPath) {
546
696
  }
547
697
 
548
698
 
549
- function saveTextToParentTag(textData, parentNode, jPath, isLeafNode) {
699
+ function saveTextToParentTag(textData, parentNode, matcher, isLeafNode) {
550
700
  if (textData) { //store previously collected data as textNode
551
701
  if (isLeafNode === undefined) isLeafNode = parentNode.child.length === 0
552
702
 
553
703
  textData = this.parseTextData(textData,
554
704
  parentNode.tagname,
555
- jPath,
705
+ matcher,
556
706
  false,
557
707
  parentNode[":@"] ? Object.keys(parentNode[":@"]).length !== 0 : false,
558
708
  isLeafNode);
@@ -566,14 +716,17 @@ function saveTextToParentTag(textData, parentNode, jPath, isLeafNode) {
566
716
 
567
717
  //TODO: use jPath to simplify the logic
568
718
  /**
569
- * @param {Set} stopNodesExact
570
- * @param {Set} stopNodesWildcard
571
- * @param {string} jPath
572
- * @param {string} currentTagName
719
+ * @param {Array<Expression>} stopNodeExpressions - Array of compiled Expression objects
720
+ * @param {Matcher} matcher - Current path matcher
573
721
  */
574
- function isItStopNode(stopNodesExact, stopNodesWildcard, jPath, currentTagName) {
575
- if (stopNodesWildcard && stopNodesWildcard.has(currentTagName)) return true;
576
- if (stopNodesExact && stopNodesExact.has(jPath)) return true;
722
+ function isItStopNode(stopNodeExpressions, matcher) {
723
+ if (!stopNodeExpressions || stopNodeExpressions.length === 0) return false;
724
+
725
+ for (let i = 0; i < stopNodeExpressions.length; i++) {
726
+ if (matcher.matches(stopNodeExpressions[i])) {
727
+ return true;
728
+ }
729
+ }
577
730
  return false;
578
731
  }
579
732
 
@@ -726,4 +879,27 @@ function fromCodePoint(str, base, prefix) {
726
879
  } else {
727
880
  return prefix + str + ";";
728
881
  }
882
+ }
883
+
884
+ function transformTagName(fn, tagName, tagExp, options) {
885
+ if (fn) {
886
+ const newTagName = fn(tagName);
887
+ if (tagExp === tagName) {
888
+ tagExp = newTagName
889
+ }
890
+ tagName = newTagName;
891
+ }
892
+ tagName = sanitizeName(tagName, options);
893
+ return { tagName, tagExp };
894
+ }
895
+
896
+
897
+
898
+ function sanitizeName(name, options) {
899
+ if (criticalProperties.includes(name)) {
900
+ throw new Error(`[SECURITY] Invalid name: "${name}" is a reserved JavaScript keyword that could cause prototype pollution`);
901
+ } else if (DANGEROUS_PROPERTY_NAMES.includes(name)) {
902
+ return options.onDangerousProperty(name);
903
+ }
904
+ return name;
729
905
  }
@@ -35,7 +35,7 @@ export default class XMLParser {
35
35
  orderedObjParser.addExternalEntities(this.externalEntities);
36
36
  const orderedResult = orderedObjParser.parseXml(xmlData);
37
37
  if (this.options.preserveOrder || orderedResult === undefined) return orderedResult;
38
- else return prettify(orderedResult, this.options);
38
+ else return prettify(orderedResult, this.options, orderedObjParser.matcher);
39
39
  }
40
40
 
41
41
  /**