@cdk8s/awscdk-resolver 0.0.509 → 0.0.511

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 (31) hide show
  1. package/.jsii +3 -3
  2. package/lib/resolve.js +1 -1
  3. package/node_modules/@aws-sdk/client-cloudformation/package.json +2 -2
  4. package/node_modules/@smithy/util-waiter/dist-cjs/index.js +1 -1
  5. package/node_modules/@smithy/util-waiter/dist-es/poller.js +1 -1
  6. package/node_modules/@smithy/util-waiter/package.json +1 -1
  7. package/node_modules/fast-xml-builder/CHANGELOG.md +13 -0
  8. package/node_modules/fast-xml-builder/README.md +1 -1
  9. package/node_modules/fast-xml-builder/lib/fxb.cjs +1 -0
  10. package/node_modules/fast-xml-builder/lib/fxb.d.cts +13 -9
  11. package/node_modules/fast-xml-builder/lib/fxb.min.js +2 -0
  12. package/node_modules/fast-xml-builder/lib/fxb.min.js.map +1 -0
  13. package/node_modules/fast-xml-builder/package.json +7 -5
  14. package/node_modules/fast-xml-builder/src/fxb.d.ts +17 -3
  15. package/node_modules/fast-xml-builder/src/fxb.js +262 -21
  16. package/node_modules/fast-xml-builder/src/orderedJs2Xml.js +161 -18
  17. package/node_modules/path-expression-matcher/LICENSE +21 -0
  18. package/node_modules/path-expression-matcher/README.md +635 -0
  19. package/node_modules/path-expression-matcher/lib/pem.cjs +1 -0
  20. package/node_modules/path-expression-matcher/lib/pem.d.cts +335 -0
  21. package/node_modules/path-expression-matcher/lib/pem.min.js +2 -0
  22. package/node_modules/path-expression-matcher/lib/pem.min.js.map +1 -0
  23. package/node_modules/path-expression-matcher/package.json +78 -0
  24. package/node_modules/path-expression-matcher/src/Expression.js +232 -0
  25. package/node_modules/path-expression-matcher/src/Matcher.js +414 -0
  26. package/node_modules/path-expression-matcher/src/index.d.ts +366 -0
  27. package/node_modules/path-expression-matcher/src/index.js +28 -0
  28. package/package.json +2 -2
  29. package/node_modules/fast-xml-builder/lib/builder.cjs +0 -1
  30. package/node_modules/fast-xml-builder/lib/builder.min.js +0 -2
  31. package/node_modules/fast-xml-builder/lib/builder.min.js.map +0 -1
@@ -2,6 +2,7 @@
2
2
  //parse Empty Node as self closing node
3
3
  import buildFromOrderedJs from './orderedJs2Xml.js';
4
4
  import getIgnoreAttributesFn from "./ignoreAttributes.js";
5
+ import { Expression, Matcher } from 'path-expression-matcher';
5
6
 
