@depup/fast-xml-parser 5.5.11-depup.0 → 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 +9 -3
- package/changes.json +8 -3
- 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 +18 -13
- 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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@depup/fast-xml-parser",
|
|
3
|
-
"version": "5.
|
|
3
|
+
"version": "5.10.1-depup.0",
|
|
4
4
|
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries (with updated dependencies)",
|
|
5
5
|
"main": "./lib/fxp.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -23,13 +23,11 @@
|
|
|
23
23
|
"test": "c8 --reporter=lcov --reporter=text jasmine spec/*spec.js",
|
|
24
24
|
"test-types": "tsc --noEmit spec/typings/typings-test.ts",
|
|
25
25
|
"unit": "jasmine",
|
|
26
|
-
"coverage": "nyc report --reporter html --reporter text -t .nyc_output --report-dir .nyc_output/summary",
|
|
27
26
|
"perf": "node ./benchmark/perfTest3.js",
|
|
28
27
|
"lint": "eslint src/**/*.js spec/**/*.js benchmark/**/*.js",
|
|
29
28
|
"bundle": "webpack --config webpack.cjs.config.js",
|
|
30
29
|
"prettier": "prettier --write src/**/*.js",
|
|
31
|
-
"
|
|
32
|
-
"publish-please": "publish-please"
|
|
30
|
+
"preversion": "npm test"
|
|
33
31
|
},
|
|
34
32
|
"bin": {
|
|
35
33
|
"fxparser": "src/cli/cli.js"
|
|
@@ -75,14 +73,13 @@
|
|
|
75
73
|
"@babel/plugin-transform-runtime": "^7.13.10",
|
|
76
74
|
"@babel/preset-env": "^7.13.10",
|
|
77
75
|
"@babel/register": "^7.13.8",
|
|
76
|
+
"@byspec/xml": "^0.1.0",
|
|
78
77
|
"@types/node": "20",
|
|
79
78
|
"babel-loader": "^8.2.2",
|
|
80
79
|
"c8": "^10.1.3",
|
|
81
80
|
"eslint": "^8.3.0",
|
|
82
|
-
"he": "^1.2.0",
|
|
83
81
|
"jasmine": "^5.6.0",
|
|
84
82
|
"prettier": "^3.5.1",
|
|
85
|
-
"publish-please": "^5.5.2",
|
|
86
83
|
"typescript": "5",
|
|
87
84
|
"webpack": "^5.64.4",
|
|
88
85
|
"webpack-cli": "^4.9.1"
|
|
@@ -94,16 +91,24 @@
|
|
|
94
91
|
}
|
|
95
92
|
],
|
|
96
93
|
"dependencies": {
|
|
97
|
-
"
|
|
98
|
-
"
|
|
99
|
-
"
|
|
94
|
+
"@nodable/entities": "^3.0.0",
|
|
95
|
+
"fast-xml-builder": "^1.3.0",
|
|
96
|
+
"is-unsafe": "^2.0.0",
|
|
97
|
+
"path-expression-matcher": "^1.6.2",
|
|
98
|
+
"strnum": "^2.4.1",
|
|
99
|
+
"xml-naming": "^0.3.0"
|
|
100
100
|
},
|
|
101
101
|
"depup": {
|
|
102
|
-
"changes": {
|
|
103
|
-
|
|
102
|
+
"changes": {
|
|
103
|
+
"fast-xml-builder": {
|
|
104
|
+
"from": "^1.2.0",
|
|
105
|
+
"to": "^1.3.0"
|
|
106
|
+
}
|
|
107
|
+
},
|
|
108
|
+
"depsUpdated": 1,
|
|
104
109
|
"originalPackage": "fast-xml-parser",
|
|
105
|
-
"originalVersion": "5.
|
|
106
|
-
"processedAt": "2026-
|
|
110
|
+
"originalVersion": "5.10.1",
|
|
111
|
+
"processedAt": "2026-07-21T16:14:33.562Z",
|
|
107
112
|
"smokeTest": "passed"
|
|
108
113
|
}
|
|
109
114
|
}
|
package/src/fxp.d.ts
CHANGED
|
@@ -66,20 +66,23 @@ export class Expression {
|
|
|
66
66
|
}
|
|
67
67
|
|
|
68
68
|
// ---------------------------------------------------------------------------
|
|
69
|
-
//
|
|
69
|
+
// MatcherView
|
|
70
70
|
// ---------------------------------------------------------------------------
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
* A live read-only view of a Matcher instance
|
|
73
|
+
* A lightweight, live read-only view of a Matcher instance.
|
|
74
74
|
*
|
|
75
|
-
*
|
|
76
|
-
*
|
|
77
|
-
*
|
|
78
|
-
*
|
|
75
|
+
* Returned by `Matcher.readOnly()`. The same instance is reused across every
|
|
76
|
+
* callback invocation — no allocation overhead per call. Reads directly from
|
|
77
|
+
* the parent Matcher's internal state so it always reflects the current parser
|
|
78
|
+
* position with no copying or freezing.
|
|
79
|
+
*
|
|
80
|
+
* Mutation methods (`push`, `pop`, `reset`, `updateCurrent`, `restore`) are
|
|
81
|
+
* simply absent — misuse is caught at compile time by TypeScript.
|
|
79
82
|
*
|
|
80
83
|
* This is the type received by all FXP user callbacks when `jPath: false`.
|
|
81
84
|
*/
|
|
82
|
-
export
|
|
85
|
+
export class MatcherView {
|
|
83
86
|
readonly separator: string;
|
|
84
87
|
|
|
85
88
|
/** Check if current path matches an Expression. */
|
|
@@ -111,14 +114,14 @@ export interface ReadonlyMatcher {
|
|
|
111
114
|
|
|
112
115
|
/** Current path as an array of tag names. */
|
|
113
116
|
toArray(): string[];
|
|
114
|
-
|
|
115
|
-
/**
|
|
116
|
-
* Create a snapshot of the current state.
|
|
117
|
-
* The snapshot can be passed to the real Matcher.restore if needed.
|
|
118
|
-
*/
|
|
119
|
-
snapshot(): MatcherSnapshot;
|
|
120
117
|
}
|
|
121
118
|
|
|
119
|
+
/**
|
|
120
|
+
* @deprecated Use {@link MatcherView} instead.
|
|
121
|
+
* Alias kept for backward compatibility.
|
|
122
|
+
*/
|
|
123
|
+
export type ReadonlyMatcher = MatcherView;
|
|
124
|
+
|
|
122
125
|
/** Internal node structure — exposed via snapshot only. */
|
|
123
126
|
export interface PathNode {
|
|
124
127
|
tag: string;
|
|
@@ -141,8 +144,8 @@ export interface MatcherSnapshot {
|
|
|
141
144
|
**********************************************************************/
|
|
142
145
|
|
|
143
146
|
// jPath: true → string
|
|
144
|
-
// jPath: false →
|
|
145
|
-
type JPathOrMatcher = string |
|
|
147
|
+
// jPath: false → MatcherView
|
|
148
|
+
type JPathOrMatcher = string | MatcherView;
|
|
146
149
|
type JPathOrExpression = string | Expression;
|
|
147
150
|
|
|
148
151
|
export type ProcessEntitiesOptions = {
|
|
@@ -208,6 +211,14 @@ export type ProcessEntitiesOptions = {
|
|
|
208
211
|
tagFilter?: ((tagName: string, jPathOrMatcher: JPathOrMatcher) => boolean) | null;
|
|
209
212
|
};
|
|
210
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
|
+
|
|
211
222
|
export type X2jOptions = {
|
|
212
223
|
/**
|
|
213
224
|
* Preserve the order of tags in resulting JS object
|
|
@@ -394,9 +405,14 @@ export type X2jOptions = {
|
|
|
394
405
|
* Whether to process HTML entities
|
|
395
406
|
*
|
|
396
407
|
* Defaults to `false`
|
|
408
|
+
* @deprecated Use `entityDecoder` instead
|
|
397
409
|
*/
|
|
398
410
|
htmlEntities?: boolean;
|
|
399
411
|
|
|
412
|
+
/**
|
|
413
|
+
* Custom entity decoder
|
|
414
|
+
*/
|
|
415
|
+
entityDecoder?: EntityDecoderOptions;
|
|
400
416
|
/**
|
|
401
417
|
* Whether to ignore the declaration tag from output
|
|
402
418
|
*
|
|
@@ -487,7 +503,9 @@ export type strnumOptions = {
|
|
|
487
503
|
hex: boolean;
|
|
488
504
|
leadingZeros: boolean,
|
|
489
505
|
skipLike?: RegExp,
|
|
490
|
-
eNotation?: boolean
|
|
506
|
+
eNotation?: boolean,
|
|
507
|
+
infinity?: string,
|
|
508
|
+
unicode?: boolean
|
|
491
509
|
}
|
|
492
510
|
|
|
493
511
|
export type validationOptions = {
|
|
@@ -680,11 +698,19 @@ export type ValidationError = {
|
|
|
680
698
|
|
|
681
699
|
export class XMLParser {
|
|
682
700
|
constructor(options?: X2jOptions);
|
|
683
|
-
|
|
701
|
+
|
|
702
|
+
parse(xmlData: string | Uint8Array): any;
|
|
703
|
+
/**
|
|
704
|
+
* @deprecated The `validationOptions` parameter is deprecated.
|
|
705
|
+
* Use the `fast-xml-validator` package instead for XML validation.
|
|
706
|
+
* @see https://www.npmjs.com/package/fast-xml-validator
|
|
707
|
+
*/
|
|
708
|
+
parse(xmlData: string | Uint8Array, validationOptions: validationOptions | boolean): any;
|
|
684
709
|
/**
|
|
685
710
|
* Add Entity which is not by default supported by this library
|
|
686
711
|
* @param entityIdentifier {string} Eg: 'ent' for &ent;
|
|
687
712
|
* @param entityValue {string} Eg: '\r'
|
|
713
|
+
* @deprecated Use `entityDecoder` instead
|
|
688
714
|
*/
|
|
689
715
|
addEntity(entityIdentifier: string, entityValue: string): void;
|
|
690
716
|
|
|
@@ -701,9 +727,16 @@ export class XMLParser {
|
|
|
701
727
|
static getMetaDataSymbol(): Symbol;
|
|
702
728
|
}
|
|
703
729
|
|
|
730
|
+
/**
|
|
731
|
+
* @deprecated Use fast-xml-validator instead
|
|
732
|
+
* @see https://www.npmjs.com/package/fast-xml-validator
|
|
733
|
+
*/
|
|
704
734
|
export class XMLValidator {
|
|
705
735
|
static validate(xmlData: string, options?: validationOptions): true | ValidationError;
|
|
706
736
|
}
|
|
737
|
+
/**
|
|
738
|
+
* @deprecated Use npm package 'fast-xml-builder' instead
|
|
739
|
+
*/
|
|
707
740
|
export class XMLBuilder {
|
|
708
741
|
constructor(options?: XmlBuilderOptions);
|
|
709
742
|
build(jObj: any): string;
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
import { isName } from '
|
|
1
|
+
import { qName as isName } from 'xml-naming';
|
|
2
2
|
|
|
3
3
|
export default class DocTypeReader {
|
|
4
|
-
constructor(options) {
|
|
4
|
+
constructor(options, xmlVersion) {
|
|
5
5
|
this.suppressValidationErr = !options;
|
|
6
6
|
this.options = options;
|
|
7
|
+
this.xmlVersion = xmlVersion || 1.0;
|
|
7
8
|
}
|
|
8
9
|
|
|
10
|
+
setXmlVersion(xmlVersion = 1.0) {
|
|
11
|
+
this.xmlVersion = xmlVersion;
|
|
12
|
+
}
|
|
9
13
|
readDocType(xmlData, i) {
|
|
10
14
|
const entities = Object.create(null);
|
|
11
15
|
let entityCount = 0;
|
|
@@ -35,11 +39,8 @@ export default class DocTypeReader {
|
|
|
35
39
|
);
|
|
36
40
|
}
|
|
37
41
|
//const escaped = entityName.replace(/[.\-+*:]/g, '\\.');
|
|
38
|
-
const escaped = entityName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
39
|
-
entities[entityName] =
|
|
40
|
-
regx: RegExp(`&${escaped};`, "g"),
|
|
41
|
-
val: val
|
|
42
|
-
};
|
|
42
|
+
//const escaped = entityName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
43
|
+
entities[entityName] = val;
|
|
43
44
|
entityCount++;
|
|
44
45
|
}
|
|
45
46
|
}
|
|
@@ -106,7 +107,7 @@ export default class DocTypeReader {
|
|
|
106
107
|
}
|
|
107
108
|
let entityName = xmlData.substring(startIndex, i);
|
|
108
109
|
|
|
109
|
-
validateEntityName(entityName);
|
|
110
|
+
validateEntityName(entityName, { xmlVersion: this.xmlVersion });
|
|
110
111
|
|
|
111
112
|
// Skip whitespace after entity name
|
|
112
113
|
i = skipWhitespace(xmlData, i);
|
|
@@ -149,7 +150,7 @@ export default class DocTypeReader {
|
|
|
149
150
|
}
|
|
150
151
|
let notationName = xmlData.substring(startIndex, i);
|
|
151
152
|
|
|
152
|
-
!this.suppressValidationErr && validateEntityName(notationName);
|
|
153
|
+
!this.suppressValidationErr && validateEntityName(notationName, { xmlVersion: this.xmlVersion });
|
|
153
154
|
|
|
154
155
|
// Skip whitespace after notation name
|
|
155
156
|
i = skipWhitespace(xmlData, i);
|
|
@@ -229,7 +230,7 @@ export default class DocTypeReader {
|
|
|
229
230
|
let elementName = xmlData.substring(startIndex, i);
|
|
230
231
|
|
|
231
232
|
// Validate element name
|
|
232
|
-
if (!this.suppressValidationErr && !isName(elementName)) {
|
|
233
|
+
if (!this.suppressValidationErr && !isName(elementName, { xmlVersion: this.xmlVersion })) {
|
|
233
234
|
throw new Error(`Invalid element name: "${elementName}"`);
|
|
234
235
|
}
|
|
235
236
|
|
|
@@ -276,7 +277,7 @@ export default class DocTypeReader {
|
|
|
276
277
|
let elementName = xmlData.substring(startIndex, i);
|
|
277
278
|
|
|
278
279
|
// Validate element name
|
|
279
|
-
validateEntityName(elementName)
|
|
280
|
+
validateEntityName(elementName, { xmlVersion: this.xmlVersion })
|
|
280
281
|
|
|
281
282
|
// Skip whitespace after element name
|
|
282
283
|
i = skipWhitespace(xmlData, i);
|
|
@@ -289,7 +290,7 @@ export default class DocTypeReader {
|
|
|
289
290
|
let attributeName = xmlData.substring(startIndex, i);
|
|
290
291
|
|
|
291
292
|
// Validate attribute name
|
|
292
|
-
if (!validateEntityName(attributeName)) {
|
|
293
|
+
if (!validateEntityName(attributeName, { xmlVersion: this.xmlVersion })) {
|
|
293
294
|
throw new Error(`Invalid attribute name: "${attributeName}"`);
|
|
294
295
|
}
|
|
295
296
|
|
|
@@ -324,7 +325,7 @@ export default class DocTypeReader {
|
|
|
324
325
|
|
|
325
326
|
// Validate notation name
|
|
326
327
|
notation = notation.trim();
|
|
327
|
-
if (!validateEntityName(notation)) {
|
|
328
|
+
if (!validateEntityName(notation, { xmlVersion: this.xmlVersion })) {
|
|
328
329
|
throw new Error(`Invalid notation name: "${notation}"`);
|
|
329
330
|
}
|
|
330
331
|
|
|
@@ -402,8 +403,8 @@ function hasSeq(data, seq, i) {
|
|
|
402
403
|
return true;
|
|
403
404
|
}
|
|
404
405
|
|
|
405
|
-
function validateEntityName(name) {
|
|
406
|
-
if (isName(name))
|
|
406
|
+
function validateEntityName(name, xmlVersion) {
|
|
407
|
+
if (isName(name, { xmlVersion: xmlVersion }))
|
|
407
408
|
return name;
|
|
408
409
|
else
|
|
409
410
|
throw new Error(`Invalid entity name ${name}`);
|
|
@@ -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)) {
|
|
@@ -24,7 +25,8 @@ export const defaultOptions = {
|
|
|
24
25
|
numberParseOptions: {
|
|
25
26
|
hex: true,
|
|
26
27
|
leadingZeros: true,
|
|
27
|
-
eNotation: true
|
|
28
|
+
eNotation: true,
|
|
29
|
+
unicode: false
|
|
28
30
|
},
|
|
29
31
|
tagValueProcessor: function (tagName, val) {
|
|
30
32
|
return val;
|
|
@@ -39,6 +41,7 @@ export const defaultOptions = {
|
|
|
39
41
|
unpairedTags: [],
|
|
40
42
|
processEntities: true,
|
|
41
43
|
htmlEntities: false,
|
|
44
|
+
entityDecoder: null,
|
|
42
45
|
ignoreDeclaration: false,
|
|
43
46
|
ignorePiTags: false,
|
|
44
47
|
transformTagName: false,
|
|
@@ -85,18 +88,19 @@ function validatePropertyName(propertyName, optionName) {
|
|
|
85
88
|
* @param {boolean|object} value
|
|
86
89
|
* @returns {object} Always returns normalized object
|
|
87
90
|
*/
|
|
88
|
-
function normalizeProcessEntities(value) {
|
|
91
|
+
function normalizeProcessEntities(value, htmlEntities) {
|
|
89
92
|
// Boolean backward compatibility
|
|
90
93
|
if (typeof value === 'boolean') {
|
|
91
94
|
return {
|
|
92
95
|
enabled: value, // true or false
|
|
93
96
|
maxEntitySize: 10000,
|
|
94
|
-
maxExpansionDepth:
|
|
95
|
-
maxTotalExpansions:
|
|
97
|
+
maxExpansionDepth: 10000,
|
|
98
|
+
maxTotalExpansions: Infinity,
|
|
96
99
|
maxExpandedLength: 100000,
|
|
97
|
-
maxEntityCount:
|
|
100
|
+
maxEntityCount: 1000,
|
|
98
101
|
allowedTags: null,
|
|
99
|
-
tagFilter: null
|
|
102
|
+
tagFilter: null,
|
|
103
|
+
appliesTo: "all",
|
|
100
104
|
};
|
|
101
105
|
}
|
|
102
106
|
|
|
@@ -110,7 +114,8 @@ function normalizeProcessEntities(value) {
|
|
|
110
114
|
maxExpandedLength: Math.max(1, value.maxExpandedLength ?? 100000),
|
|
111
115
|
maxEntityCount: Math.max(1, value.maxEntityCount ?? 1000),
|
|
112
116
|
allowedTags: value.allowedTags ?? null,
|
|
113
|
-
tagFilter: value.tagFilter ?? null
|
|
117
|
+
tagFilter: value.tagFilter ?? null,
|
|
118
|
+
appliesTo: value.appliesTo ?? "all",
|
|
114
119
|
};
|
|
115
120
|
}
|
|
116
121
|
|
|
@@ -141,7 +146,7 @@ export const buildOptions = function (options) {
|
|
|
141
146
|
}
|
|
142
147
|
|
|
143
148
|
// Always normalize processEntities for backward compatibility and validation
|
|
144
|
-
built.processEntities = normalizeProcessEntities(built.processEntities);
|
|
149
|
+
built.processEntities = normalizeProcessEntities(built.processEntities, built.htmlEntities);
|
|
145
150
|
built.unpairedTagsSet = new Set(built.unpairedTags);
|
|
146
151
|
// Convert old-style stopNodes for backward compatibility
|
|
147
152
|
if (built.stopNodes && Array.isArray(built.stopNodes)) {
|
|
@@ -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;
|