@depup/fast-xml-parser 5.5.11-depup.1 → 5.10.1-depup.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 +79 -0
- package/README.md +3 -3
- package/changes.json +4 -4
- 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 +40 -11
- 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 +14 -14
- package/src/fxp.d.ts +50 -17
- package/src/xmlparser/DocTypeReader.js +16 -15
- package/src/xmlparser/OptionsBuilder.js +13 -8
- package/src/xmlparser/OrderedObjParser.js +59 -132
- package/src/xmlparser/XMLParser.js +2 -2
- package/src/xmlparser/node2json.js +4 -0
|
@@ -8,6 +8,9 @@ import toNumber from "strnum";
|
|
|
8
8
|
import getIgnoreAttributesFn from "../ignoreAttributes.js";
|
|
9
9
|
import { Expression, Matcher } from 'path-expression-matcher';
|
|
10
10
|
import { ExpressionSet } from 'path-expression-matcher';
|
|
11
|
+
import { EntityDecoder, XML, CURRENCY, COMMON_HTML, ENTITY_ACTION } from '@nodable/entities';
|
|
12
|
+
import { isUnsafe, HTML as HTML_CONTEXT, XML as XML_CONTEXT } from "is-unsafe"
|
|
13
|
+
|
|
11
14
|
|
|
12
15
|
// const regx =
|
|
13
16
|
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
|
|
@@ -68,36 +71,10 @@ function extractNamespace(rawTagName) {
|
|
|
68
71
|
}
|
|
69
72
|
|
|
70
73
|
export default class OrderedObjParser {
|
|
71
|
-
constructor(options) {
|
|
74
|
+
constructor(options, externalEntities) {
|
|
72
75
|
this.options = options;
|
|
73
76
|
this.currentNode = null;
|
|
74
77
|
this.tagsNodeStack = [];
|
|
75
|
-
this.docTypeEntities = {};
|
|
76
|
-
this.lastEntities = {
|
|
77
|
-
"apos": { regex: /&(apos|#39|#x27);/g, val: "'" },
|
|
78
|
-
"gt": { regex: /&(gt|#62|#x3E);/g, val: ">" },
|
|
79
|
-
"lt": { regex: /&(lt|#60|#x3C);/g, val: "<" },
|
|
80
|
-
"quot": { regex: /&(quot|#34|#x22);/g, val: "\"" },
|
|
81
|
-
};
|
|
82
|
-
this.ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" };
|
|
83
|
-
this.htmlEntities = {
|
|
84
|
-
"space": { regex: /&(nbsp|#160);/g, val: " " },
|
|
85
|
-
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
|
|
86
|
-
// "gt" : { regex: /&(gt|#62);/g, val: ">" },
|
|
87
|
-
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
|
|
88
|
-
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
|
|
89
|
-
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
|
|
90
|
-
"cent": { regex: /&(cent|#162);/g, val: "¢" },
|
|
91
|
-
"pound": { regex: /&(pound|#163);/g, val: "£" },
|
|
92
|
-
"yen": { regex: /&(yen|#165);/g, val: "¥" },
|
|
93
|
-
"euro": { regex: /&(euro|#8364);/g, val: "€" },
|
|
94
|
-
"copyright": { regex: /&(copy|#169);/g, val: "©" },
|
|
95
|
-
"reg": { regex: /&(reg|#174);/g, val: "®" },
|
|
96
|
-
"inr": { regex: /&(inr|#8377);/g, val: "₹" },
|
|
97
|
-
"num_dec": { regex: /&#([0-9]{1,7});/g, val: (_, str) => fromCodePoint(str, 10, "&#") },
|
|
98
|
-
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => fromCodePoint(str, 16, "&#x") },
|
|
99
|
-
};
|
|
100
|
-
this.addExternalEntities = addExternalEntities;
|
|
101
78
|
this.parseXml = parseXml;
|
|
102
79
|
this.parseTextData = parseTextData;
|
|
103
80
|
this.resolveNameSpace = resolveNameSpace;
|
|
@@ -110,12 +87,32 @@ export default class OrderedObjParser {
|
|
|
110
87
|
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
|
|
111
88
|
this.entityExpansionCount = 0;
|
|
112
89
|
this.currentExpandedLength = 0;
|
|
90
|
+
this.doctypefound = false;
|
|
91
|
+
let namedEntities = { ...XML };
|
|
92
|
+
if (this.options.entityDecoder) {
|
|
93
|
+
this.entityDecoder = this.options.entityDecoder
|
|
94
|
+
} else {
|
|
95
|
+
if (typeof this.options.htmlEntities === "object") namedEntities = this.options.htmlEntities;
|
|
96
|
+
else if (this.options.htmlEntities === true) namedEntities = { ...COMMON_HTML, ...CURRENCY };
|
|
97
|
+
this.entityDecoder = new EntityDecoder({
|
|
98
|
+
namedEntities: { ...namedEntities, ...externalEntities },
|
|
99
|
+
numericAllowed: this.options.htmlEntities,
|
|
100
|
+
limit: {
|
|
101
|
+
maxTotalExpansions: this.options.processEntities.maxTotalExpansions,
|
|
102
|
+
maxExpandedLength: this.options.processEntities.maxExpandedLength,
|
|
103
|
+
applyLimitsTo: this.options.processEntities.appliesTo,
|
|
104
|
+
},
|
|
105
|
+
// onExternalEntity: (name, value) => isUnsafe(value) ? 'block' : 'allow',
|
|
106
|
+
onInputEntity: (name, value) =>
|
|
107
|
+
//TODO: VALID_CONTEXTS.HTML should be set only if this.options.htmlEntities
|
|
108
|
+
isUnsafe(value, [HTML_CONTEXT, XML_CONTEXT]) ? ENTITY_ACTION.BLOCK : ENTITY_ACTION.ALLOW,
|
|
109
|
+
|
|
110
|
+
//postCheck: resolved => resolved
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
113
|
|
|
114
114
|
// Initialize path matcher for path-expression-matcher
|
|
115
115
|
this.matcher = new Matcher();
|
|
116
|
-
|
|
117
|
-
// Live read-only proxy of matcher — PEM creates and caches this internally.
|
|
118
|
-
// All user callbacks receive this instead of the mutable matcher.
|
|
119
116
|
this.readonlyMatcher = this.matcher.readOnly();
|
|
120
117
|
|
|
121
118
|
// Flag to track if current node is a stop node (optimization)
|
|
@@ -141,17 +138,6 @@ export default class OrderedObjParser {
|
|
|
141
138
|
|
|
142
139
|
}
|
|
143
140
|
|
|
144
|
-
function addExternalEntities(externalEntities) {
|
|
145
|
-
const entKeys = Object.keys(externalEntities);
|
|
146
|
-
for (let i = 0; i < entKeys.length; i++) {
|
|
147
|
-
const ent = entKeys[i];
|
|
148
|
-
const escaped = ent.replace(/[.\-+*:]/g, '\\.');
|
|
149
|
-
this.lastEntities[ent] = {
|
|
150
|
-
regex: new RegExp("&" + escaped + ";", "g"),
|
|
151
|
-
val: externalEntities[ent]
|
|
152
|
-
}
|
|
153
|
-
}
|
|
154
|
-
}
|
|
155
141
|
|
|
156
142
|
/**
|
|
157
143
|
* @param {string} val
|
|
@@ -212,9 +198,9 @@ function resolveNameSpace(tagname) {
|
|
|
212
198
|
//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
|
|
213
199
|
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
|
|
214
200
|
|
|
215
|
-
function buildAttributesMap(attrStr, jPath, tagName) {
|
|
201
|
+
function buildAttributesMap(attrStr, jPath, tagName, force = false) {
|
|
216
202
|
const options = this.options;
|
|
217
|
-
if (options.ignoreAttributes !== true && typeof attrStr === 'string') {
|
|
203
|
+
if (force === true || (options.ignoreAttributes !== true && typeof attrStr === 'string')) {
|
|
218
204
|
// attrStr = attrStr.replace(/\r?\n/g, ' ');
|
|
219
205
|
//attrStr = attrStr || attrStr.trim();
|
|
220
206
|
|
|
@@ -288,7 +274,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
288
274
|
|
|
289
275
|
if (!hasAttrs) return;
|
|
290
276
|
|
|
291
|
-
if (options.attributesGroupName) {
|
|
277
|
+
if (options.attributesGroupName && !options.preserveOrder) {
|
|
292
278
|
const attrCollection = {};
|
|
293
279
|
attrCollection[options.attributesGroupName] = attrs;
|
|
294
280
|
return attrCollection;
|
|
@@ -304,13 +290,12 @@ const parseXml = function (xmlData) {
|
|
|
304
290
|
|
|
305
291
|
// Reset matcher for new document
|
|
306
292
|
this.matcher.reset();
|
|
293
|
+
this.entityDecoder.reset();
|
|
307
294
|
|
|
308
295
|
// Reset entity expansion counters for this document
|
|
309
296
|
this.entityExpansionCount = 0;
|
|
310
297
|
this.currentExpandedLength = 0;
|
|
311
|
-
this.
|
|
312
|
-
this.lastEntitiesKeys = Object.keys(this.lastEntities);
|
|
313
|
-
this.htmlEntitiesKeys = this.options.htmlEntities ? Object.keys(this.htmlEntities) : [];
|
|
298
|
+
this.doctypefound = false;
|
|
314
299
|
const options = this.options;
|
|
315
300
|
const docTypeReader = new DocTypeReader(options.processEntities);
|
|
316
301
|
const xmlLen = xmlData.length;
|
|
@@ -360,6 +345,12 @@ const parseXml = function (xmlData) {
|
|
|
360
345
|
if (!tagData) throw new Error("Pi Tag is not closed.");
|
|
361
346
|
|
|
362
347
|
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
348
|
+
const attsMap = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName, true);
|
|
349
|
+
if (attsMap) {
|
|
350
|
+
const ver = attsMap[this.options.attributeNamePrefix + "version"];
|
|
351
|
+
this.entityDecoder.setXmlVersion(Number(ver) || 1.0);
|
|
352
|
+
docTypeReader.setXmlVersion(Number(ver) || 1.0);
|
|
353
|
+
}
|
|
363
354
|
if ((options.ignoreDeclaration && tagData.tagName === "?xml") || options.ignorePiTags) {
|
|
364
355
|
//do nothing
|
|
365
356
|
} else {
|
|
@@ -367,8 +358,8 @@ const parseXml = function (xmlData) {
|
|
|
367
358
|
const childNode = new xmlNode(tagData.tagName);
|
|
368
359
|
childNode.add(options.textNodeName, "");
|
|
369
360
|
|
|
370
|
-
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
|
|
371
|
-
childNode[":@"] =
|
|
361
|
+
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent && options.ignoreAttributes !== true) {
|
|
362
|
+
childNode[":@"] = attsMap
|
|
372
363
|
}
|
|
373
364
|
this.addChild(currentNode, childNode, this.readonlyMatcher, i);
|
|
374
365
|
}
|
|
@@ -389,9 +380,10 @@ const parseXml = function (xmlData) {
|
|
|
389
380
|
i = endIndex;
|
|
390
381
|
} else if (c1 === 33
|
|
391
382
|
&& xmlData.charCodeAt(i + 2) === 68) { //'!D'
|
|
383
|
+
if (this.doctypefound) throw new Error("Multiple DOCTYPE declarations found.");
|
|
384
|
+
this.doctypefound = true;
|
|
392
385
|
const result = docTypeReader.readDocType(xmlData, i);
|
|
393
|
-
this.
|
|
394
|
-
this.docTypeEntitiesKeys = Object.keys(this.docTypeEntities) || []
|
|
386
|
+
this.entityDecoder.addInputEntities(result.entities);
|
|
395
387
|
i = result.i;
|
|
396
388
|
} else if (c1 === 33
|
|
397
389
|
&& xmlData.charCodeAt(i + 2) === 91) { // '!['
|
|
@@ -490,6 +482,7 @@ const parseXml = function (xmlData) {
|
|
|
490
482
|
|
|
491
483
|
if (prefixedAttrs) {
|
|
492
484
|
// Extract raw attributes (without prefix) for our use
|
|
485
|
+
//TODO: seems a performance overhead
|
|
493
486
|
rawAttrs = extractRawAttributes(prefixedAttrs, options);
|
|
494
487
|
}
|
|
495
488
|
}
|
|
@@ -632,78 +625,7 @@ function replaceEntitiesValue(val, tagName, jPath) {
|
|
|
632
625
|
}
|
|
633
626
|
}
|
|
634
627
|
|
|
635
|
-
|
|
636
|
-
for (const entityName of this.docTypeEntitiesKeys) {
|
|
637
|
-
const entity = this.docTypeEntities[entityName];
|
|
638
|
-
const matches = val.match(entity.regx);
|
|
639
|
-
|
|
640
|
-
if (matches) {
|
|
641
|
-
// Track expansions
|
|
642
|
-
this.entityExpansionCount += matches.length;
|
|
643
|
-
|
|
644
|
-
// Check expansion limit
|
|
645
|
-
if (entityConfig.maxTotalExpansions &&
|
|
646
|
-
this.entityExpansionCount > entityConfig.maxTotalExpansions) {
|
|
647
|
-
throw new Error(
|
|
648
|
-
`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
|
|
649
|
-
);
|
|
650
|
-
}
|
|
651
|
-
|
|
652
|
-
// Store length before replacement
|
|
653
|
-
const lengthBefore = val.length;
|
|
654
|
-
val = val.replace(entity.regx, entity.val);
|
|
655
|
-
|
|
656
|
-
// Check expanded length immediately after replacement
|
|
657
|
-
if (entityConfig.maxExpandedLength) {
|
|
658
|
-
this.currentExpandedLength += (val.length - lengthBefore);
|
|
659
|
-
|
|
660
|
-
if (this.currentExpandedLength > entityConfig.maxExpandedLength) {
|
|
661
|
-
throw new Error(
|
|
662
|
-
`Total expanded content size exceeded: ${this.currentExpandedLength} > ${entityConfig.maxExpandedLength}`
|
|
663
|
-
);
|
|
664
|
-
}
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
if (val.indexOf('&') === -1) return val;
|
|
669
|
-
// Replace standard entities
|
|
670
|
-
for (const entityName of this.lastEntitiesKeys) {
|
|
671
|
-
const entity = this.lastEntities[entityName];
|
|
672
|
-
const matches = val.match(entity.regex);
|
|
673
|
-
if (matches) {
|
|
674
|
-
this.entityExpansionCount += matches.length;
|
|
675
|
-
if (entityConfig.maxTotalExpansions &&
|
|
676
|
-
this.entityExpansionCount > entityConfig.maxTotalExpansions) {
|
|
677
|
-
throw new Error(
|
|
678
|
-
`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
|
|
679
|
-
);
|
|
680
|
-
}
|
|
681
|
-
}
|
|
682
|
-
val = val.replace(entity.regex, entity.val);
|
|
683
|
-
}
|
|
684
|
-
if (val.indexOf('&') === -1) return val;
|
|
685
|
-
|
|
686
|
-
// Replace HTML entities if enabled
|
|
687
|
-
for (const entityName of this.htmlEntitiesKeys) {
|
|
688
|
-
const entity = this.htmlEntities[entityName];
|
|
689
|
-
const matches = val.match(entity.regex);
|
|
690
|
-
if (matches) {
|
|
691
|
-
//console.log(matches);
|
|
692
|
-
this.entityExpansionCount += matches.length;
|
|
693
|
-
if (entityConfig.maxTotalExpansions &&
|
|
694
|
-
this.entityExpansionCount > entityConfig.maxTotalExpansions) {
|
|
695
|
-
throw new Error(
|
|
696
|
-
`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
|
|
697
|
-
);
|
|
698
|
-
}
|
|
699
|
-
}
|
|
700
|
-
val = val.replace(entity.regex, entity.val);
|
|
701
|
-
}
|
|
702
|
-
|
|
703
|
-
// Replace ampersand entity last
|
|
704
|
-
val = val.replace(this.ampEntity.regex, this.ampEntity.val);
|
|
705
|
-
|
|
706
|
-
return val;
|
|
628
|
+
return this.entityDecoder.decode(val);
|
|
707
629
|
}
|
|
708
630
|
|
|
709
631
|
|
|
@@ -742,12 +664,16 @@ function isItStopNode() {
|
|
|
742
664
|
* @returns
|
|
743
665
|
*/
|
|
744
666
|
function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
|
|
667
|
+
//TODO: ignore boolean attributes in tag expression
|
|
668
|
+
//TODO: if ignore attributes, dont read full attribute expression but the end. But read for xml declaration
|
|
745
669
|
let attrBoundary = 0;
|
|
746
|
-
const chars = [];
|
|
747
670
|
const len = xmlData.length;
|
|
748
671
|
const closeCode0 = closingChar.charCodeAt(0);
|
|
749
672
|
const closeCode1 = closingChar.length > 1 ? closingChar.charCodeAt(1) : -1;
|
|
750
673
|
|
|
674
|
+
let result = '';
|
|
675
|
+
let segmentStart = i;
|
|
676
|
+
|
|
751
677
|
for (let index = i; index < len; index++) {
|
|
752
678
|
const code = xmlData.charCodeAt(index);
|
|
753
679
|
|
|
@@ -758,17 +684,18 @@ function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
|
|
|
758
684
|
} else if (code === closeCode0) {
|
|
759
685
|
if (closeCode1 !== -1) {
|
|
760
686
|
if (xmlData.charCodeAt(index + 1) === closeCode1) {
|
|
761
|
-
|
|
687
|
+
result += xmlData.substring(segmentStart, index);
|
|
688
|
+
return { data: result, index };
|
|
762
689
|
}
|
|
763
690
|
} else {
|
|
764
|
-
|
|
691
|
+
result += xmlData.substring(segmentStart, index);
|
|
692
|
+
return { data: result, index };
|
|
765
693
|
}
|
|
766
|
-
} else if (code === 9) { // \t
|
|
767
|
-
|
|
768
|
-
|
|
694
|
+
} else if (code === 9 && !attrBoundary) { // \t - only replace with space outside attribute values
|
|
695
|
+
// Flush accumulated segment, add space, start new segment
|
|
696
|
+
result += xmlData.substring(segmentStart, index) + ' ';
|
|
697
|
+
segmentStart = index + 1;
|
|
769
698
|
}
|
|
770
|
-
|
|
771
|
-
chars.push(code);
|
|
772
699
|
}
|
|
773
700
|
}
|
|
774
701
|
|
|
@@ -858,7 +785,7 @@ function readStopNodeData(xmlData, tagName, i) {
|
|
|
858
785
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
|
|
859
786
|
i = closeIndex;
|
|
860
787
|
} else {
|
|
861
|
-
const tagData = readTagExp(xmlData, i,
|
|
788
|
+
const tagData = readTagExp(xmlData, i, false)
|
|
862
789
|
|
|
863
790
|
if (tagData) {
|
|
864
791
|
const openTagName = tagData && tagData.tagName;
|
|
@@ -31,8 +31,8 @@ export default class XMLParser {
|
|
|
31
31
|
throw Error(`${result.err.msg}:${result.err.line}:${result.err.col}`)
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
|
-
const orderedObjParser = new OrderedObjParser(this.options);
|
|
35
|
-
orderedObjParser.
|
|
34
|
+
const orderedObjParser = new OrderedObjParser(this.options, this.externalEntities);
|
|
35
|
+
// orderedObjParser.entityDecoder.setExternalEntities(this.externalEntities);
|
|
36
36
|
const orderedResult = orderedObjParser.parseXml(xmlData);
|
|
37
37
|
if (this.options.preserveOrder || orderedResult === undefined) return orderedResult;
|
|
38
38
|
else return prettify(orderedResult, this.options, orderedObjParser.matcher, orderedObjParser.readonlyMatcher);
|
|
@@ -71,6 +71,10 @@ function compress(arr, options, matcher, readonlyMatcher) {
|
|
|
71
71
|
let val = compress(tagObj[property], options, matcher, readonlyMatcher);
|
|
72
72
|
const isLeaf = isLeafTag(val, options);
|
|
73
73
|
|
|
74
|
+
if (Object.keys(val).length === 0 && options.alwaysCreateTextNode) {
|
|
75
|
+
val[options.textNodeName] = "";
|
|
76
|
+
}
|
|
77
|
+
|
|
74
78
|
if (tagObj[":@"]) {
|
|
75
79
|
assignAttributes(val, tagObj[":@"], readonlyMatcher, options);
|
|
76
80
|
} else if (Object.keys(val).length === 1 && val[options.textNodeName] !== undefined && !options.alwaysCreateTextNode) {
|