6
7
  const defaultOptions = {
7
8
  attributeNamePrefix: '@_',
@@ -34,11 +35,39 @@ const defaultOptions = {
34
35
  stopNodes: [],
35
36
  // transformTagName: false,
36
37
  // transformAttributeName: false,
37
- oneListGroup: false
38
+ oneListGroup: false,
39
+ jPath: true // When true, callbacks receive string jPath; when false, receive Matcher instance
38
40
  };
39
41
 
40
42
  export default function Builder(options) {
41
43
  this.options = Object.assign({}, defaultOptions, options);
44
+
45
+ // Convert old-style stopNodes for backward compatibility
46
+ // Old syntax: "*.tag" meant "tag anywhere in tree"
47
+ // New syntax: "..tag" means "tag anywhere in tree"
48
+ if (this.options.stopNodes && Array.isArray(this.options.stopNodes)) {
49
+ this.options.stopNodes = this.options.stopNodes.map(node => {
50
+ if (typeof node === 'string' && node.startsWith('*.')) {
51
+ // Convert old wildcard syntax to deep wildcard
52
+ return '..' + node.substring(2);
53
+ }
54
+ return node;
55
+ });
56
+ }
57
+
58
+ // Pre-compile stopNode expressions for pattern matching
59
+ this.stopNodeExpressions = [];
60
+ if (this.options.stopNodes && Array.isArray(this.options.stopNodes)) {
61
+ for (let i = 0; i < this.options.stopNodes.length; i++) {
62
+ const node = this.options.stopNodes[i];
63
+ if (typeof node === 'string') {
64
+ this.stopNodeExpressions.push(new Expression(node));
65
+ } else if (node instanceof Expression) {
66
+ this.stopNodeExpressions.push(node);
67
+ }
68
+ }
69
+ }
70
+
42
71
  if (this.options.ignoreAttributes === true || this.options.attributesGroupName) {
43
72
  this.isAttribute = function (/*a*/) {
44
73
  return false;
@@ -73,14 +102,22 @@ Builder.prototype.build = function (jObj) {
73
102
  [this.options.arrayNodeName]: jObj
74
103
  }
75
104
  }
76
- return this.j2x(jObj, 0, []).val;
105
+ // Initialize matcher for path tracking
106
+ const matcher = new Matcher();
107
+ return this.j2x(jObj, 0, matcher).val;
77
108
  }
78
109
  };
79
110
 
80
- Builder.prototype.j2x = function (jObj, level, ajPath) {
111
+ Builder.prototype.j2x = function (jObj, level, matcher) {
81
112
  let attrStr = '';
82
113
  let val = '';
83
- const jPath = ajPath.join('.')
114
+
115
+ // Get jPath based on option: string for backward compatibility, or Matcher for new features
116
+ const jPath = this.options.jPath ? matcher.toString() : matcher;
117
+
118
+ // Check if current node is a stopNode (will be used for attribute encoding)
119
+ const isCurrentStopNode = this.checkStopNode(matcher);
120
+
84
121
  for (let key in jObj) {
85
122
  if (!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
86
123
  if (typeof jObj[key] === 'undefined') {
@@ -101,19 +138,34 @@ Builder.prototype.j2x = function (jObj, level, ajPath) {
101
138
  }
102
139
  // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
103
140
  } else if (jObj[key] instanceof Date) {
104
- val += this.buildTextValNode(jObj[key], key, '', level);
141
+ val += this.buildTextValNode(jObj[key], key, '', level, matcher);
105
142
  } else if (typeof jObj[key] !== 'object') {
106
143
  //premitive type
107
144
  const attr = this.isAttribute(key);
108
145
  if (attr && !this.ignoreAttributesFn(attr, jPath)) {
109
- attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
146
+ attrStr += this.buildAttrPairStr(attr, '' + jObj[key], isCurrentStopNode);
110
147
  } else if (!attr) {
111
148
  //tag value
112
149
  if (key === this.options.textNodeName) {
113
150
  let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
114
151
  val += this.replaceEntitiesValue(newval);
115
152
  } else {
116
- val += this.buildTextValNode(jObj[key], key, '', level);
153
+ // Check if this is a stopNode before building
154
+ matcher.push(key);
155
+ const isStopNode = this.checkStopNode(matcher);
156
+ matcher.pop();
157
+
158
+ if (isStopNode) {
159
+ // Build as raw content without encoding
160
+ const textValue = '' + jObj[key];
161
+ if (textValue === '') {
162
+ val += this.indentate(level) + '<' + key + this.closeTag(key) + this.tagEndChar;
163
+ } else {
164
+ val += this.indentate(level) + '<' + key + '>' + textValue + '</' + key + this.tagEndChar;
165
+ }
166
+ } else {
167
+ val += this.buildTextValNode(jObj[key], key, '', level, matcher);
168
+ }
117
169
  }
118
170
  }
119
171
  } else if (Array.isArray(jObj[key])) {
@@ -131,13 +183,18 @@ Builder.prototype.j2x = function (jObj, level, ajPath) {
131
183
  // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
132
184
  } else if (typeof item === 'object') {
133
185
  if (this.options.oneListGroup) {
134
- const result = this.j2x(item, level + 1, ajPath.concat(key));
186
+ // Push tag to matcher before recursive call
187
+ matcher.push(key);
188
+ const result = this.j2x(item, level + 1, matcher);
189
+ // Pop tag from matcher after recursive call
190
+ matcher.pop();
191
+
135
192
  listTagVal += result.val;
136
193
  if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
137
194
  listTagAttr += result.attrStr
138
195
  }
139
196
  } else {
140
- listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
197
+ listTagVal += this.processTextOrObjNode(item, key, level, matcher)
141
198
  }
142
199
  } else {
143
200
  if (this.options.oneListGroup) {
@@ -145,7 +202,22 @@ Builder.prototype.j2x = function (jObj, level, ajPath) {
145
202
  textValue = this.replaceEntitiesValue(textValue);
146
203
  listTagVal += textValue;
147
204
  } else {
148
- listTagVal += this.buildTextValNode(item, key, '', level);
205
+ // Check if this is a stopNode before building
206
+ matcher.push(key);
207
+ const isStopNode = this.checkStopNode(matcher);
208
+ matcher.pop();
209
+
210
+ if (isStopNode) {
211
+ // Build as raw content without encoding
212
+ const textValue = '' + item;
213
+ if (textValue === '') {
214
+ listTagVal += this.indentate(level) + '<' + key + this.closeTag(key) + this.tagEndChar;
215
+ } else {
216
+ listTagVal += this.indentate(level) + '<' + key + '>' + textValue + '</' + key + this.tagEndChar;
217
+ }
218
+ } else {
219
+ listTagVal += this.buildTextValNode(item, key, '', level, matcher);
220
+ }
149
221
  }
150
222
  }
151
223
  }
@@ -159,33 +231,191 @@ Builder.prototype.j2x = function (jObj, level, ajPath) {
159
231
  const Ks = Object.keys(jObj[key]);
160
232
  const L = Ks.length;
161
233
  for (let j = 0; j < L; j++) {
162
- attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
234
+ attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]], isCurrentStopNode);
163
235
  }
164
236
  } else {
165
- val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
237
+ val += this.processTextOrObjNode(jObj[key], key, level, matcher)
166
238
  }
167
239
  }
168
240
  }
