fast-xml-parser 5.4.2 → 5.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +5 -0
- package/README.md +8 -7
- package/lib/fxbuilder.min.js +1 -1
- package/lib/fxbuilder.min.js.map +1 -1
- package/lib/fxp.cjs +1 -1
- package/lib/fxp.d.cts +46 -19
- package/lib/fxp.min.js +1 -1
- package/lib/fxp.min.js.map +1 -1
- package/lib/fxparser.min.js +1 -1
- package/lib/fxparser.min.js.map +1 -1
- package/package.json +4 -3
- package/src/fxp.d.ts +46 -19
- package/src/xmlparser/OptionsBuilder.js +13 -0
- package/src/xmlparser/OrderedObjParser.js +238 -89
- package/src/xmlparser/XMLParser.js +1 -1
- package/src/xmlparser/node2json.js +65 -14
|
@@ -6,6 +6,8 @@ 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';
|
|
10
|
+
|
|
9
11
|
|
|
10
12
|
// const regx =
|
|
11
13
|
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
|
|
@@ -14,6 +16,57 @@ import getIgnoreAttributesFn from "../ignoreAttributes.js";
|
|
|
14
16
|
//const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
|
|
15
17
|
//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
|
|
16
18
|
|
|
19
|
+
// Helper functions for attribute and namespace handling
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Extract raw attributes (without prefix) from prefixed attribute map
|
|
23
|
+
* @param {object} prefixedAttrs - Attributes with prefix from buildAttributesMap
|
|
24
|
+
* @param {object} options - Parser options containing attributeNamePrefix
|
|
25
|
+
* @returns {object} Raw attributes for matcher
|
|
26
|
+
*/
|
|
27
|
+
function extractRawAttributes(prefixedAttrs, options) {
|
|
28
|
+
if (!prefixedAttrs) return {};
|
|
29
|
+
|
|
30
|
+
// Handle attributesGroupName option
|
|
31
|
+
const attrs = options.attributesGroupName
|
|
32
|
+
? prefixedAttrs[options.attributesGroupName]
|
|
33
|
+
: prefixedAttrs;
|
|
34
|
+
|
|
35
|
+
if (!attrs) return {};
|
|
36
|
+
|
|
37
|
+
const rawAttrs = {};
|
|
38
|
+
for (const key in attrs) {
|
|
39
|
+
// Remove the attribute prefix to get raw name
|
|
40
|
+
if (key.startsWith(options.attributeNamePrefix)) {
|
|
41
|
+
const rawName = key.substring(options.attributeNamePrefix.length);
|
|
42
|
+
rawAttrs[rawName] = attrs[key];
|
|
43
|
+
} else {
|
|
44
|
+
// Attribute without prefix (shouldn't normally happen, but be safe)
|
|
45
|
+
rawAttrs[key] = attrs[key];
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
return rawAttrs;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Extract namespace from raw tag name
|
|
53
|
+
* @param {string} rawTagName - Tag name possibly with namespace (e.g., "soap:Envelope")
|
|
54
|
+
* @returns {string|undefined} Namespace or undefined
|
|
55
|
+
*/
|
|
56
|
+
function extractNamespace(rawTagName) {
|
|
57
|
+
if (!rawTagName || typeof rawTagName !== 'string') return undefined;
|
|
58
|
+
|
|
59
|
+
const colonIndex = rawTagName.indexOf(':');
|
|
60
|
+
if (colonIndex !== -1 && colonIndex > 0) {
|
|
61
|
+
const ns = rawTagName.substring(0, colonIndex);
|
|
62
|
+
// Don't treat xmlns as a namespace
|
|
63
|
+
if (ns !== 'xmlns') {
|
|
64
|
+
return ns;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
17
70
|
export default class OrderedObjParser {
|
|
18
71
|
constructor(options) {
|
|
19
72
|
this.options = options;
|
|
@@ -58,16 +111,23 @@ export default class OrderedObjParser {
|
|
|
58
111
|
this.entityExpansionCount = 0;
|
|
59
112
|
this.currentExpandedLength = 0;
|
|
60
113
|
|
|
114
|
+
// Initialize path matcher for path-expression-matcher
|
|
115
|
+
this.matcher = new Matcher();
|
|
116
|
+
|
|
117
|
+
// Flag to track if current node is a stop node (optimization)
|
|
118
|
+
this.isCurrentNodeStopNode = false;
|
|
119
|
+
|
|
120
|
+
// Pre-compile stopNodes expressions
|
|
61
121
|
if (this.options.stopNodes && this.options.stopNodes.length > 0) {
|
|
62
|
-
this.
|
|
63
|
-
this.stopNodesWildcard = new Set();
|
|
122
|
+
this.stopNodeExpressions = [];
|
|
64
123
|
for (let i = 0; i < this.options.stopNodes.length; i++) {
|
|
65
124
|
const stopNodeExp = this.options.stopNodes[i];
|
|
66
|
-
if (typeof stopNodeExp
|
|
67
|
-
|
|
68
|
-
this.
|
|
69
|
-
} else {
|
|
70
|
-
|
|
125
|
+
if (typeof stopNodeExp === 'string') {
|
|
126
|
+
// Convert string to Expression object
|
|
127
|
+
this.stopNodeExpressions.push(new Expression(stopNodeExp));
|
|
128
|
+
} else if (stopNodeExp instanceof Expression) {
|
|
129
|
+
// Already an Expression object
|
|
130
|
+
this.stopNodeExpressions.push(stopNodeExp);
|
|
71
131
|
}
|
|
72
132
|
}
|
|
73
133
|
}
|
|
@@ -90,7 +150,7 @@ function addExternalEntities(externalEntities) {
|
|
|
90
150
|
/**
|
|
91
151
|
* @param {string} val
|
|
92
152
|
* @param {string} tagName
|
|
93
|
-
* @param {string} jPath
|
|
153
|
+
* @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
|
|
94
154
|
* @param {boolean} dontTrim
|
|
95
155
|
* @param {boolean} hasAttributes
|
|
96
156
|
* @param {boolean} isLeafNode
|
|
@@ -104,7 +164,9 @@ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode,
|
|
|
104
164
|
if (val.length > 0) {
|
|
105
165
|
if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath);
|
|
106
166
|
|
|
107
|
-
|
|
167
|
+
// Pass jPath string or matcher based on options.jPath setting
|
|
168
|
+
const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
|
|
169
|
+
const newval = this.options.tagValueProcessor(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode);
|
|
108
170
|
if (newval === null || newval === undefined) {
|
|
109
171
|
//don't parse
|
|
110
172
|
return val;
|
|
@@ -151,13 +213,42 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
151
213
|
const matches = getAllMatches(attrStr, attrsRegx);
|
|
152
214
|
const len = matches.length; //don't make it inline
|
|
153
215
|
const attrs = {};
|
|
216
|
+
|
|
217
|
+
// First pass: parse all attributes and update matcher with raw values
|
|
218
|
+
// This ensures the matcher has all attribute values when processors run
|
|
219
|
+
const rawAttrsForMatcher = {};
|
|
220
|
+
for (let i = 0; i < len; i++) {
|
|
221
|
+
const attrName = this.resolveNameSpace(matches[i][1]);
|
|
222
|
+
const oldVal = matches[i][4];
|
|
223
|
+
|
|
224
|
+
if (attrName.length && oldVal !== undefined) {
|
|
225
|
+
let parsedVal = oldVal;
|
|
226
|
+
if (this.options.trimValues) {
|
|
227
|
+
parsedVal = parsedVal.trim();
|
|
228
|
+
}
|
|
229
|
+
parsedVal = this.replaceEntitiesValue(parsedVal, tagName, jPath);
|
|
230
|
+
rawAttrsForMatcher[attrName] = parsedVal;
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
// Update matcher with raw attribute values BEFORE running processors
|
|
235
|
+
if (Object.keys(rawAttrsForMatcher).length > 0 && typeof jPath === 'object' && jPath.updateCurrent) {
|
|
236
|
+
jPath.updateCurrent(rawAttrsForMatcher);
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// Second pass: now process attributes with matcher having full attribute context
|
|
154
240
|
for (let i = 0; i < len; i++) {
|
|
155
241
|
const attrName = this.resolveNameSpace(matches[i][1]);
|
|
156
|
-
|
|
242
|
+
|
|
243
|
+
// Convert jPath to string if needed for ignoreAttributesFn
|
|
244
|
+
const jPathStr = this.options.jPath ? jPath.toString() : jPath;
|
|
245
|
+
if (this.ignoreAttributesFn(attrName, jPathStr)) {
|
|
157
246
|
continue
|
|
158
247
|
}
|
|
248
|
+
|
|
159
249
|
let oldVal = matches[i][4];
|
|
160
250
|
let aName = this.options.attributeNamePrefix + attrName;
|
|
251
|
+
|
|
161
252
|
if (attrName.length) {
|
|
162
253
|
if (this.options.transformAttributeName) {
|
|
163
254
|
aName = this.options.transformAttributeName(aName);
|
|
@@ -169,7 +260,10 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
169
260
|
oldVal = oldVal.trim();
|
|
170
261
|
}
|
|
171
262
|
oldVal = this.replaceEntitiesValue(oldVal, tagName, jPath);
|
|
172
|
-
|
|
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
|
-
|
|
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;
|
|
@@ -234,22 +331,22 @@ const parseXml = function (xmlData) {
|
|
|
234
331
|
}
|
|
235
332
|
|
|
236
333
|
if (currentNode) {
|
|
237
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
334
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
|
|
238
335
|
}
|
|
239
336
|
|
|
240
337
|
//check if last tag of nested tag was unpaired tag
|
|
241
|
-
const lastTagName =
|
|
338
|
+
const lastTagName = this.matcher.getCurrentTag();
|
|
242
339
|
if (tagName && this.options.unpairedTags.indexOf(tagName) !== -1) {
|
|
243
340
|
throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
|
|
244
341
|
}
|
|
245
|
-
let propIndex = 0
|
|
246
342
|
if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) {
|
|
247
|
-
|
|
343
|
+
// Pop the unpaired tag
|
|
344
|
+
this.matcher.pop();
|
|
248
345
|
this.tagsNodeStack.pop();
|
|
249
|
-
} else {
|
|
250
|
-
propIndex = jPath.lastIndexOf(".");
|
|
251
346
|
}
|
|
252
|
-
|
|
347
|
+
// Pop the closing tag
|
|
348
|
+
this.matcher.pop();
|
|
349
|
+
this.isCurrentNodeStopNode = false; // Reset flag when closing tag
|
|
253
350
|
|
|
254
351
|
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
|
|
255
352
|
textData = "";
|
|
@@ -259,7 +356,7 @@ const parseXml = function (xmlData) {
|
|
|
259
356
|
let tagData = readTagExp(xmlData, i, false, "?>");
|
|
260
357
|
if (!tagData) throw new Error("Pi Tag is not closed.");
|
|
261
358
|
|
|
262
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
359
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
|
|
263
360
|
if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) {
|
|
264
361
|
//do nothing
|
|
265
362
|
} else {
|
|
@@ -268,9 +365,9 @@ const parseXml = function (xmlData) {
|
|
|
268
365
|
childNode.add(this.options.textNodeName, "");
|
|
269
366
|
|
|
270
367
|
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
|
|
271
|
-
childNode[":@"] = this.buildAttributesMap(tagData.tagExp,
|
|
368
|
+
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName);
|
|
272
369
|
}
|
|
273
|
-
this.addChild(currentNode, childNode,
|
|
370
|
+
this.addChild(currentNode, childNode, this.matcher, i);
|
|
274
371
|
}
|
|
275
372
|
|
|
276
373
|
|
|
@@ -280,7 +377,7 @@ const parseXml = function (xmlData) {
|
|
|
280
377
|
if (this.options.commentPropName) {
|
|
281
378
|
const comment = xmlData.substring(i + 4, endIndex - 2);
|
|
282
379
|
|
|
283
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
380
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
|
|
284
381
|
|
|
285
382
|
currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
|
|
286
383
|
}
|
|
@@ -293,9 +390,9 @@ const parseXml = function (xmlData) {
|
|
|
293
390
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
|
|
294
391
|
const tagExp = xmlData.substring(i + 9, closeIndex);
|
|
295
392
|
|
|
296
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
393
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.matcher);
|
|
297
394
|
|
|
298
|
-
let val = this.parseTextData(tagExp, currentNode.tagname,
|
|
395
|
+
let val = this.parseTextData(tagExp, currentNode.tagname, this.matcher, true, false, true, true);
|
|
299
396
|
if (val == undefined) val = "";
|
|
300
397
|
|
|
301
398
|
//cdata should be set even if it is 0 length string
|
|
@@ -308,6 +405,14 @@ const parseXml = function (xmlData) {
|
|
|
308
405
|
i = closeIndex + 2;
|
|
309
406
|
} else {//Opening tag
|
|
310
407
|
let result = readTagExp(xmlData, i, this.options.removeNSPrefix);
|
|
408
|
+
|
|
409
|
+
// Safety check: readTagExp can return undefined
|
|
410
|
+
if (!result) {
|
|
411
|
+
// Log context for debugging
|
|
412
|
+
const context = xmlData.substring(Math.max(0, i - 50), Math.min(xmlData.length, i + 50));
|
|
413
|
+
throw new Error(`readTagExp returned undefined at position ${i}. Context: "${context}"`);
|
|
414
|
+
}
|
|
415
|
+
|
|
311
416
|
let tagName = result.tagName;
|
|
312
417
|
const rawTagName = result.rawTagName;
|
|
313
418
|
let tagExp = result.tagExp;
|
|
@@ -334,7 +439,7 @@ const parseXml = function (xmlData) {
|
|
|
334
439
|
if (currentNode && textData) {
|
|
335
440
|
if (currentNode.tagname !== '!xml') {
|
|
336
441
|
//when nested tag is found
|
|
337
|
-
textData = this.saveTextToParentTag(textData, currentNode,
|
|
442
|
+
textData = this.saveTextToParentTag(textData, currentNode, this.matcher, false);
|
|
338
443
|
}
|
|
339
444
|
}
|
|
340
445
|
|
|
@@ -342,28 +447,65 @@ const parseXml = function (xmlData) {
|
|
|
342
447
|
const lastTag = currentNode;
|
|
343
448
|
if (lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) {
|
|
344
449
|
currentNode = this.tagsNodeStack.pop();
|
|
345
|
-
|
|
450
|
+
this.matcher.pop();
|
|
451
|
+
}
|
|
452
|
+
|
|
453
|
+
// Clean up self-closing syntax BEFORE processing attributes
|
|
454
|
+
// This is where tagExp gets the trailing / removed
|
|
455
|
+
let isSelfClosing = false;
|
|
456
|
+
if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
|
|
457
|
+
isSelfClosing = true;
|
|
458
|
+
if (tagName[tagName.length - 1] === "/") {
|
|
459
|
+
tagName = tagName.substr(0, tagName.length - 1);
|
|
460
|
+
tagExp = tagName;
|
|
461
|
+
} else {
|
|
462
|
+
tagExp = tagExp.substr(0, tagExp.length - 1);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// Re-check attrExpPresent after cleaning
|
|
466
|
+
attrExpPresent = (tagName !== tagExp);
|
|
467
|
+
}
|
|
468
|
+
|
|
469
|
+
// Now process attributes with CLEAN tagExp (no trailing /)
|
|
470
|
+
let prefixedAttrs = null;
|
|
471
|
+
let rawAttrs = {};
|
|
472
|
+
let namespace = undefined;
|
|
473
|
+
|
|
474
|
+
// Extract namespace from rawTagName
|
|
475
|
+
namespace = extractNamespace(rawTagName);
|
|
476
|
+
|
|
477
|
+
// Push tag to matcher FIRST (with empty attrs for now) so callbacks see correct path
|
|
478
|
+
if (tagName !== xmlObj.tagname) {
|
|
479
|
+
this.matcher.push(tagName, {}, namespace);
|
|
346
480
|
}
|
|
481
|
+
|
|
482
|
+
// Now build attributes - callbacks will see correct matcher state
|
|
483
|
+
if (tagName !== tagExp && attrExpPresent) {
|
|
484
|
+
// Build attributes (returns prefixed attributes for the tree)
|
|
485
|
+
// Note: buildAttributesMap now internally updates the matcher with raw attributes
|
|
486
|
+
prefixedAttrs = this.buildAttributesMap(tagExp, this.matcher, tagName);
|
|
487
|
+
|
|
488
|
+
if (prefixedAttrs) {
|
|
489
|
+
// Extract raw attributes (without prefix) for our use
|
|
490
|
+
rawAttrs = extractRawAttributes(prefixedAttrs, this.options);
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
|
|
494
|
+
// Now check if this is a stop node (after attributes are set)
|
|
347
495
|
if (tagName !== xmlObj.tagname) {
|
|
348
|
-
|
|
496
|
+
this.isCurrentNodeStopNode = this.isItStopNode(this.stopNodeExpressions, this.matcher);
|
|
349
497
|
}
|
|
498
|
+
|
|
350
499
|
const startIndex = i;
|
|
351
|
-
if (this.
|
|
500
|
+
if (this.isCurrentNodeStopNode) {
|
|
352
501
|
let tagContent = "";
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
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
|
-
}
|
|
502
|
+
|
|
503
|
+
// For self-closing tags, content is empty
|
|
504
|
+
if (isSelfClosing) {
|
|
362
505
|
i = result.closeIndex;
|
|
363
506
|
}
|
|
364
507
|
//unpaired tag
|
|
365
508
|
else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
|
|
366
|
-
|
|
367
509
|
i = result.closeIndex;
|
|
368
510
|
}
|
|
369
511
|
//normal tag
|
|
@@ -377,28 +519,20 @@ const parseXml = function (xmlData) {
|
|
|
377
519
|
|
|
378
520
|
const childNode = new xmlNode(tagName);
|
|
379
521
|
|
|
380
|
-
if (
|
|
381
|
-
childNode[":@"] =
|
|
382
|
-
}
|
|
383
|
-
if (tagContent) {
|
|
384
|
-
tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
|
|
522
|
+
if (prefixedAttrs) {
|
|
523
|
+
childNode[":@"] = prefixedAttrs;
|
|
385
524
|
}
|
|
386
525
|
|
|
387
|
-
|
|
526
|
+
// For stop nodes, store raw content as-is without any processing
|
|
388
527
|
childNode.add(this.options.textNodeName, tagContent);
|
|
389
528
|
|
|
390
|
-
this.
|
|
529
|
+
this.matcher.pop(); // Pop the stop node tag
|
|
530
|
+
this.isCurrentNodeStopNode = false; // Reset flag
|
|
531
|
+
|
|
532
|
+
this.addChild(currentNode, childNode, this.matcher, startIndex);
|
|
391
533
|
} else {
|
|
392
534
|
//selfClosing tag
|
|
393
|
-
if (
|
|
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
|
-
|
|
535
|
+
if (isSelfClosing) {
|
|
402
536
|
if (this.options.transformTagName) {
|
|
403
537
|
const newTagName = this.options.transformTagName(tagName);
|
|
404
538
|
if (tagExp === tagName) {
|
|
@@ -408,19 +542,21 @@ const parseXml = function (xmlData) {
|
|
|
408
542
|
}
|
|
409
543
|
|
|
410
544
|
const childNode = new xmlNode(tagName);
|
|
411
|
-
if (
|
|
412
|
-
childNode[":@"] =
|
|
545
|
+
if (prefixedAttrs) {
|
|
546
|
+
childNode[":@"] = prefixedAttrs;
|
|
413
547
|
}
|
|
414
|
-
this.addChild(currentNode, childNode,
|
|
415
|
-
|
|
548
|
+
this.addChild(currentNode, childNode, this.matcher, startIndex);
|
|
549
|
+
this.matcher.pop(); // Pop self-closing tag
|
|
550
|
+
this.isCurrentNodeStopNode = false; // Reset flag
|
|
416
551
|
}
|
|
417
|
-
else if(this.options.unpairedTags.indexOf(tagName) !== -1){//unpaired tag
|
|
552
|
+
else if (this.options.unpairedTags.indexOf(tagName) !== -1) {//unpaired tag
|
|
418
553
|
const childNode = new xmlNode(tagName);
|
|
419
|
-
if(
|
|
420
|
-
childNode[":@"] =
|
|
554
|
+
if (prefixedAttrs) {
|
|
555
|
+
childNode[":@"] = prefixedAttrs;
|
|
421
556
|
}
|
|
422
|
-
this.addChild(currentNode, childNode,
|
|
423
|
-
|
|
557
|
+
this.addChild(currentNode, childNode, this.matcher, startIndex);
|
|
558
|
+
this.matcher.pop(); // Pop unpaired tag
|
|
559
|
+
this.isCurrentNodeStopNode = false; // Reset flag
|
|
424
560
|
i = result.closeIndex;
|
|
425
561
|
// Continue to next iteration without changing currentNode
|
|
426
562
|
continue;
|
|
@@ -433,10 +569,10 @@ const parseXml = function (xmlData) {
|
|
|
433
569
|
}
|
|
434
570
|
this.tagsNodeStack.push(currentNode);
|
|
435
571
|
|
|
436
|
-
if (
|
|
437
|
-
childNode[":@"] =
|
|
572
|
+
if (prefixedAttrs) {
|
|
573
|
+
childNode[":@"] = prefixedAttrs;
|
|
438
574
|
}
|
|
439
|
-
this.addChild(currentNode, childNode,
|
|
575
|
+
this.addChild(currentNode, childNode, this.matcher, startIndex);
|
|
440
576
|
currentNode = childNode;
|
|
441
577
|
}
|
|
442
578
|
textData = "";
|
|
@@ -450,10 +586,13 @@ const parseXml = function (xmlData) {
|
|
|
450
586
|
return xmlObj.child;
|
|
451
587
|
}
|
|
452
588
|
|
|
453
|
-
function addChild(currentNode, childNode,
|
|
589
|
+
function addChild(currentNode, childNode, matcher, startIndex) {
|
|
454
590
|
// unset startIndex if not requested
|
|
455
591
|
if (!this.options.captureMetaData) startIndex = undefined;
|
|
456
|
-
|
|
592
|
+
|
|
593
|
+
// Pass jPath string or matcher based on options.jPath setting
|
|
594
|
+
const jPathOrMatcher = this.options.jPath ? matcher.toString() : matcher;
|
|
595
|
+
const result = this.options.updateTag(childNode.tagname, jPathOrMatcher, childNode[":@"])
|
|
457
596
|
if (result === false) {
|
|
458
597
|
//do nothing
|
|
459
598
|
} else if (typeof result === "string") {
|
|
@@ -464,27 +603,34 @@ function addChild(currentNode, childNode, jPath, startIndex) {
|
|
|
464
603
|
}
|
|
465
604
|
}
|
|
466
605
|
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
606
|
+
/**
|
|
607
|
+
* @param {object} val - Entity object with regex and val properties
|
|
608
|
+
* @param {string} tagName - Tag name
|
|
609
|
+
* @param {string|Matcher} jPath - jPath string or Matcher instance based on options.jPath
|
|
610
|
+
*/
|
|
611
|
+
function replaceEntitiesValue(val, tagName, jPath) {
|
|
473
612
|
const entityConfig = this.options.processEntities;
|
|
474
613
|
|
|
475
|
-
if (!entityConfig.enabled) {
|
|
614
|
+
if (!entityConfig || !entityConfig.enabled) {
|
|
476
615
|
return val;
|
|
477
616
|
}
|
|
478
617
|
|
|
479
|
-
// Check tag
|
|
618
|
+
// Check if tag is allowed to contain entities
|
|
480
619
|
if (entityConfig.allowedTags) {
|
|
481
|
-
|
|
482
|
-
|
|
620
|
+
const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
|
|
621
|
+
const allowed = Array.isArray(entityConfig.allowedTags)
|
|
622
|
+
? entityConfig.allowedTags.includes(tagName)
|
|
623
|
+
: entityConfig.allowedTags(tagName, jPathOrMatcher);
|
|
624
|
+
|
|
625
|
+
if (!allowed) {
|
|
626
|
+
return val;
|
|
483
627
|
}
|
|
484
628
|
}
|
|
485
629
|
|
|
630
|
+
// Apply custom tag filter if provided
|
|
486
631
|
if (entityConfig.tagFilter) {
|
|
487
|
-
|
|
632
|
+
const jPathOrMatcher = this.options.jPath ? jPath.toString() : jPath;
|
|
633
|
+
if (!entityConfig.tagFilter(tagName, jPathOrMatcher)) {
|
|
488
634
|
return val; // Skip based on custom filter
|
|
489
635
|
}
|
|
490
636
|
}
|
|
@@ -546,13 +692,13 @@ const replaceEntitiesValue = function (val, tagName, jPath) {
|
|
|
546
692
|
}
|
|
547
693
|
|
|
548
694
|
|
|
549
|
-
function saveTextToParentTag(textData, parentNode,
|
|
695
|
+
function saveTextToParentTag(textData, parentNode, matcher, isLeafNode) {
|
|
550
696
|
if (textData) { //store previously collected data as textNode
|
|
551
697
|
if (isLeafNode === undefined) isLeafNode = parentNode.child.length === 0
|
|
552
698
|
|
|
553
699
|
textData = this.parseTextData(textData,
|
|
554
700
|
parentNode.tagname,
|
|
555
|
-
|
|
701
|
+
matcher,
|
|
556
702
|
false,
|
|
557
703
|
parentNode[":@"] ? Object.keys(parentNode[":@"]).length !== 0 : false,
|
|
558
704
|
isLeafNode);
|
|
@@ -566,14 +712,17 @@ function saveTextToParentTag(textData, parentNode, jPath, isLeafNode) {
|
|
|
566
712
|
|
|
567
713
|
//TODO: use jPath to simplify the logic
|
|
568
714
|
/**
|
|
569
|
-
* @param {
|
|
570
|
-
* @param {
|
|
571
|
-
* @param {string} jPath
|
|
572
|
-
* @param {string} currentTagName
|
|
715
|
+
* @param {Array<Expression>} stopNodeExpressions - Array of compiled Expression objects
|
|
716
|
+
* @param {Matcher} matcher - Current path matcher
|
|
573
717
|
*/
|
|
574
|
-
function isItStopNode(
|
|
575
|
-
if (
|
|
576
|
-
|
|
718
|
+
function isItStopNode(stopNodeExpressions, matcher) {
|
|
719
|
+
if (!stopNodeExpressions || stopNodeExpressions.length === 0) return false;
|
|
720
|
+
|
|
721
|
+
for (let i = 0; i < stopNodeExpressions.length; i++) {
|
|
722
|
+
if (matcher.matches(stopNodeExpressions[i])) {
|
|
723
|
+
return true;
|
|
724
|
+
}
|
|
725
|
+
}
|
|
577
726
|
return false;
|
|
578
727
|
}
|
|
579
728
|
|
|
@@ -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
|
/**
|