fast-xml-parser 5.3.5 → 5.3.7
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 +12 -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 +75 -12
- 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/lib/fxvalidator.min.js +1 -1
- package/lib/fxvalidator.min.js.map +1 -1
- package/package.json +4 -4
- package/src/fxp.d.ts +74 -12
- package/src/util.js +1 -26
- package/src/xmlparser/DocTypeReader.js +63 -53
- package/src/xmlparser/OptionsBuilder.js +84 -44
- package/src/xmlparser/OrderedObjParser.js +254 -187
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
///@ts-check
|
|
3
3
|
|
|
4
|
-
import {getAllMatches, isExist} from '../util.js';
|
|
4
|
+
import { getAllMatches, isExist } from '../util.js';
|
|
5
5
|
import xmlNode from './xmlNode.js';
|
|
6
6
|
import DocTypeReader from './DocTypeReader.js';
|
|
7
7
|
import toNumber from "strnum";
|
|
@@ -14,19 +14,19 @@ import getIgnoreAttributesFn from "../ignoreAttributes.js";
|
|
|
14
14
|
//const tagsRegx = new RegExp("<(\\/?[\\w:\\-\._]+)([^>]*)>(\\s*"+cdataRegx+")*([^<]+)?","g");
|
|
15
15
|
//const tagsRegx = new RegExp("<(\\/?)((\\w*:)?([\\w:\\-\._]+))([^>]*)>([^<]*)("+cdataRegx+"([^<]*))*([^<]+)?","g");
|
|
16
16
|
|
|
17
|
-
export default class OrderedObjParser{
|
|
18
|
-
constructor(options){
|
|
17
|
+
export default class OrderedObjParser {
|
|
18
|
+
constructor(options) {
|
|
19
19
|
this.options = options;
|
|
20
20
|
this.currentNode = null;
|
|
21
21
|
this.tagsNodeStack = [];
|
|
22
22
|
this.docTypeEntities = {};
|
|
23
23
|
this.lastEntities = {
|
|
24
|
-
"apos"
|
|
25
|
-
"gt"
|
|
26
|
-
"lt"
|
|
27
|
-
"quot"
|
|
24
|
+
"apos": { regex: /&(apos|#39|#x27);/g, val: "'" },
|
|
25
|
+
"gt": { regex: /&(gt|#62|#x3E);/g, val: ">" },
|
|
26
|
+
"lt": { regex: /&(lt|#60|#x3C);/g, val: "<" },
|
|
27
|
+
"quot": { regex: /&(quot|#34|#x22);/g, val: "\"" },
|
|
28
28
|
};
|
|
29
|
-
this.ampEntity = { regex: /&(amp|#38|#x26);/g, val
|
|
29
|
+
this.ampEntity = { regex: /&(amp|#38|#x26);/g, val: "&" };
|
|
30
30
|
this.htmlEntities = {
|
|
31
31
|
"space": { regex: /&(nbsp|#160);/g, val: " " },
|
|
32
32
|
// "lt" : { regex: /&(lt|#60);/g, val: "<" },
|
|
@@ -34,15 +34,15 @@ export default class OrderedObjParser{
|
|
|
34
34
|
// "amp" : { regex: /&(amp|#38);/g, val: "&" },
|
|
35
35
|
// "quot" : { regex: /&(quot|#34);/g, val: "\"" },
|
|
36
36
|
// "apos" : { regex: /&(apos|#39);/g, val: "'" },
|
|
37
|
-
"cent"
|
|
38
|
-
"pound"
|
|
39
|
-
"yen"
|
|
40
|
-
"euro"
|
|
41
|
-
"copyright"
|
|
42
|
-
"reg"
|
|
43
|
-
"inr"
|
|
44
|
-
"num_dec": { regex: /&#([0-9]{1,7});/g, val
|
|
45
|
-
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val
|
|
37
|
+
"cent": { regex: /&(cent|#162);/g, val: "¢" },
|
|
38
|
+
"pound": { regex: /&(pound|#163);/g, val: "£" },
|
|
39
|
+
"yen": { regex: /&(yen|#165);/g, val: "¥" },
|
|
40
|
+
"euro": { regex: /&(euro|#8364);/g, val: "€" },
|
|
41
|
+
"copyright": { regex: /&(copy|#169);/g, val: "©" },
|
|
42
|
+
"reg": { regex: /&(reg|#174);/g, val: "®" },
|
|
43
|
+
"inr": { regex: /&(inr|#8377);/g, val: "₹" },
|
|
44
|
+
"num_dec": { regex: /&#([0-9]{1,7});/g, val: (_, str) => fromCodePoint(str, 10, "&#") },
|
|
45
|
+
"num_hex": { regex: /&#x([0-9a-fA-F]{1,6});/g, val: (_, str) => fromCodePoint(str, 16, "&#x") },
|
|
46
46
|
};
|
|
47
47
|
this.addExternalEntities = addExternalEntities;
|
|
48
48
|
this.parseXml = parseXml;
|
|
@@ -55,16 +55,18 @@ export default class OrderedObjParser{
|
|
|
55
55
|
this.saveTextToParentTag = saveTextToParentTag;
|
|
56
56
|
this.addChild = addChild;
|
|
57
57
|
this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
|
|
58
|
+
this.entityExpansionCount = 0;
|
|
59
|
+
this.currentExpandedLength = 0;
|
|
58
60
|
|
|
59
|
-
if(this.options.stopNodes && this.options.stopNodes.length > 0){
|
|
61
|
+
if (this.options.stopNodes && this.options.stopNodes.length > 0) {
|
|
60
62
|
this.stopNodesExact = new Set();
|
|
61
63
|
this.stopNodesWildcard = new Set();
|
|
62
|
-
for(let i = 0; i < this.options.stopNodes.length; i++){
|
|
64
|
+
for (let i = 0; i < this.options.stopNodes.length; i++) {
|
|
63
65
|
const stopNodeExp = this.options.stopNodes[i];
|
|
64
|
-
if(typeof stopNodeExp !== 'string') continue;
|
|
65
|
-
if(stopNodeExp.startsWith("*.")){
|
|
66
|
+
if (typeof stopNodeExp !== 'string') continue;
|
|
67
|
+
if (stopNodeExp.startsWith("*.")) {
|
|
66
68
|
this.stopNodesWildcard.add(stopNodeExp.substring(2));
|
|
67
|
-
}else{
|
|
69
|
+
} else {
|
|
68
70
|
this.stopNodesExact.add(stopNodeExp);
|
|
69
71
|
}
|
|
70
72
|
}
|
|
@@ -73,14 +75,14 @@ export default class OrderedObjParser{
|
|
|
73
75
|
|
|
74
76
|
}
|
|
75
77
|
|
|
76
|
-
function addExternalEntities(externalEntities){
|
|
78
|
+
function addExternalEntities(externalEntities) {
|
|
77
79
|
const entKeys = Object.keys(externalEntities);
|
|
78
80
|
for (let i = 0; i < entKeys.length; i++) {
|
|
79
81
|
const ent = entKeys[i];
|
|
80
82
|
const escaped = ent.replace(/[.\-+*:]/g, '\\.');
|
|
81
83
|
this.lastEntities[ent] = {
|
|
82
|
-
|
|
83
|
-
|
|
84
|
+
regex: new RegExp("&" + escaped + ";", "g"),
|
|
85
|
+
val: externalEntities[ent]
|
|
84
86
|
}
|
|
85
87
|
}
|
|
86
88
|
}
|
|
@@ -99,23 +101,23 @@ function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode,
|
|
|
99
101
|
if (this.options.trimValues && !dontTrim) {
|
|
100
102
|
val = val.trim();
|
|
101
103
|
}
|
|
102
|
-
if(val.length > 0){
|
|
103
|
-
if(!escapeEntities) val = this.replaceEntitiesValue(val);
|
|
104
|
-
|
|
104
|
+
if (val.length > 0) {
|
|
105
|
+
if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath);
|
|
106
|
+
|
|
105
107
|
const newval = this.options.tagValueProcessor(tagName, val, jPath, hasAttributes, isLeafNode);
|
|
106
|
-
if(newval === null || newval === undefined){
|
|
108
|
+
if (newval === null || newval === undefined) {
|
|
107
109
|
//don't parse
|
|
108
110
|
return val;
|
|
109
|
-
}else if(typeof newval !== typeof val || newval !== val){
|
|
111
|
+
} else if (typeof newval !== typeof val || newval !== val) {
|
|
110
112
|
//overwrite
|
|
111
113
|
return newval;
|
|
112
|
-
}else if(this.options.trimValues){
|
|
114
|
+
} else if (this.options.trimValues) {
|
|
113
115
|
return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
|
|
114
|
-
}else{
|
|
116
|
+
} else {
|
|
115
117
|
const trimmedVal = val.trim();
|
|
116
|
-
if(trimmedVal === val){
|
|
118
|
+
if (trimmedVal === val) {
|
|
117
119
|
return parseValue(val, this.options.parseTagValue, this.options.numberParseOptions);
|
|
118
|
-
}else{
|
|
120
|
+
} else {
|
|
119
121
|
return val;
|
|
120
122
|
}
|
|
121
123
|
}
|
|
@@ -141,7 +143,7 @@ function resolveNameSpace(tagname) {
|
|
|
141
143
|
//const attrsRegx = new RegExp("([\\w\\-\\.\\:]+)\\s*=\\s*(['\"])((.|\n)*?)\\2","gm");
|
|
142
144
|
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
|
|
143
145
|
|
|
144
|
-
function buildAttributesMap(attrStr, jPath) {
|
|
146
|
+
function buildAttributesMap(attrStr, jPath, tagName) {
|
|
145
147
|
if (this.options.ignoreAttributes !== true && typeof attrStr === 'string') {
|
|
146
148
|
// attrStr = attrStr.replace(/\r?\n/g, ' ');
|
|
147
149
|
//attrStr = attrStr || attrStr.trim();
|
|
@@ -160,20 +162,20 @@ function buildAttributesMap(attrStr, jPath) {
|
|
|
160
162
|
if (this.options.transformAttributeName) {
|
|
161
163
|
aName = this.options.transformAttributeName(aName);
|
|
162
164
|
}
|
|
163
|
-
if(aName === "__proto__") aName
|
|
165
|
+
if (aName === "__proto__") aName = "#__proto__";
|
|
164
166
|
if (oldVal !== undefined) {
|
|
165
167
|
if (this.options.trimValues) {
|
|
166
168
|
oldVal = oldVal.trim();
|
|
167
169
|
}
|
|
168
|
-
oldVal = this.replaceEntitiesValue(oldVal);
|
|
170
|
+
oldVal = this.replaceEntitiesValue(oldVal, tagName, jPath);
|
|
169
171
|
const newVal = this.options.attributeValueProcessor(attrName, oldVal, jPath);
|
|
170
|
-
if(newVal === null || newVal === undefined){
|
|
172
|
+
if (newVal === null || newVal === undefined) {
|
|
171
173
|
//don't parse
|
|
172
174
|
attrs[aName] = oldVal;
|
|
173
|
-
}else if(typeof newVal !== typeof oldVal || newVal !== oldVal){
|
|
175
|
+
} else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
|
|
174
176
|
//overwrite
|
|
175
177
|
attrs[aName] = newVal;
|
|
176
|
-
}else{
|
|
178
|
+
} else {
|
|
177
179
|
//parse
|
|
178
180
|
attrs[aName] = parseValue(
|
|
179
181
|
oldVal,
|
|
@@ -198,47 +200,52 @@ function buildAttributesMap(attrStr, jPath) {
|
|
|
198
200
|
}
|
|
199
201
|
}
|
|
200
202
|
|
|
201
|
-
const parseXml = function(xmlData) {
|
|
203
|
+
const parseXml = function (xmlData) {
|
|
202
204
|
xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
|
|
203
205
|
const xmlObj = new xmlNode('!xml');
|
|
204
206
|
let currentNode = xmlObj;
|
|
205
207
|
let textData = "";
|
|
206
208
|
let jPath = "";
|
|
209
|
+
|
|
210
|
+
// Reset entity expansion counters for this document
|
|
211
|
+
this.entityExpansionCount = 0;
|
|
212
|
+
this.currentExpandedLength = 0;
|
|
213
|
+
|
|
207
214
|
const docTypeReader = new DocTypeReader(this.options.processEntities);
|
|
208
|
-
for(let i=0; i< xmlData.length; i++){//for each char in XML data
|
|
215
|
+
for (let i = 0; i < xmlData.length; i++) {//for each char in XML data
|
|
209
216
|
const ch = xmlData[i];
|
|
210
|
-
if(ch === '<'){
|
|
217
|
+
if (ch === '<') {
|
|
211
218
|
// const nextIndex = i+1;
|
|
212
219
|
// const _2ndChar = xmlData[nextIndex];
|
|
213
|
-
if(
|
|
220
|
+
if (xmlData[i + 1] === '/') {//Closing Tag
|
|
214
221
|
const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
|
|
215
|
-
let tagName = xmlData.substring(i+2,closeIndex).trim();
|
|
222
|
+
let tagName = xmlData.substring(i + 2, closeIndex).trim();
|
|
216
223
|
|
|
217
|
-
if(this.options.removeNSPrefix){
|
|
224
|
+
if (this.options.removeNSPrefix) {
|
|
218
225
|
const colonIndex = tagName.indexOf(":");
|
|
219
|
-
if(colonIndex !== -1){
|
|
220
|
-
tagName = tagName.substr(colonIndex+1);
|
|
226
|
+
if (colonIndex !== -1) {
|
|
227
|
+
tagName = tagName.substr(colonIndex + 1);
|
|
221
228
|
}
|
|
222
229
|
}
|
|
223
230
|
|
|
224
|
-
if(this.options.transformTagName) {
|
|
231
|
+
if (this.options.transformTagName) {
|
|
225
232
|
tagName = this.options.transformTagName(tagName);
|
|
226
233
|
}
|
|
227
234
|
|
|
228
|
-
if(currentNode){
|
|
235
|
+
if (currentNode) {
|
|
229
236
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
|
230
237
|
}
|
|
231
238
|
|
|
232
239
|
//check if last tag of nested tag was unpaired tag
|
|
233
|
-
const lastTagName = jPath.substring(jPath.lastIndexOf(".")+1);
|
|
234
|
-
if(tagName && this.options.unpairedTags.indexOf(tagName) !== -1
|
|
240
|
+
const lastTagName = jPath.substring(jPath.lastIndexOf(".") + 1);
|
|
241
|
+
if (tagName && this.options.unpairedTags.indexOf(tagName) !== -1) {
|
|
235
242
|
throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
|
|
236
243
|
}
|
|
237
244
|
let propIndex = 0
|
|
238
|
-
if(lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1
|
|
239
|
-
propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.')-1)
|
|
245
|
+
if (lastTagName && this.options.unpairedTags.indexOf(lastTagName) !== -1) {
|
|
246
|
+
propIndex = jPath.lastIndexOf('.', jPath.lastIndexOf('.') - 1)
|
|
240
247
|
this.tagsNodeStack.pop();
|
|
241
|
-
}else{
|
|
248
|
+
} else {
|
|
242
249
|
propIndex = jPath.lastIndexOf(".");
|
|
243
250
|
}
|
|
244
251
|
jPath = jPath.substring(0, propIndex);
|
|
@@ -246,61 +253,61 @@ const parseXml = function(xmlData) {
|
|
|
246
253
|
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
|
|
247
254
|
textData = "";
|
|
248
255
|
i = closeIndex;
|
|
249
|
-
} else if(
|
|
256
|
+
} else if (xmlData[i + 1] === '?') {
|
|
250
257
|
|
|
251
|
-
let tagData = readTagExp(xmlData,i, false, "?>");
|
|
252
|
-
if(!tagData) throw new Error("Pi Tag is not closed.");
|
|
258
|
+
let tagData = readTagExp(xmlData, i, false, "?>");
|
|
259
|
+
if (!tagData) throw new Error("Pi Tag is not closed.");
|
|
253
260
|
|
|
254
261
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
|
255
|
-
if
|
|
262
|
+
if ((this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags) {
|
|
256
263
|
//do nothing
|
|
257
|
-
}else{
|
|
258
|
-
|
|
264
|
+
} else {
|
|
265
|
+
|
|
259
266
|
const childNode = new xmlNode(tagData.tagName);
|
|
260
267
|
childNode.add(this.options.textNodeName, "");
|
|
261
|
-
|
|
262
|
-
if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
|
|
263
|
-
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath);
|
|
268
|
+
|
|
269
|
+
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
|
|
270
|
+
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath, tagData.tagName);
|
|
264
271
|
}
|
|
265
272
|
this.addChild(currentNode, childNode, jPath, i);
|
|
266
273
|
}
|
|
267
274
|
|
|
268
275
|
|
|
269
276
|
i = tagData.closeIndex + 1;
|
|
270
|
-
} else if(xmlData.substr(i + 1, 3) === '!--') {
|
|
271
|
-
const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.")
|
|
272
|
-
if(this.options.commentPropName){
|
|
277
|
+
} else if (xmlData.substr(i + 1, 3) === '!--') {
|
|
278
|
+
const endIndex = findClosingIndex(xmlData, "-->", i + 4, "Comment is not closed.")
|
|
279
|
+
if (this.options.commentPropName) {
|
|
273
280
|
const comment = xmlData.substring(i + 4, endIndex - 2);
|
|
274
281
|
|
|
275
282
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
|
276
283
|
|
|
277
|
-
currentNode.add(this.options.commentPropName, [
|
|
284
|
+
currentNode.add(this.options.commentPropName, [{ [this.options.textNodeName]: comment }]);
|
|
278
285
|
}
|
|
279
286
|
i = endIndex;
|
|
280
|
-
} else if(
|
|
287
|
+
} else if (xmlData.substr(i + 1, 2) === '!D') {
|
|
281
288
|
const result = docTypeReader.readDocType(xmlData, i);
|
|
282
289
|
this.docTypeEntities = result.entities;
|
|
283
290
|
i = result.i;
|
|
284
|
-
}else if(xmlData.substr(i + 1, 2) === '![') {
|
|
291
|
+
} else if (xmlData.substr(i + 1, 2) === '![') {
|
|
285
292
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
|
|
286
|
-
const tagExp = xmlData.substring(i + 9,closeIndex);
|
|
293
|
+
const tagExp = xmlData.substring(i + 9, closeIndex);
|
|
287
294
|
|
|
288
295
|
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
|
289
296
|
|
|
290
297
|
let val = this.parseTextData(tagExp, currentNode.tagname, jPath, true, false, true, true);
|
|
291
|
-
if(val == undefined) val = "";
|
|
298
|
+
if (val == undefined) val = "";
|
|
292
299
|
|
|
293
300
|
//cdata should be set even if it is 0 length string
|
|
294
|
-
if(this.options.cdataPropName){
|
|
295
|
-
currentNode.add(this.options.cdataPropName, [
|
|
296
|
-
}else{
|
|
301
|
+
if (this.options.cdataPropName) {
|
|
302
|
+
currentNode.add(this.options.cdataPropName, [{ [this.options.textNodeName]: tagExp }]);
|
|
303
|
+
} else {
|
|
297
304
|
currentNode.add(this.options.textNodeName, val);
|
|
298
305
|
}
|
|
299
|
-
|
|
306
|
+
|
|
300
307
|
i = closeIndex + 2;
|
|
301
|
-
}else {//Opening tag
|
|
302
|
-
let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
|
|
303
|
-
let tagName= result.tagName;
|
|
308
|
+
} else {//Opening tag
|
|
309
|
+
let result = readTagExp(xmlData, i, this.options.removeNSPrefix);
|
|
310
|
+
let tagName = result.tagName;
|
|
304
311
|
const rawTagName = result.rawTagName;
|
|
305
312
|
let tagExp = result.tagExp;
|
|
306
313
|
let attrExpPresent = result.attrExpPresent;
|
|
@@ -309,15 +316,15 @@ const parseXml = function(xmlData) {
|
|
|
309
316
|
if (this.options.transformTagName) {
|
|
310
317
|
//console.log(tagExp, tagName)
|
|
311
318
|
const newTagName = this.options.transformTagName(tagName);
|
|
312
|
-
if(tagExp === tagName) {
|
|
319
|
+
if (tagExp === tagName) {
|
|
313
320
|
tagExp = newTagName
|
|
314
321
|
}
|
|
315
322
|
tagName = newTagName;
|
|
316
323
|
}
|
|
317
|
-
|
|
324
|
+
|
|
318
325
|
//save text as child node
|
|
319
326
|
if (currentNode && textData) {
|
|
320
|
-
if(currentNode.tagname !== '!xml'){
|
|
327
|
+
if (currentNode.tagname !== '!xml') {
|
|
321
328
|
//when nested tag is found
|
|
322
329
|
textData = this.saveTextToParentTag(textData, currentNode, jPath, false);
|
|
323
330
|
}
|
|
@@ -325,88 +332,87 @@ const parseXml = function(xmlData) {
|
|
|
325
332
|
|
|
326
333
|
//check if last tag was unpaired tag
|
|
327
334
|
const lastTag = currentNode;
|
|
328
|
-
if(lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1
|
|
335
|
+
if (lastTag && this.options.unpairedTags.indexOf(lastTag.tagname) !== -1) {
|
|
329
336
|
currentNode = this.tagsNodeStack.pop();
|
|
330
337
|
jPath = jPath.substring(0, jPath.lastIndexOf("."));
|
|
331
338
|
}
|
|
332
|
-
if(tagName !== xmlObj.tagname){
|
|
339
|
+
if (tagName !== xmlObj.tagname) {
|
|
333
340
|
jPath += jPath ? "." + tagName : tagName;
|
|
334
341
|
}
|
|
335
342
|
const startIndex = i;
|
|
336
343
|
if (this.isItStopNode(this.stopNodesExact, this.stopNodesWildcard, jPath, tagName)) {
|
|
337
344
|
let tagContent = "";
|
|
338
345
|
//self-closing tag
|
|
339
|
-
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
|
|
340
|
-
if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
|
|
346
|
+
if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
|
|
347
|
+
if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
|
|
341
348
|
tagName = tagName.substr(0, tagName.length - 1);
|
|
342
349
|
jPath = jPath.substr(0, jPath.length - 1);
|
|
343
350
|
tagExp = tagName;
|
|
344
|
-
}else{
|
|
351
|
+
} else {
|
|
345
352
|
tagExp = tagExp.substr(0, tagExp.length - 1);
|
|
346
353
|
}
|
|
347
354
|
i = result.closeIndex;
|
|
348
355
|
}
|
|
349
356
|
//unpaired tag
|
|
350
|
-
else if(this.options.unpairedTags.indexOf(tagName) !== -1){
|
|
351
|
-
|
|
357
|
+
else if (this.options.unpairedTags.indexOf(tagName) !== -1) {
|
|
358
|
+
|
|
352
359
|
i = result.closeIndex;
|
|
353
360
|
}
|
|
354
361
|
//normal tag
|
|
355
|
-
else{
|
|
362
|
+
else {
|
|
356
363
|
//read until closing tag is found
|
|
357
364
|
const result = this.readStopNodeData(xmlData, rawTagName, closeIndex + 1);
|
|
358
|
-
if(!result) throw new Error(`Unexpected end of ${rawTagName}`);
|
|
365
|
+
if (!result) throw new Error(`Unexpected end of ${rawTagName}`);
|
|
359
366
|
i = result.i;
|
|
360
367
|
tagContent = result.tagContent;
|
|
361
368
|
}
|
|
362
369
|
|
|
363
370
|
const childNode = new xmlNode(tagName);
|
|
364
371
|
|
|
365
|
-
if(tagName !== tagExp && attrExpPresent){
|
|
366
|
-
childNode[":@"] = this.buildAttributesMap(tagExp, jPath
|
|
367
|
-
);
|
|
372
|
+
if (tagName !== tagExp && attrExpPresent) {
|
|
373
|
+
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
|
|
368
374
|
}
|
|
369
|
-
if(tagContent) {
|
|
375
|
+
if (tagContent) {
|
|
370
376
|
tagContent = this.parseTextData(tagContent, tagName, jPath, true, attrExpPresent, true, true);
|
|
371
377
|
}
|
|
372
|
-
|
|
378
|
+
|
|
373
379
|
jPath = jPath.substr(0, jPath.lastIndexOf("."));
|
|
374
380
|
childNode.add(this.options.textNodeName, tagContent);
|
|
375
|
-
|
|
381
|
+
|
|
376
382
|
this.addChild(currentNode, childNode, jPath, startIndex);
|
|
377
|
-
}else{
|
|
378
|
-
|
|
379
|
-
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
|
|
380
|
-
if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
|
|
383
|
+
} else {
|
|
384
|
+
//selfClosing tag
|
|
385
|
+
if (tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1) {
|
|
386
|
+
if (tagName[tagName.length - 1] === "/") { //remove trailing '/'
|
|
381
387
|
tagName = tagName.substr(0, tagName.length - 1);
|
|
382
388
|
jPath = jPath.substr(0, jPath.length - 1);
|
|
383
389
|
tagExp = tagName;
|
|
384
|
-
}else{
|
|
390
|
+
} else {
|
|
385
391
|
tagExp = tagExp.substr(0, tagExp.length - 1);
|
|
386
392
|
}
|
|
387
|
-
|
|
388
|
-
if(this.options.transformTagName) {
|
|
393
|
+
|
|
394
|
+
if (this.options.transformTagName) {
|
|
389
395
|
const newTagName = this.options.transformTagName(tagName);
|
|
390
|
-
if(tagExp === tagName) {
|
|
396
|
+
if (tagExp === tagName) {
|
|
391
397
|
tagExp = newTagName
|
|
392
398
|
}
|
|
393
399
|
tagName = newTagName;
|
|
394
400
|
}
|
|
395
401
|
|
|
396
402
|
const childNode = new xmlNode(tagName);
|
|
397
|
-
if(tagName !== tagExp && attrExpPresent){
|
|
398
|
-
childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
|
|
403
|
+
if (tagName !== tagExp && attrExpPresent) {
|
|
404
|
+
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
|
|
399
405
|
}
|
|
400
406
|
this.addChild(currentNode, childNode, jPath, startIndex);
|
|
401
407
|
jPath = jPath.substr(0, jPath.lastIndexOf("."));
|
|
402
408
|
}
|
|
403
|
-
|
|
404
|
-
else{
|
|
405
|
-
const childNode = new xmlNode(
|
|
409
|
+
//opening tag
|
|
410
|
+
else {
|
|
411
|
+
const childNode = new xmlNode(tagName);
|
|
406
412
|
this.tagsNodeStack.push(currentNode);
|
|
407
|
-
|
|
408
|
-
if(tagName !== tagExp && attrExpPresent){
|
|
409
|
-
childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
|
|
413
|
+
|
|
414
|
+
if (tagName !== tagExp && attrExpPresent) {
|
|
415
|
+
childNode[":@"] = this.buildAttributesMap(tagExp, jPath, tagName);
|
|
410
416
|
}
|
|
411
417
|
this.addChild(currentNode, childNode, jPath, startIndex);
|
|
412
418
|
currentNode = childNode;
|
|
@@ -415,52 +421,113 @@ const parseXml = function(xmlData) {
|
|
|
415
421
|
i = closeIndex;
|
|
416
422
|
}
|
|
417
423
|
}
|
|
418
|
-
}else{
|
|
424
|
+
} else {
|
|
419
425
|
textData += xmlData[i];
|
|
420
426
|
}
|
|
421
427
|
}
|
|
422
428
|
return xmlObj.child;
|
|
423
429
|
}
|
|
424
430
|
|
|
425
|
-
function addChild(currentNode, childNode, jPath, startIndex){
|
|
431
|
+
function addChild(currentNode, childNode, jPath, startIndex) {
|
|
426
432
|
// unset startIndex if not requested
|
|
427
433
|
if (!this.options.captureMetaData) startIndex = undefined;
|
|
428
434
|
const result = this.options.updateTag(childNode.tagname, jPath, childNode[":@"])
|
|
429
|
-
if(result === false){
|
|
435
|
+
if (result === false) {
|
|
430
436
|
//do nothing
|
|
431
|
-
} else if(typeof result === "string"){
|
|
437
|
+
} else if (typeof result === "string") {
|
|
432
438
|
childNode.tagname = result
|
|
433
439
|
currentNode.addChild(childNode, startIndex);
|
|
434
|
-
}else{
|
|
440
|
+
} else {
|
|
435
441
|
currentNode.addChild(childNode, startIndex);
|
|
436
442
|
}
|
|
437
443
|
}
|
|
438
444
|
|
|
439
|
-
const replaceEntitiesValue = function(val){
|
|
445
|
+
const replaceEntitiesValue = function (val, tagName, jPath) {
|
|
446
|
+
// Performance optimization: Early return if no entities to replace
|
|
447
|
+
if (val.indexOf('&') === -1) {
|
|
448
|
+
return val;
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
const entityConfig = this.options.processEntities;
|
|
440
452
|
|
|
441
|
-
if(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
453
|
+
if (!entityConfig.enabled) {
|
|
454
|
+
return val;
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
// Check tag-specific filtering
|
|
458
|
+
if (entityConfig.allowedTags) {
|
|
459
|
+
if (!entityConfig.allowedTags.includes(tagName)) {
|
|
460
|
+
return val; // Skip entity replacement for current tag as not set
|
|
445
461
|
}
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
if (entityConfig.tagFilter) {
|
|
465
|
+
if (!entityConfig.tagFilter(tagName, jPath)) {
|
|
466
|
+
return val; // Skip based on custom filter
|
|
449
467
|
}
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// Replace DOCTYPE entities
|
|
471
|
+
for (let entityName in this.docTypeEntities) {
|
|
472
|
+
const entity = this.docTypeEntities[entityName];
|
|
473
|
+
const matches = val.match(entity.regx);
|
|
474
|
+
|
|
475
|
+
if (matches) {
|
|
476
|
+
// Track expansions
|
|
477
|
+
this.entityExpansionCount += matches.length;
|
|
478
|
+
|
|
479
|
+
// Check expansion limit
|
|
480
|
+
if (entityConfig.maxTotalExpansions &&
|
|
481
|
+
this.entityExpansionCount > entityConfig.maxTotalExpansions) {
|
|
482
|
+
throw new Error(
|
|
483
|
+
`Entity expansion limit exceeded: ${this.entityExpansionCount} > ${entityConfig.maxTotalExpansions}`
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
|
|
487
|
+
// Store length before replacement
|
|
488
|
+
const lengthBefore = val.length;
|
|
489
|
+
val = val.replace(entity.regx, entity.val);
|
|
490
|
+
|
|
491
|
+
// Check expanded length immediately after replacement
|
|
492
|
+
if (entityConfig.maxExpandedLength) {
|
|
493
|
+
this.currentExpandedLength += (val.length - lengthBefore);
|
|
494
|
+
|
|
495
|
+
if (this.currentExpandedLength > entityConfig.maxExpandedLength) {
|
|
496
|
+
throw new Error(
|
|
497
|
+
`Total expanded content size exceeded: ${this.currentExpandedLength} > ${entityConfig.maxExpandedLength}`
|
|
498
|
+
);
|
|
499
|
+
}
|
|
454
500
|
}
|
|
455
501
|
}
|
|
456
|
-
val = val.replace( this.ampEntity.regex, this.ampEntity.val);
|
|
457
502
|
}
|
|
503
|
+
if (val.indexOf('&') === -1) return val; // Early exit
|
|
504
|
+
|
|
505
|
+
// Replace standard entities
|
|
506
|
+
for (let entityName in this.lastEntities) {
|
|
507
|
+
const entity = this.lastEntities[entityName];
|
|
508
|
+
val = val.replace(entity.regex, entity.val);
|
|
509
|
+
}
|
|
510
|
+
if (val.indexOf('&') === -1) return val; // Early exit
|
|
511
|
+
|
|
512
|
+
// Replace HTML entities if enabled
|
|
513
|
+
if (this.options.htmlEntities) {
|
|
514
|
+
for (let entityName in this.htmlEntities) {
|
|
515
|
+
const entity = this.htmlEntities[entityName];
|
|
516
|
+
val = val.replace(entity.regex, entity.val);
|
|
517
|
+
}
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
// Replace ampersand entity last
|
|
521
|
+
val = val.replace(this.ampEntity.regex, this.ampEntity.val);
|
|
522
|
+
|
|
458
523
|
return val;
|
|
459
524
|
}
|
|
525
|
+
|
|
526
|
+
|
|
460
527
|
function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
|
|
461
528
|
if (textData) { //store previously collected data as textNode
|
|
462
|
-
if(isLeafNode === undefined) isLeafNode = currentNode.child.length === 0
|
|
463
|
-
|
|
529
|
+
if (isLeafNode === undefined) isLeafNode = currentNode.child.length === 0
|
|
530
|
+
|
|
464
531
|
textData = this.parseTextData(textData,
|
|
465
532
|
currentNode.tagname,
|
|
466
533
|
jPath,
|
|
@@ -482,9 +549,9 @@ function saveTextToParentTag(textData, currentNode, jPath, isLeafNode) {
|
|
|
482
549
|
* @param {string} jPath
|
|
483
550
|
* @param {string} currentTagName
|
|
484
551
|
*/
|
|
485
|
-
function isItStopNode(stopNodesExact, stopNodesWildcard, jPath, currentTagName){
|
|
486
|
-
if(stopNodesWildcard && stopNodesWildcard.has(currentTagName)) return true;
|
|
487
|
-
if(stopNodesExact && stopNodesExact.has(jPath)) return true;
|
|
552
|
+
function isItStopNode(stopNodesExact, stopNodesWildcard, jPath, currentTagName) {
|
|
553
|
+
if (stopNodesWildcard && stopNodesWildcard.has(currentTagName)) return true;
|
|
554
|
+
if (stopNodesExact && stopNodesExact.has(jPath)) return true;
|
|
488
555
|
return false;
|
|
489
556
|
}
|
|
490
557
|
|
|
@@ -494,24 +561,24 @@ function isItStopNode(stopNodesExact, stopNodesWildcard, jPath, currentTagName){
|
|
|
494
561
|
* @param {number} i starting index
|
|
495
562
|
* @returns
|
|
496
563
|
*/
|
|
497
|
-
function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
|
|
564
|
+
function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
|
|
498
565
|
let attrBoundary;
|
|
499
566
|
let tagExp = "";
|
|
500
567
|
for (let index = i; index < xmlData.length; index++) {
|
|
501
568
|
let ch = xmlData[index];
|
|
502
569
|
if (attrBoundary) {
|
|
503
|
-
|
|
570
|
+
if (ch === attrBoundary) attrBoundary = "";//reset
|
|
504
571
|
} else if (ch === '"' || ch === "'") {
|
|
505
|
-
|
|
572
|
+
attrBoundary = ch;
|
|
506
573
|
} else if (ch === closingChar[0]) {
|
|
507
|
-
if(closingChar[1]){
|
|
508
|
-
if(xmlData[index + 1] === closingChar[1]){
|
|
574
|
+
if (closingChar[1]) {
|
|
575
|
+
if (xmlData[index + 1] === closingChar[1]) {
|
|
509
576
|
return {
|
|
510
577
|
data: tagExp,
|
|
511
578
|
index: index
|
|
512
579
|
}
|
|
513
580
|
}
|
|
514
|
-
}else{
|
|
581
|
+
} else {
|
|
515
582
|
return {
|
|
516
583
|
data: tagExp,
|
|
517
584
|
index: index
|
|
@@ -524,33 +591,33 @@ function tagExpWithClosingIndex(xmlData, i, closingChar = ">"){
|
|
|
524
591
|
}
|
|
525
592
|
}
|
|
526
593
|
|
|
527
|
-
function findClosingIndex(xmlData, str, i, errMsg){
|
|
594
|
+
function findClosingIndex(xmlData, str, i, errMsg) {
|
|
528
595
|
const closingIndex = xmlData.indexOf(str, i);
|
|
529
|
-
if(closingIndex === -1){
|
|
596
|
+
if (closingIndex === -1) {
|
|
530
597
|
throw new Error(errMsg)
|
|
531
|
-
}else{
|
|
598
|
+
} else {
|
|
532
599
|
return closingIndex + str.length - 1;
|
|
533
600
|
}
|
|
534
601
|
}
|
|
535
602
|
|
|
536
|
-
function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
|
|
537
|
-
const result = tagExpWithClosingIndex(xmlData, i+1, closingChar);
|
|
538
|
-
if(!result) return;
|
|
603
|
+
function readTagExp(xmlData, i, removeNSPrefix, closingChar = ">") {
|
|
604
|
+
const result = tagExpWithClosingIndex(xmlData, i + 1, closingChar);
|
|
605
|
+
if (!result) return;
|
|
539
606
|
let tagExp = result.data;
|
|
540
607
|
const closeIndex = result.index;
|
|
541
608
|
const separatorIndex = tagExp.search(/\s/);
|
|
542
609
|
let tagName = tagExp;
|
|
543
610
|
let attrExpPresent = true;
|
|
544
|
-
if(separatorIndex !== -1){//separate tag name and attributes expression
|
|
611
|
+
if (separatorIndex !== -1) {//separate tag name and attributes expression
|
|
545
612
|
tagName = tagExp.substring(0, separatorIndex);
|
|
546
613
|
tagExp = tagExp.substring(separatorIndex + 1).trimStart();
|
|
547
614
|
}
|
|
548
615
|
|
|
549
616
|
const rawTagName = tagName;
|
|
550
|
-
if(removeNSPrefix){
|
|
617
|
+
if (removeNSPrefix) {
|
|
551
618
|
const colonIndex = tagName.indexOf(":");
|
|
552
|
-
if(colonIndex !== -1){
|
|
553
|
-
tagName = tagName.substr(colonIndex+1);
|
|
619
|
+
if (colonIndex !== -1) {
|
|
620
|
+
tagName = tagName.substr(colonIndex + 1);
|
|
554
621
|
attrExpPresent = tagName !== result.data.substr(colonIndex + 1);
|
|
555
622
|
}
|
|
556
623
|
}
|
|
@@ -569,47 +636,47 @@ function readTagExp(xmlData,i, removeNSPrefix, closingChar = ">"){
|
|
|
569
636
|
* @param {string} tagName
|
|
570
637
|
* @param {number} i
|
|
571
638
|
*/
|
|
572
|
-
function readStopNodeData(xmlData, tagName, i){
|
|
639
|
+
function readStopNodeData(xmlData, tagName, i) {
|
|
573
640
|
const startIndex = i;
|
|
574
641
|
// Starting at 1 since we already have an open tag
|
|
575
642
|
let openTagCount = 1;
|
|
576
643
|
|
|
577
644
|
for (; i < xmlData.length; i++) {
|
|
578
|
-
if(
|
|
579
|
-
if (xmlData[i+1] === "/") {//close tag
|
|
580
|
-
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
}
|
|
645
|
+
if (xmlData[i] === "<") {
|
|
646
|
+
if (xmlData[i + 1] === "/") {//close tag
|
|
647
|
+
const closeIndex = findClosingIndex(xmlData, ">", i, `${tagName} is not closed`);
|
|
648
|
+
let closeTagName = xmlData.substring(i + 2, closeIndex).trim();
|
|
649
|
+
if (closeTagName === tagName) {
|
|
650
|
+
openTagCount--;
|
|
651
|
+
if (openTagCount === 0) {
|
|
652
|
+
return {
|
|
653
|
+
tagContent: xmlData.substring(startIndex, i),
|
|
654
|
+
i: closeIndex
|
|
589
655
|
}
|
|
590
656
|
}
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
657
|
+
}
|
|
658
|
+
i = closeIndex;
|
|
659
|
+
} else if (xmlData[i + 1] === '?') {
|
|
660
|
+
const closeIndex = findClosingIndex(xmlData, "?>", i + 1, "StopNode is not closed.")
|
|
661
|
+
i = closeIndex;
|
|
662
|
+
} else if (xmlData.substr(i + 1, 3) === '!--') {
|
|
663
|
+
const closeIndex = findClosingIndex(xmlData, "-->", i + 3, "StopNode is not closed.")
|
|
664
|
+
i = closeIndex;
|
|
665
|
+
} else if (xmlData.substr(i + 1, 2) === '![') {
|
|
666
|
+
const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
|
|
667
|
+
i = closeIndex;
|
|
668
|
+
} else {
|
|
669
|
+
const tagData = readTagExp(xmlData, i, '>')
|
|
603
670
|
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
}
|
|
609
|
-
i=tagData.closeIndex;
|
|
671
|
+
if (tagData) {
|
|
672
|
+
const openTagName = tagData && tagData.tagName;
|
|
673
|
+
if (openTagName === tagName && tagData.tagExp[tagData.tagExp.length - 1] !== "/") {
|
|
674
|
+
openTagCount++;
|
|
610
675
|
}
|
|
676
|
+
i = tagData.closeIndex;
|
|
611
677
|
}
|
|
612
678
|
}
|
|
679
|
+
}
|
|
613
680
|
}//end for loop
|
|
614
681
|
}
|
|
615
682
|
|
|
@@ -617,8 +684,8 @@ function parseValue(val, shouldParse, options) {
|
|
|
617
684
|
if (shouldParse && typeof val === 'string') {
|
|
618
685
|
//console.log(options)
|
|
619
686
|
const newval = val.trim();
|
|
620
|
-
if(newval === 'true'
|
|
621
|
-
else if(newval === 'false'
|
|
687
|
+
if (newval === 'true') return true;
|
|
688
|
+
else if (newval === 'false') return false;
|
|
622
689
|
else return toNumber(val, options);
|
|
623
690
|
} else {
|
|
624
691
|
if (isExist(val)) {
|
|
@@ -629,12 +696,12 @@ function parseValue(val, shouldParse, options) {
|
|
|
629
696
|
}
|
|
630
697
|
}
|
|
631
698
|
|
|
632
|
-
function fromCodePoint(str, base, prefix){
|
|
699
|
+
function fromCodePoint(str, base, prefix) {
|
|
633
700
|
const codePoint = Number.parseInt(str, base);
|
|
634
701
|
|
|
635
702
|
if (codePoint >= 0 && codePoint <= 0x10FFFF) {
|
|
636
|
-
|
|
703
|
+
return String.fromCodePoint(codePoint);
|
|
637
704
|
} else {
|
|
638
|
-
|
|
705
|
+
return prefix + str + ";";
|
|
639
706
|
}
|
|
640
707
|
}
|