169
241
  return { attrStr: attrStr, val: val };
170
242
  };
171
243
 
172
- Builder.prototype.buildAttrPairStr = function (attrName, val) {
173
- val = this.options.attributeValueProcessor(attrName, '' + val);
174
- val = this.replaceEntitiesValue(val);
244
+ Builder.prototype.buildAttrPairStr = function (attrName, val, isStopNode) {
245
+ if (!isStopNode) {
246
+ val = this.options.attributeValueProcessor(attrName, '' + val);
247
+ val = this.replaceEntitiesValue(val);
248
+ }
175
249
  if (this.options.suppressBooleanAttributes && val === "true") {
176
250
  return ' ' + attrName;
177
251
  } else return ' ' + attrName + '="' + val + '"';
178
252
  }
179
253
 
180
- function processTextOrObjNode(object, key, level, ajPath) {
181
- const result = this.j2x(object, level + 1, ajPath.concat(key));
254
+ function processTextOrObjNode(object, key, level, matcher) {
255
+ // Extract attributes to pass to matcher
256
+ const attrValues = this.extractAttributes(object);
257
+
258
+ // Push tag to matcher before recursion WITH attributes
259
+ matcher.push(key, attrValues);
260
+
261
+ // Check if this entire node is a stopNode
262
+ const isStopNode = this.checkStopNode(matcher);
263
+
264
+ if (isStopNode) {
265
+ // For stopNodes, build raw content without entity encoding
266
+ const rawContent = this.buildRawContent(object);
267
+ const attrStr = this.buildAttributesForStopNode(object);
268
+ matcher.pop();
269
+ return this.buildObjectNode(rawContent, key, attrStr, level);
270
+ }
271
+
272
+ const result = this.j2x(object, level + 1, matcher);
273
+ // Pop tag from matcher after recursion
274
+ matcher.pop();
275
+
182
276
  if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
183
- return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
277
+ return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level, matcher);
184
278
  } else {
185
279
  return this.buildObjectNode(result.val, key, result.attrStr, level);
186
280
  }
187
281
  }
