fast-xml-parser 5.5.12 → 5.7.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 +23 -0
- 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 +18 -2
- 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 +3 -2
- package/src/fxp.d.ts +16 -0
- package/src/xmlparser/DocTypeReader.js +2 -5
- package/src/xmlparser/OptionsBuilder.js +11 -7
- package/src/xmlparser/OrderedObjParser.js +31 -118
- package/src/xmlparser/XMLParser.js +1 -1
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-xml-parser",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.7.0",
|
|
4
4
|
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
|
|
5
5
|
"main": "./lib/fxp.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -87,7 +87,8 @@
|
|
|
87
87
|
}
|
|
88
88
|
],
|
|
89
89
|
"dependencies": {
|
|
90
|
-
"
|
|
90
|
+
"@nodable/entities": "^2.1.0",
|
|
91
|
+
"fast-xml-builder": "^1.1.5",
|
|
91
92
|
"path-expression-matcher": "^1.5.0",
|
|
92
93
|
"strnum": "^2.2.3"
|
|
93
94
|
}
|
package/src/fxp.d.ts
CHANGED
|
@@ -211,6 +211,14 @@ export type ProcessEntitiesOptions = {
|
|
|
211
211
|
tagFilter?: ((tagName: string, jPathOrMatcher: JPathOrMatcher) => boolean) | null;
|
|
212
212
|
};
|
|
213
213
|
|
|
214
|
+
export type EntityDecoderOptions = {
|
|
215
|
+
setExternalEntities: (entities: Record<string, string>) => void;
|
|
216
|
+
addInputEntities: (entities: Record<string, string>) => void;
|
|
217
|
+
reset: () => void;
|
|
218
|
+
decode: (text: string) => string;
|
|
219
|
+
setXmlVersion: (version: string) => void;
|
|
220
|
+
}
|
|
221
|
+
|
|
214
222
|
export type X2jOptions = {
|
|
215
223
|
/**
|
|
216
224
|
* Preserve the order of tags in resulting JS object
|
|
@@ -397,9 +405,14 @@ export type X2jOptions = {
|
|
|
397
405
|
* Whether to process HTML entities
|
|
398
406
|
*
|
|
399
407
|
* Defaults to `false`
|
|
408
|
+
* @deprecated Use `entityDecoder` instead
|
|
400
409
|
*/
|
|
401
410
|
htmlEntities?: boolean;
|
|
402
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Custom entity decoder
|
|
414
|
+
*/
|
|
415
|
+
entityDecoder?: EntityDecoderOptions;
|
|
403
416
|
/**
|
|
404
417
|
* Whether to ignore the declaration tag from output
|
|
405
418
|
*
|
|
@@ -707,6 +720,9 @@ export class XMLParser {
|
|
|
707
720
|
export class XMLValidator {
|
|
708
721
|
static validate(xmlData: string, options?: validationOptions): true | ValidationError;
|
|
709
722
|
}
|
|
723
|
+
/**
|
|
724
|
+
* @deprecated Use npm package 'fast-xml-builder' instead
|
|
725
|
+
*/
|
|
710
726
|
export class XMLBuilder {
|
|
711
727
|
constructor(options?: XmlBuilderOptions);
|
|
712
728
|
build(jObj: any): string;
|
|
@@ -35,11 +35,8 @@ export default class DocTypeReader {
|
|
|
35
35
|
);
|
|
36
36
|
}
|
|
37
37
|
//const escaped = entityName.replace(/[.\-+*:]/g, '\\.');
|
|
38
|
-
const escaped = entityName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
39
|
-
entities[entityName] =
|
|
40
|
-
regx: RegExp(`&${escaped};`, "g"),
|
|
41
|
-
val: val
|
|
42
|
-
};
|
|
38
|
+
//const escaped = entityName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
39
|
+
entities[entityName] = val;
|
|
43
40
|
entityCount++;
|
|
44
41
|
}
|
|
45
42
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { DANGEROUS_PROPERTY_NAMES, criticalProperties } from "../util.js";
|
|
2
|
+
import { COMMON_HTML, CURRENCY } from '@nodable/entities';
|
|
2
3
|
|
|
3
4
|
const defaultOnDangerousProperty = (name) => {
|
|
4
5
|
if (DANGEROUS_PROPERTY_NAMES.includes(name)) {
|
|
@@ -39,6 +40,7 @@ export const defaultOptions = {
|
|
|
39
40
|
unpairedTags: [],
|
|
40
41
|
processEntities: true,
|
|
41
42
|
htmlEntities: false,
|
|
43
|
+
entityDecoder: null,
|
|
42
44
|
ignoreDeclaration: false,
|
|
43
45
|
ignorePiTags: false,
|
|
44
46
|
transformTagName: false,
|
|
@@ -85,18 +87,19 @@ function validatePropertyName(propertyName, optionName) {
|
|
|
85
87
|
* @param {boolean|object} value
|
|
86
88
|
* @returns {object} Always returns normalized object
|
|
87
89
|
*/
|
|
88
|
-
function normalizeProcessEntities(value) {
|
|
90
|
+
function normalizeProcessEntities(value, htmlEntities) {
|
|
89
91
|
// Boolean backward compatibility
|
|
90
92
|
if (typeof value === 'boolean') {
|
|
91
93
|
return {
|
|
92
94
|
enabled: value, // true or false
|
|
93
95
|
maxEntitySize: 10000,
|
|
94
|
-
maxExpansionDepth:
|
|
95
|
-
maxTotalExpansions:
|
|
96
|
+
maxExpansionDepth: 10000,
|
|
97
|
+
maxTotalExpansions: Infinity,
|
|
96
98
|
maxExpandedLength: 100000,
|
|
97
|
-
maxEntityCount:
|
|
99
|
+
maxEntityCount: 1000,
|
|
98
100
|
allowedTags: null,
|
|
99
|
-
tagFilter: null
|
|
101
|
+
tagFilter: null,
|
|
102
|
+
appliesTo: "all",
|
|
100
103
|
};
|
|
101
104
|
}
|
|
102
105
|
|
|
@@ -110,7 +113,8 @@ function normalizeProcessEntities(value) {
|
|
|
110
113
|
maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 100000),
|
|
111
114
|
maxEntityCount: Math.max(1, value.maxEntityCount ?? 1000),
|
|
112
115
|
allowedTags: value.allowedTags ?? null,
|
|
113
|
-
tagFilter: value.tagFilter ?? null
|
|
116
|
+
tagFilter: value.tagFilter ?? null,
|
|
117
|
+
appliesTo: value.appliesTo ?? "all",
|
|
114
118
|
};
|
|
115
119
|
}
|
|
116
120
|
|
|
@@ -141,7 +145,7 @@ export const buildOptions = function (options) {
|
|
|
141
145
|
}
|
|
142
146
|
|
|
143
147
|
// Always normalize processEntities for backward compatibility and validation
|
|
144
|
-
built.processEntities = normalizeProcessEntities(built.processEntities);
|
|
148
|
+
built.processEntities = normalizeProcessEntities(built.processEntities, built.htmlEntities);
|
|
145
149
|
built.unpairedTagsSet = new Set(built.unpairedTags);
|
|
146
150
|
// Convert old-style stopNodes for backward compatibility
|
|
147
151
|
if (built.stopNodes && Array.isArray(built.stopNodes)) {
|
|
@@ -8,6 +8,7 @@ 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 } from '@nodable/entities';
|
|
11
12
|
|
|
12
13
|
// const regx =
|
|
13
14
|
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
|
|
@@ -72,32 +73,6 @@ export default class OrderedObjParser {
|
|
|
72
73
|
this.options = options;
|
|
73
74
|
this.currentNode = null;
|
|
74
75
|
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
76
|
this.parseXml = parseXml;
|
|
102
77
|
this.parseTextData = parseTextData;
|
|
103
78
|
this.resolveNameSpace = resolveNameSpace;
|
|
@@ -110,6 +85,23 @@ export default class OrderedObjParser {
|
|
|
110
85
|
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
|
|
111
86
|
this.entityExpansionCount = 0;
|
|
112
87
|
this.currentExpandedLength = 0;
|
|
88
|
+
let namedEntities = { ...XML };
|
|
89
|
+
if (this.options.entityDecoder) {
|
|
90
|
+
this.entityDecoder = this.options.entityDecoder
|
|
91
|
+
} else {
|
|
92
|
+
if (typeof this.options.htmlEntities === "object") namedEntities = this.options.htmlEntities;
|
|
93
|
+
else if (this.options.htmlEntities === true) namedEntities = { ...COMMON_HTML, ...CURRENCY };
|
|
94
|
+
this.entityDecoder = new EntityDecoder({
|
|
95
|
+
namedEntities: namedEntities,
|
|
96
|
+
numericAllowed: this.options.htmlEntities,
|
|
97
|
+
limit: {
|
|
98
|
+
maxTotalExpansions: this.options.processEntities.maxTotalExpansions,
|
|
99
|
+
maxExpandedLength: this.options.processEntities.maxExpandedLength,
|
|
100
|
+
applyLimitsTo: this.options.processEntities.appliesTo,
|
|
101
|
+
}
|
|
102
|
+
//postCheck: resolved => resolved
|
|
103
|
+
});
|
|
104
|
+
}
|
|
113
105
|
|
|
114
106
|
// Initialize path matcher for path-expression-matcher
|
|
115
107
|
this.matcher = new Matcher();
|
|
@@ -141,17 +133,6 @@ export default class OrderedObjParser {
|
|
|
141
133
|
|
|
142
134
|
}
|
|
143
135
|
|
|
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
136
|
|
|
156
137
|
/**
|
|
157
138
|
* @param {string} val
|
|
@@ -212,9 +193,9 @@ function resolveNameSpace(tagname) {
|
|
|
212
193
|
//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
|
|
213
194
|
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
|
|
214
195
|
|
|
215
|
-
function buildAttributesMap(attrStr, jPath, tagName) {
|
|
196
|
+
function buildAttributesMap(attrStr, jPath, tagName, force = false) {
|
|
216
197
|
const options = this.options;
|
|
217
|
-
if (options.ignoreAttributes !== true && typeof attrStr === 'string') {
|
|
198
|
+
if (force === true || (options.ignoreAttributes !== true && typeof attrStr === 'string')) {
|
|
218
199
|
// attrStr = attrStr.replace(/\r?\n/g, ' ');
|
|
219
200
|
//attrStr = attrStr || attrStr.trim();
|
|
220
201
|
|
|
@@ -304,13 +285,11 @@ const parseXml = function (xmlData) {
|
|
|
304
285
|
|
|
305
286
|
// Reset matcher for new document
|
|
306
287
|
this.matcher.reset();
|
|
288
|
+
this.entityDecoder.reset();
|
|
307
289
|
|
|
308
290
|
// Reset entity expansion counters for this document
|
|
309
291
|
this.entityExpansionCount = 0;
|
|
310
292
|
this.currentExpandedLength = 0;
|
|
311
|
-
this.docTypeEntitiesKeys = [];
|
|
312
|
-
this.lastEntitiesKeys = Object.keys(this.lastEntities);
|
|
313
|
-
this.htmlEntitiesKeys = this.options.htmlEntities ? Object.keys(this.htmlEntities) : [];
|
|
314
293
|
const options = this.options;
|
|
315
294
|
const docTypeReader = new DocTypeReader(options.processEntities);
|
|
316
295
|
const xmlLen = xmlData.length;
|
|
@@ -360,6 +339,11 @@ const parseXml = function (xmlData) {
|
|
|
360
339
|
if (!tagData) throw new Error("Pi Tag is not closed.");
|
|
361
340
|
|
|
362
341
|
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
342
|
+
const attsMap = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName, true);
|
|
343
|
+
if (attsMap) {
|
|
344
|
+
const ver = attsMap[this.options.attributeNamePrefix + "version"];
|
|
345
|
+
this.entityDecoder.setXmlVersion(Number(ver) || 1.0);
|
|
346
|
+
}
|
|
363
347
|
if ((options.ignoreDeclaration && tagData.tagName === "?xml") || options.ignorePiTags) {
|
|
364
348
|
//do nothing
|
|
365
349
|
} else {
|
|
@@ -367,8 +351,8 @@ const parseXml = function (xmlData) {
|
|
|
367
351
|
const childNode = new xmlNode(tagData.tagName);
|
|
368
352
|
childNode.add(options.textNodeName, "");
|
|
369
353
|
|
|
370
|
-
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
|
|
371
|
-
childNode[":@"] =
|
|
354
|
+
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent && options.ignoreAttributes !== true) {
|
|
355
|
+
childNode[":@"] = attsMap
|
|
372
356
|
}
|
|
373
357
|
this.addChild(currentNode, childNode, this.readonlyMatcher, i);
|
|
374
358
|
}
|
|
@@ -390,8 +374,7 @@ const parseXml = function (xmlData) {
|
|
|
390
374
|
} else if (c1 === 33
|
|
391
375
|
&& xmlData.charCodeAt(i + 2) === 68) { //'!D'
|
|
392
376
|
const result = docTypeReader.readDocType(xmlData, i);
|
|
393
|
-
this.
|
|
394
|
-
this.docTypeEntitiesKeys = Object.keys(this.docTypeEntities) || []
|
|
377
|
+
this.entityDecoder.addInputEntities(result.entities);
|
|
395
378
|
i = result.i;
|
|
396
379
|
} else if (c1 === 33
|
|
397
380
|
&& xmlData.charCodeAt(i + 2) === 91) { // '!['
|
|
@@ -490,6 +473,7 @@ const parseXml = function (xmlData) {
|
|
|
490
473
|
|
|
491
474
|
if (prefixedAttrs) {
|
|
492
475
|
// Extract raw attributes (without prefix) for our use
|
|
476
|
+
//TODO: seems a performance overhead
|
|
493
477
|
rawAttrs = extractRawAttributes(prefixedAttrs, options);
|
|
494
478
|
}
|
|
495
479
|
}
|
|
@@ -632,78 +616,7 @@ function replaceEntitiesValue(val, tagName, jPath) {
|
|
|
632
616
|
}
|
|
633
617
|
}
|
|
634
618
|
|
|
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;
|
|
619
|
+
return this.entityDecoder.decode(val);
|
|
707
620
|
}
|
|
708
621
|
|
|
709
622
|
|
|
@@ -32,7 +32,7 @@ export default class XMLParser {
|
|
|
32
32
|
}
|
|
33
33
|
}
|
|
34
34
|
const orderedObjParser = new OrderedObjParser(this.options);
|
|
35
|
-
orderedObjParser.
|
|
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);
|