188
282
 
283
+ // Helper method to extract attributes from an object
284
+ Builder.prototype.extractAttributes = function (obj) {
285
+ if (!obj || typeof obj !== 'object') return null;
286
+
287
+ const attrValues = {};
288
+ let hasAttrs = false;
289
+
290
+ // Check for attributesGroupName (when attributes are grouped)
291
+ if (this.options.attributesGroupName && obj[this.options.attributesGroupName]) {
292
+ const attrGroup = obj[this.options.attributesGroupName];
293
+ for (let attrKey in attrGroup) {
294
+ if (!Object.prototype.hasOwnProperty.call(attrGroup, attrKey)) continue;
295
+ // Remove attribute prefix if present
296
+ const cleanKey = attrKey.startsWith(this.options.attributeNamePrefix)
297
+ ? attrKey.substring(this.options.attributeNamePrefix.length)
298
+ : attrKey;
299
+ attrValues[cleanKey] = attrGroup[attrKey];
300
+ hasAttrs = true;
301
+ }
302
+ } else {
303
+ // Look for individual attributes (prefixed with attributeNamePrefix)
304
+ for (let key in obj) {
305
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
306
+ const attr = this.isAttribute(key);
307
+ if (attr) {
308
+ attrValues[attr] = obj[key];
309
+ hasAttrs = true;
310
+ }
311
+ }
312
+ }
313
+
314
+ return hasAttrs ? attrValues : null;
315
+ };
316
+
317
+ // Build raw content for stopNode without entity encoding
318
+ Builder.prototype.buildRawContent = function (obj) {
319
+ if (typeof obj === 'string') {
320
+ return obj; // Already a string, return as-is
321
+ }
322
+
323
+ if (typeof obj !== 'object' || obj === null) {
324
+ return String(obj);
325
+ }
326
+
327
+ // Check if this is a stopNode data from parser: { "#text": "raw xml", "@_attr": "val" }
328
+ if (obj[this.options.textNodeName] !== undefined) {
329
+ return obj[this.options.textNodeName]; // Return raw text without encoding
330
+ }
331
+
332
+ // Build raw XML from nested structure
333
+ let content = '';
334
+
335
+ for (let key in obj) {
336
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
337
+
338
+ // Skip attributes
339
+ if (this.isAttribute(key)) continue;
340
+ if (this.options.attributesGroupName && key === this.options.attributesGroupName) continue;
341
+
342
+ const value = obj[key];
343
+
344
+ if (key === this.options.textNodeName) {
345
+ content += value; // Raw text
346
+ } else if (Array.isArray(value)) {
347
+ // Array of same tag
348
+ for (let item of value) {
349
+ if (typeof item === 'string' || typeof item === 'number') {
350
+ content += `<${key}>${item}</${key}>`;
351
+ } else if (typeof item === 'object' && item !== null) {
352
+ const nestedContent = this.buildRawContent(item);
353
+ const nestedAttrs = this.buildAttributesForStopNode(item);
354
+ if (nestedContent === '') {
355
+ content += `<${key}${nestedAttrs}/>`;
356
+ } else {
357
+ content += `<${key}${nestedAttrs}>${nestedContent}</${key}>`;
358
+ }
359
+ }
360
+ }
361
+ } else if (typeof value === 'object' && value !== null) {
362
+ // Nested object
363
+ const nestedContent = this.buildRawContent(value);
364
+ const nestedAttrs = this.buildAttributesForStopNode(value);
365
+ if (nestedContent === '') {
366
+ content += `<${key}${nestedAttrs}/>`;
367
+ } else {
368
+ content += `<${key}${nestedAttrs}>${nestedContent}</${key}>`;
369
+ }
370
+ } else {
371
+ // Primitive value
372
+ content += `<${key}>${value}</${key}>`;
373
+ }
374
+ }
375
+
376
+ return content;
377
+ };
378
+
379
+ // Build attribute string for stopNode (no entity encoding)
380
+ Builder.prototype.buildAttributesForStopNode = function (obj) {
381
+ if (!obj || typeof obj !== 'object') return '';
382
+
383
+ let attrStr = '';
384
+
385
+ // Check for attributesGroupName (when attributes are grouped)
386
+ if (this.options.attributesGroupName && obj[this.options.attributesGroupName]) {
387
+ const attrGroup = obj[this.options.attributesGroupName];
388
+ for (let attrKey in attrGroup) {
389
+ if (!Object.prototype.hasOwnProperty.call(attrGroup, attrKey)) continue;
390
+ const cleanKey = attrKey.startsWith(this.options.attributeNamePrefix)
391
+ ? attrKey.substring(this.options.attributeNamePrefix.length)
392
+ : attrKey;
393
+ const val = attrGroup[attrKey];
394
+ if (val === true && this.options.suppressBooleanAttributes) {
395
+ attrStr += ' ' + cleanKey;
396
+ } else {
397
+ attrStr += ' ' + cleanKey + '="' + val + '"'; // No encoding for stopNode
398
+ }
399
+ }
400
+ } else {
401
+ // Look for individual attributes
402
+ for (let key in obj) {
403
+ if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
404
+ const attr = this.isAttribute(key);
405
+ if (attr) {
406
+ const val = obj[key];
407
+ if (val === true && this.options.suppressBooleanAttributes) {
408
+ attrStr += ' ' + attr;
409
+ } else {
410
+ attrStr += ' ' + attr + '="' + val + '"'; // No encoding for stopNode
411
+ }
412
+ }
413
+ }
414
+ }
415
+
416
+ return attrStr;
417
+ };
418
+
189
419
  Builder.prototype.buildObjectNode = function (val, key, attrStr, level) {
190
420
  if (val === "") {
191
421
  if (key[0] === "?") return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
@@ -228,6 +458,17 @@ Builder.prototype.closeTag = function (key) {
228
458
  return closeTag;
229
459
  }
230
460
 
461
+ Builder.prototype.checkStopNode = function (matcher) {
462
+ if (!this.stopNodeExpressions || this.stopNodeExpressions.length === 0) return false;
463
+
464
+ for (let i = 0; i < this.stopNodeExpressions.length; i++) {
465
+ if (matcher.matches(this.stopNodeExpressions[i])) {
466
+ return true;
467
+ }
468
+ }
469
+ return false;
470
+ }
471
+
231
472
  function buildEmptyObjNode(val, key, attrStr, level) {
232
473
  if (val !== '') {
233
474
  return this.buildObjectNode(val, key, attrStr, level);
@@ -240,7 +481,7 @@ function buildEmptyObjNode(val, key, attrStr, level) {
240
481
  }
241
482
  }
242
483
 
243
- Builder.prototype.buildTextValNode = function (val, key, attrStr, level) {
484
+ Builder.prototype.buildTextValNode = function (val, key, attrStr, level, matcher) {
244
485
  if (this.options.cdataPropName !== false && key === this.options.cdataPropName) {
245
486
  return this.indentate(level) + `<![CDATA[${val}]]>` + this.newLine;
246
487
  } else if (this.options.commentPropName !== false && key === this.options.commentPropName) {
@@ -248,6 +489,7 @@ Builder.prototype.buildTextValNode = function (val, key, attrStr, level) {
248
489
  } else if (key[0] === "?") {//PI tag
249
490
  return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
250
491
  } else {
492
+ // Normal processing: apply tagValueProcessor and entity replacement
251
493
  let textValue = this.options.tagValueProcessor(key, val);
252
494
  textValue = this.replaceEntitiesValue(textValue);
253
495
 
@@ -281,5 +523,4 @@ function isAttribute(name /*, options*/) {
281
523
  } else {
282
524
  return false;
283
525
  }
284
- }
285
-
526
+ }
@@ -1,3 +1,5 @@
1
+ import { Expression, Matcher } from 'path-expression-matcher';
2
+
1
3
  const EOL = "\n";
2
4
 
3
5
  /**
@@ -11,10 +13,27 @@ export default function toXml(jArray, options) {
11
13
  if (options.format && options.indentBy.length > 0) {
12
14
  indentation = EOL;
13
15
  }
14
- return arrToStr(jArray, options, "", indentation);
16
+
17
+ // Pre-compile stopNode expressions for pattern matching
18
+ const stopNodeExpressions = [];
19
+ if (options.stopNodes && Array.isArray(options.stopNodes)) {
20
+ for (let i = 0; i < options.stopNodes.length; i++) {
21
+ const node = options.stopNodes[i];
22
+ if (typeof node === 'string') {
23
+ stopNodeExpressions.push(new Expression(node));
24
+ } else if (node instanceof Expression) {
25
+ stopNodeExpressions.push(node);
26
+ }
27
+ }
28
+ }
29
+
30
+ // Initialize matcher for path tracking
31
+ const matcher = new Matcher();
32
+
33
+ return arrToStr(jArray, options, indentation, matcher, stopNodeExpressions);
15
34
  }
16
35
 
17
- function arrToStr(arr, options, jPath, indentation) {
36
+ function arrToStr(arr, options, indentation, matcher, stopNodeExpressions) {
18
37
  let xmlStr = "";
19
38
  let isPreviousElementTag = false;
20
39
 
@@ -34,13 +53,18 @@ function arrToStr(arr, options, jPath, indentation) {
34
53
  const tagName = propName(tagObj);
35
54
  if (tagName === undefined) continue;
36
55
 
37
- let newJPath = "";
38
- if (jPath.length === 0) newJPath = tagName
39
- else newJPath = `${jPath}.${tagName}`;
56
+ // Extract attributes from ":@" property
57
+ const attrValues = extractAttributeValues(tagObj[":@"], options);
58
+
59
+ // Push tag to matcher WITH attributes
60
+ matcher.push(tagName, attrValues);
61
+
62
+ // Check if this is a stop node using Expression matching
63
+ const isStopNode = checkStopNode(matcher, stopNodeExpressions);
40
64
 
41
65
  if (tagName === options.textNodeName) {
42
66
  let tagText = tagObj[tagName];
43
- if (!isStopNode(newJPath, options)) {
67
+ if (!isStopNode) {
44
68
  tagText = options.tagValueProcessor(tagName, tagText);
45
69
  tagText = replaceEntitiesValue(tagText, options);
46
70
  }
@@ -49,6 +73,7 @@ function arrToStr(arr, options, jPath, indentation) {
49
73
  }
50
74
  xmlStr += tagText;
51
75
  isPreviousElementTag = false;
76
+ matcher.pop();
52
77
  continue;
53
78
  } else if (tagName === options.cdataPropName) {
54
79
  if (isPreviousElementTag) {
@@ -56,27 +81,41 @@ function arrToStr(arr, options, jPath, indentation) {
56
81
  }
57
82
  xmlStr += `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
58
83
  isPreviousElementTag = false;
84
+ matcher.pop();
59
85
  continue;
60
86
  } else if (tagName === options.commentPropName) {
61
87
  xmlStr += indentation + `<!--${tagObj[tagName][0][options.textNodeName]}-->`;
62
88
  isPreviousElementTag = true;
89
+ matcher.pop();
63
90
  continue;
64
91
  } else if (tagName[0] === "?") {
65
- const attStr = attr_to_str(tagObj[":@"], options);
92
+ const attStr = attr_to_str(tagObj[":@"], options, isStopNode);
66
93
  const tempInd = tagName === "?xml" ? "" : indentation;
67
94
  let piTextNodeName = tagObj[tagName][0][options.textNodeName];
68
95
  piTextNodeName = piTextNodeName.length !== 0 ? " " + piTextNodeName : ""; //remove extra spacing
69
96
  xmlStr += tempInd + `<${tagName}${piTextNodeName}${attStr}?>`;
70
97
  isPreviousElementTag = true;
98
+ matcher.pop();
71
99
  continue;
72
100
  }
101
+
73
102
  let newIdentation = indentation;
74
103
  if (newIdentation !== "") {
75
104
  newIdentation += options.indentBy;
76
105
  }
77
- const attStr = attr_to_str(tagObj[":@"], options);
106
+
107
+ // Pass isStopNode to attr_to_str so attributes are also not processed for stopNodes
108
+ const attStr = attr_to_str(tagObj[":@"], options, isStopNode);
78
109
  const tagStart = indentation + `<${tagName}${attStr}`;
79
- const tagValue = arrToStr(tagObj[tagName], options, newJPath, newIdentation);
110
+
111
+ // If this is a stopNode, get raw content without processing
112
+ let tagValue;
113
+ if (isStopNode) {
114
+ tagValue = getRawContent(tagObj[tagName], options);
115
+ } else {
116
+ tagValue = arrToStr(tagObj[tagName], options, newIdentation, matcher, stopNodeExpressions);
117
+ }
118
+
80
119
  if (options.unpairedTags.indexOf(tagName) !== -1) {
81
120
  if (options.suppressUnpairedNode) xmlStr += tagStart + ">";
82
121
  else xmlStr += tagStart + "/>";
@@ -94,11 +133,104 @@ function arrToStr(arr, options, jPath, indentation) {
94
133
  xmlStr += `</${tagName}>`;
95
134
  }
96
135
  isPreviousElementTag = true;
136
+
137
+ // Pop tag from matcher
138
+ matcher.pop();
97
139
  }
98
140
 
99
141
  return xmlStr;
100
142
  }
101
143
 
144
+ /**
145
+ * Extract attribute values from the ":@" object and return as plain object
146
+ * for passing to matcher.push()
147
+ */
148
+ function extractAttributeValues(attrMap, options) {
149
+ if (!attrMap || options.ignoreAttributes) return null;
150
+
151
+ const attrValues = {};
152
+ let hasAttrs = false;
153
+
154
+ for (let attr in attrMap) {
155
+ if (!Object.prototype.hasOwnProperty.call(attrMap, attr)) continue;
156
+ // Remove the attribute prefix to get clean attribute name
157
+ const cleanAttrName = attr.startsWith(options.attributeNamePrefix)
158
+ ? attr.substr(options.attributeNamePrefix.length)
159
+ : attr;
160
+ attrValues[cleanAttrName] = attrMap[attr];
161
+ hasAttrs = true;
162
+ }
163
+
164
+ return hasAttrs ? attrValues : null;
165
+ }
166
+
167
+ /**
168
+ * Extract raw content from a stopNode without any processing
169
+ * This preserves the content exactly as-is, including special characters
170
+ */
171
+ function getRawContent(arr, options) {
172
+ if (!Array.isArray(arr)) {
173
+ // Non-array values return as-is
174
+ if (arr !== undefined && arr !== null) {
175
+ return arr.toString();
176
+ }
177
+ return "";
178
+ }
179
+
180
+ let content = "";
181
+ for (let i = 0; i < arr.length; i++) {
182
+ const item = arr[i];
183
+ const tagName = propName(item);
184
+
185
+ if (tagName === options.textNodeName) {
186
+ // Raw text content - NO processing, NO entity replacement
187
+ content += item[tagName];
188
+ } else if (tagName === options.cdataPropName) {
189
+ // CDATA content
190
+ content += item[tagName][0][options.textNodeName];
191
+ } else if (tagName === options.commentPropName) {
192
+ // Comment content
193
+ content += item[tagName][0][options.textNodeName];
194
+ } else if (tagName && tagName[0] === "?") {
195
+ // Processing instruction - skip for stopNodes
196
+ continue;
197
+ } else if (tagName) {
198
+ // Nested tags within stopNode
199
+ // Recursively get raw content and reconstruct the tag
200
+ // For stopNodes, we don't process attributes either
201
+ const attStr = attr_to_str_raw(item[":@"], options);
202
+ const nestedContent = getRawContent(item[tagName], options);
203
+
204
+ if (!nestedContent || nestedContent.length === 0) {
205
+ content += `<${tagName}${attStr}/>`;
206
+ } else {
207
+ content += `<${tagName}${attStr}>${nestedContent}</${tagName}>`;
208
+ }
209
+ }
210
+ }
211
+ return content;
212
+ }
213
+
214
+ /**
215
+ * Build attribute string for stopNodes - NO entity replacement
216
+ */
217
+ function attr_to_str_raw(attrMap, options) {
218
+ let attrStr = "";
219
+ if (attrMap && !options.ignoreAttributes) {
220
+ for (let attr in attrMap) {
221
+ if (!Object.prototype.hasOwnProperty.call(attrMap, attr)) continue;
222
+ // For stopNodes, use raw value without processing
223
+ let attrVal = attrMap[attr];
224
+ if (attrVal === true && options.suppressBooleanAttributes) {
225
+ attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
226
+ } else {
227
+ attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
228
+ }
229
+ }
230
+ }
231
+ return attrStr;
232
+ }
233
+
102
234
  function propName(obj) {
103
235
  const keys = Object.keys(obj);
104
236
  for (let i = 0; i < keys.length; i++) {
@@ -108,13 +240,22 @@ function propName(obj) {
108
240
  }
109
241
  }
110
242
 
111
- function attr_to_str(attrMap, options) {
243
+ function attr_to_str(attrMap, options, isStopNode) {
112
244
  let attrStr = "";
113
245
  if (attrMap && !options.ignoreAttributes) {
114
246
  for (let attr in attrMap) {
115
247
  if (!Object.prototype.hasOwnProperty.call(attrMap, attr)) continue;
116
- let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
117
- attrVal = replaceEntitiesValue(attrVal, options);
248
+ let attrVal;
249
+
250
+ if (isStopNode) {
251
+ // For stopNodes, use raw value without any processing
252
+ attrVal = attrMap[attr];
253
+ } else {
254
+ // Normal processing: apply attributeValueProcessor and entity replacement
255
+ attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
256
+ attrVal = replaceEntitiesValue(attrVal, options);
257
+ }
258
+
118
259
  if (attrVal === true && options.suppressBooleanAttributes) {
119
260
  attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
120
261
  } else {
@@ -125,11 +266,13 @@ function attr_to_str(attrMap, options) {
125
266
  return attrStr;
126
267
  }
127
268
 
128
- function isStopNode(jPath, options) {
129
- jPath = jPath.substr(0, jPath.length - options.textNodeName.length - 1);
130
- let tagName = jPath.substr(jPath.lastIndexOf(".") + 1);
131
- for (let index in options.stopNodes) {
132
- if (options.stopNodes[index] === jPath || options.stopNodes[index] === "*." + tagName) return true;
269
+ function checkStopNode(matcher, stopNodeExpressions) {
270
+ if (!stopNodeExpressions || stopNodeExpressions.length === 0) return false;
271
+
272
+ for (let i = 0; i < stopNodeExpressions.length; i++) {
273
+ if (matcher.matches(stopNodeExpressions[i])) {
274
+ return true;
275
+ }
133
276
  }
134
277
  return false;
135
278
  }
@@ -142,4 +285,4 @@ function replaceEntitiesValue(textValue, options) {
142
285
  }
143
286
  }
144
287
  return textValue;
145
- }
288
+ }
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2024
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.