fast-xml-parser 5.5.10 → 5.5.11
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 +3 -1
- package/lib/fxbuilder.min.js +1 -1
- package/lib/fxbuilder.min.js.map +1 -1
- package/lib/fxp.cjs +1 -1
- 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 -3
- package/src/xmlparser/OptionsBuilder.js +1 -1
- package/src/xmlparser/OrderedObjParser.js +130 -111
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "fast-xml-parser",
|
|
3
|
-
"version": "5.5.
|
|
3
|
+
"version": "5.5.11",
|
|
4
4
|
"description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
|
|
5
5
|
"main": "./lib/fxp.cjs",
|
|
6
6
|
"type": "module",
|
|
@@ -88,7 +88,7 @@
|
|
|
88
88
|
],
|
|
89
89
|
"dependencies": {
|
|
90
90
|
"fast-xml-builder": "^1.1.4",
|
|
91
|
-
"path-expression-matcher": "^1.
|
|
92
|
-
"strnum": "^2.2.
|
|
91
|
+
"path-expression-matcher": "^1.4.0",
|
|
92
|
+
"strnum": "^2.2.3"
|
|
93
93
|
}
|
|
94
94
|
}
|
|
@@ -142,7 +142,7 @@ export const buildOptions = function (options) {
|
|
|
142
142
|
|
|
143
143
|
// Always normalize processEntities for backward compatibility and validation
|
|
144
144
|
built.processEntities = normalizeProcessEntities(built.processEntities);
|
|
145
|
-
|
|
145
|
+
built.unpairedTagsSet = new Set(built.unpairedTags);
|
|
146
146
|
// Convert old-style stopNodes for backward compatibility
|
|
147
147
|
if (built.stopNodes && Array.isArray(built.stopNodes)) {
|
|
148
148
|
built.stopNodes = built.stopNodes.map(node => {
|
|
@@ -7,6 +7,7 @@ import DocTypeReader from './DocTypeReader.js';
|
|
|
7
7
|
import toNumber from "strnum";
|
|
8
8
|
import getIgnoreAttributesFn from "../ignoreAttributes.js";
|
|
9
9
|
import { Expression, Matcher } from 'path-expression-matcher';
|
|
10
|
+
import { ExpressionSet } from 'path-expression-matcher';
|
|
10
11
|
|
|
11
12
|
// const regx =
|
|
12
13
|
// '<((!\\[CDATA\\[([\\s\\S]*?)(]]>))|((NAME:)?(NAME))([^>]*)>|((\\/)(NAME)\\s*>))([^<]*)'
|
|
@@ -121,18 +122,20 @@ export default class OrderedObjParser {
|
|
|
121
122
|
this.isCurrentNodeStopNode = false;
|
|
122
123
|
|
|
123
124
|
// Pre-compile stopNodes expressions
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
125
|
+
this.stopNodeExpressionsSet = new ExpressionSet();
|
|
126
|
+
const stopNodesOpts = this.options.stopNodes;
|
|
127
|
+
if (stopNodesOpts && stopNodesOpts.length > 0) {
|
|
128
|
+
for (let i = 0; i < stopNodesOpts.length; i++) {
|
|
129
|
+
const stopNodeExp = stopNodesOpts[i];
|
|
128
130
|
if (typeof stopNodeExp === 'string') {
|
|
129
131
|
// Convert string to Expression object
|
|
130
|
-
this.
|
|
132
|
+
this.stopNodeExpressionsSet.add(new Expression(stopNodeExp));
|
|
131
133
|
} else if (stopNodeExp instanceof Expression) {
|
|
132
134
|
// Already an Expression object
|
|
133
|
-
this.
|
|
135
|
+
this.stopNodeExpressionsSet.add(stopNodeExp);
|
|
134
136
|
}
|
|
135
137
|
}
|
|
138
|
+
this.stopNodeExpressionsSet.seal();
|
|
136
139
|
}
|
|
137
140
|
}
|
|
138
141
|
|
|
@@ -160,28 +163,29 @@ function addExternalEntities(externalEntities) {
|
|
|
160
163
|
* @param {boolean} escapeEntities
|
|
161
164
|
*/
|
|
162
165
|
function parseTextData(val, tagName, jPath, dontTrim, hasAttributes, isLeafNode, escapeEntities) {
|
|
166
|
+
const options = this.options;
|
|
163
167
|
if (val !== undefined) {
|
|
164
|
-
if (
|
|
168
|
+
if (options.trimValues && !dontTrim) {
|
|
165
169
|
val = val.trim();
|
|
166
170
|
}
|
|
167
171
|
if (val.length > 0) {
|
|
168
172
|
if (!escapeEntities) val = this.replaceEntitiesValue(val, tagName, jPath);
|
|
169
173
|
|
|
170
174
|
// Pass jPath string or matcher based on options.jPath setting
|
|
171
|
-
const jPathOrMatcher =
|
|
172
|
-
const newval =
|
|
175
|
+
const jPathOrMatcher = options.jPath ? jPath.toString() : jPath;
|
|
176
|
+
const newval = options.tagValueProcessor(tagName, val, jPathOrMatcher, hasAttributes, isLeafNode);
|
|
173
177
|
if (newval === null || newval === undefined) {
|
|
174
178
|
//don't parse
|
|
175
179
|
return val;
|
|
176
180
|
} else if (typeof newval !== typeof val || newval !== val) {
|
|
177
181
|
//overwrite
|
|
178
182
|
return newval;
|
|
179
|
-
} else if (
|
|
180
|
-
return parseValue(val,
|
|
183
|
+
} else if (options.trimValues) {
|
|
184
|
+
return parseValue(val, options.parseTagValue, options.numberParseOptions);
|
|
181
185
|
} else {
|
|
182
186
|
const trimmedVal = val.trim();
|
|
183
187
|
if (trimmedVal === val) {
|
|
184
|
-
return parseValue(val,
|
|
188
|
+
return parseValue(val, options.parseTagValue, options.numberParseOptions);
|
|
185
189
|
} else {
|
|
186
190
|
return val;
|
|
187
191
|
}
|
|
@@ -209,7 +213,8 @@ function resolveNameSpace(tagname) {
|
|
|
209
213
|
const attrsRegx = new RegExp('([^\\s=]+)\\s*(=\\s*([\'"])([\\s\\S]*?)\\3)?', 'gm');
|
|
210
214
|
|
|
211
215
|
function buildAttributesMap(attrStr, jPath, tagName) {
|
|
212
|
-
|
|
216
|
+
const options = this.options;
|
|
217
|
+
if (options.ignoreAttributes !== true && typeof attrStr === 'string') {
|
|
213
218
|
// attrStr = attrStr.replace(/\r?\n/g, ' ');
|
|
214
219
|
//attrStr = attrStr || attrStr.trim();
|
|
215
220
|
|
|
@@ -229,7 +234,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
229
234
|
|
|
230
235
|
if (attrName.length && oldVal !== undefined) {
|
|
231
236
|
let val = oldVal;
|
|
232
|
-
if (
|
|
237
|
+
if (options.trimValues) val = val.trim();
|
|
233
238
|
val = this.replaceEntitiesValue(val, tagName, this.readonlyMatcher);
|
|
234
239
|
processedVals[i] = val;
|
|
235
240
|
|
|
@@ -244,7 +249,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
244
249
|
}
|
|
245
250
|
|
|
246
251
|
// Hoist toString() once — path doesn't change during attribute processing
|
|
247
|
-
const jPathStr =
|
|
252
|
+
const jPathStr = options.jPath ? jPath.toString() : this.readonlyMatcher;
|
|
248
253
|
|
|
249
254
|
// Second pass: apply processors, build final attrs
|
|
250
255
|
let hasAttrs = false;
|
|
@@ -253,28 +258,28 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
253
258
|
|
|
254
259
|
if (this.ignoreAttributesFn(attrName, jPathStr)) continue;
|
|
255
260
|
|
|
256
|
-
let aName =
|
|
261
|
+
let aName = options.attributeNamePrefix + attrName;
|
|
257
262
|
|
|
258
263
|
if (attrName.length) {
|
|
259
|
-
if (
|
|
260
|
-
aName =
|
|
264
|
+
if (options.transformAttributeName) {
|
|
265
|
+
aName = options.transformAttributeName(aName);
|
|
261
266
|
}
|
|
262
|
-
aName = sanitizeName(aName,
|
|
267
|
+
aName = sanitizeName(aName, options);
|
|
263
268
|
|
|
264
269
|
if (matches[i][4] !== undefined) {
|
|
265
270
|
// Reuse already-processed value — no double entity replacement
|
|
266
271
|
const oldVal = processedVals[i];
|
|
267
272
|
|
|
268
|
-
const newVal =
|
|
273
|
+
const newVal = options.attributeValueProcessor(attrName, oldVal, jPathStr);
|
|
269
274
|
if (newVal === null || newVal === undefined) {
|
|
270
275
|
attrs[aName] = oldVal;
|
|
271
276
|
} else if (typeof newVal !== typeof oldVal || newVal !== oldVal) {
|
|
272
277
|
attrs[aName] = newVal;
|
|
273
278
|
} else {
|
|
274
|
-
attrs[aName] = parseValue(oldVal,
|
|
279
|
+
attrs[aName] = parseValue(oldVal, options.parseAttributeValue, options.numberParseOptions);
|
|
275
280
|
}
|
|
276
281
|
hasAttrs = true;
|
|
277
|
-
} else if (
|
|
282
|
+
} else if (options.allowBooleanAttributes) {
|
|
278
283
|
attrs[aName] = true;
|
|
279
284
|
hasAttrs = true;
|
|
280
285
|
}
|
|
@@ -283,9 +288,9 @@ function buildAttributesMap(attrStr, jPath, tagName) {
|
|
|
283
288
|
|
|
284
289
|
if (!hasAttrs) return;
|
|
285
290
|
|
|
286
|
-
if (
|
|
291
|
+
if (options.attributesGroupName) {
|
|
287
292
|
const attrCollection = {};
|
|
288
|
-
attrCollection[
|
|
293
|
+
attrCollection[options.attributesGroupName] = attrs;
|
|
289
294
|
return attrCollection;
|
|
290
295
|
}
|
|
291
296
|
return attrs;
|
|
@@ -303,25 +308,30 @@ const parseXml = function (xmlData) {
|
|
|
303
308
|
// Reset entity expansion counters for this document
|
|
304
309
|
this.entityExpansionCount = 0;
|
|
305
310
|
this.currentExpandedLength = 0;
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
311
|
+
this.docTypeEntitiesKeys = [];
|
|
312
|
+
this.lastEntitiesKeys = Object.keys(this.lastEntities);
|
|
313
|
+
this.htmlEntitiesKeys = this.options.htmlEntities ? Object.keys(this.htmlEntities) : [];
|
|
314
|
+
const options = this.options;
|
|
315
|
+
const docTypeReader = new DocTypeReader(options.processEntities);
|
|
316
|
+
const xmlLen = xmlData.length;
|
|
317
|
+
for (let i = 0; i < xmlLen; i++) {//for each char in XML data
|
|
309
318
|
const ch = xmlData[i];
|
|
310
319
|
if (ch === '<') {
|
|
311
320
|
// const nextIndex = i+1;
|
|
312
321
|
// const _2ndChar = xmlData[nextIndex];
|
|
313
|
-
|
|
322
|
+
const c1 = xmlData.charCodeAt(i + 1);
|
|
323
|
+
if (c1 === 47) {//Closing Tag '/'
|
|
314
324
|
const closeIndex = findClosingIndex(xmlData, ">", i, "Closing Tag is not closed.")
|
|
315
325
|
let tagName = xmlData.substring(i + 2, closeIndex).trim();
|
|
316
326
|
|
|
317
|
-
if (
|
|
327
|
+
if (options.removeNSPrefix) {
|
|
318
328
|
const colonIndex = tagName.indexOf(":");
|
|
319
329
|
if (colonIndex !== -1) {
|
|
320
330
|
tagName = tagName.substr(colonIndex + 1);
|
|
321
331
|
}
|
|
322
332
|
}
|
|
323
333
|
|
|
324
|
-
tagName = transformTagName(
|
|
334
|
+
tagName = transformTagName(options.transformTagName, tagName, "", options).tagName;
|
|
325
335
|
|
|
326
336
|
if (currentNode) {
|
|
327
337
|
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
@@ -329,10 +339,10 @@ const parseXml = function (xmlData) {
|
|
|
329
339
|
|
|
330
340
|
//check if last tag of nested tag was unpaired tag
|
|
331
341
|
const lastTagName = this.matcher.getCurrentTag();
|
|
332
|
-
if (tagName &&
|
|
342
|
+
if (tagName && options.unpairedTagsSet.has(tagName)) {
|
|
333
343
|
throw new Error(`Unpaired tag can not be used as closing tag: </${tagName}>`);
|
|
334
344
|
}
|
|
335
|
-
if (lastTagName &&
|
|
345
|
+
if (lastTagName && options.unpairedTagsSet.has(lastTagName)) {
|
|
336
346
|
// Pop the unpaired tag
|
|
337
347
|
this.matcher.pop();
|
|
338
348
|
this.tagsNodeStack.pop();
|
|
@@ -344,18 +354,18 @@ const parseXml = function (xmlData) {
|
|
|
344
354
|
currentNode = this.tagsNodeStack.pop();//avoid recursion, set the parent tag scope
|
|
345
355
|
textData = "";
|
|
346
356
|
i = closeIndex;
|
|
347
|
-
} else if (
|
|
357
|
+
} else if (c1 === 63) { //'?'
|
|
348
358
|
|
|
349
359
|
let tagData = readTagExp(xmlData, i, false, "?>");
|
|
350
360
|
if (!tagData) throw new Error("Pi Tag is not closed.");
|
|
351
361
|
|
|
352
362
|
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
353
|
-
if ((
|
|
363
|
+
if ((options.ignoreDeclaration && tagData.tagName === "?xml") || options.ignorePiTags) {
|
|
354
364
|
//do nothing
|
|
355
365
|
} else {
|
|
356
366
|
|
|
357
367
|
const childNode = new xmlNode(tagData.tagName);
|
|
358
|
-
childNode.add(
|
|
368
|
+
childNode.add(options.textNodeName, "");
|
|
359
369
|
|
|
360
370
|
if (tagData.tagName !== tagData.tagExp && tagData.attrExpPresent) {
|
|
361
371
|
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, this.matcher, tagData.tagName);
|
|
@@ -365,21 +375,26 @@ const parseXml = function (xmlData) {
|
|
|
365
375
|
|
|
366
376
|
|
|
367
377
|
i = tagData.closeIndex + 1;
|
|
368
|
-
} else if (
|
|
378
|
+
} else if (c1 === 33
|
|
379
|
+
&& xmlData.charCodeAt(i + 2) === 45
|
|
380
|
+
&& xmlData.charCodeAt(i + 3) === 45) { //'!--'
|
|
369
381
|
const endIndex = findClosingIndex(xmlData, "-->", i + 4, "Comment is not closed.")
|
|
370
|
-
if (
|
|
382
|
+
if (options.commentPropName) {
|
|
371
383
|
const comment = xmlData.substring(i + 4, endIndex - 2);
|
|
372
384
|
|
|
373
385
|
textData = this.saveTextToParentTag(textData, currentNode, this.readonlyMatcher);
|
|
374
386
|
|
|
375
|
-
currentNode.add(
|
|
387
|
+
currentNode.add(options.commentPropName, [{ [options.textNodeName]: comment }]);
|
|
376
388
|
}
|
|
377
389
|
i = endIndex;
|
|
378
|
-
} else if (
|
|
390
|
+
} else if (c1 === 33
|
|
391
|
+
&& xmlData.charCodeAt(i + 2) === 68) { //'!D'
|
|
379
392
|
const result = docTypeReader.readDocType(xmlData, i);
|
|
380
393
|
this.docTypeEntities = result.entities;
|
|
394
|
+
this.docTypeEntitiesKeys = Object.keys(this.docTypeEntities) || []
|
|
381
395
|
i = result.i;
|
|
382
|
-
} else if (
|
|
396
|
+
} else if (c1 === 33
|
|
397
|
+
&& xmlData.charCodeAt(i + 2) === 91) { // '!['
|
|
383
398
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
|
|
384
399
|
const tagExp = xmlData.substring(i + 9, closeIndex);
|
|
385
400
|
|
|
@@ -389,20 +404,20 @@ const parseXml = function (xmlData) {
|
|
|
389
404
|
if (val == undefined) val = "";
|
|
390
405
|
|
|
391
406
|
//cdata should be set even if it is 0 length string
|
|
392
|
-
if (
|
|
393
|
-
currentNode.add(
|
|
407
|
+
if (options.cdataPropName) {
|
|
408
|
+
currentNode.add(options.cdataPropName, [{ [options.textNodeName]: tagExp }]);
|
|
394
409
|
} else {
|
|
395
|
-
currentNode.add(
|
|
410
|
+
currentNode.add(options.textNodeName, val);
|
|
396
411
|
}
|
|
397
412
|
|
|
398
413
|
i = closeIndex + 2;
|
|
399
414
|
} else {//Opening tag
|
|
400
|
-
let result = readTagExp(xmlData, i,
|
|
415
|
+
let result = readTagExp(xmlData, i, options.removeNSPrefix);
|
|
401
416
|
|
|
402
417
|
// Safety check: readTagExp can return undefined
|
|
403
418
|
if (!result) {
|
|
404
419
|
// Log context for debugging
|
|
405
|
-
const context = xmlData.substring(Math.max(0, i - 50), Math.min(
|
|
420
|
+
const context = xmlData.substring(Math.max(0, i - 50), Math.min(xmlLen, i + 50));
|
|
406
421
|
throw new Error(`readTagExp returned undefined at position ${i}. Context: "${context}"`);
|
|
407
422
|
}
|
|
408
423
|
|
|
@@ -412,13 +427,13 @@ const parseXml = function (xmlData) {
|
|
|
412
427
|
let attrExpPresent = result.attrExpPresent;
|
|
413
428
|
let closeIndex = result.closeIndex;
|
|
414
429
|
|
|
415
|
-
({ tagName, tagExp } = transformTagName(
|
|
430
|
+
({ tagName, tagExp } = transformTagName(options.transformTagName, tagName, tagExp, options));
|
|
416
431
|
|
|
417
|
-
if (
|
|
418
|
-
(tagName ===
|
|
419
|
-
|| tagName ===
|
|
420
|
-
|| tagName ===
|
|
421
|
-
|| tagName ===
|
|
432
|
+
if (options.strictReservedNames &&
|
|
433
|
+
(tagName === options.commentPropName
|
|
434
|
+
|| tagName === options.cdataPropName
|
|
435
|
+
|| tagName === options.textNodeName
|
|
436
|
+
|| tagName === options.attributesGroupName
|
|
422
437
|
)) {
|
|
423
438
|
throw new Error(`Invalid tag name: ${tagName}`);
|
|
424
439
|
}
|
|
@@ -433,7 +448,7 @@ const parseXml = function (xmlData) {
|
|
|
433
448
|
|
|
434
449
|
//check if last tag was unpaired tag
|
|
435
450
|
const lastTag = currentNode;
|
|
436
|
-
if (lastTag &&
|
|
451
|
+
if (lastTag && options.unpairedTagsSet.has(lastTag.tagname)) {
|
|
437
452
|
currentNode = this.tagsNodeStack.pop();
|
|
438
453
|
this.matcher.pop();
|
|
439
454
|
}
|
|
@@ -475,13 +490,13 @@ const parseXml = function (xmlData) {
|
|
|
475
490
|
|
|
476
491
|
if (prefixedAttrs) {
|
|
477
492
|
// Extract raw attributes (without prefix) for our use
|
|
478
|
-
rawAttrs = extractRawAttributes(prefixedAttrs,
|
|
493
|
+
rawAttrs = extractRawAttributes(prefixedAttrs, options);
|
|
479
494
|
}
|
|
480
495
|
}
|
|
481
496
|
|
|
482
497
|
// Now check if this is a stop node (after attributes are set)
|
|
483
498
|
if (tagName !== xmlObj.tagname) {
|
|
484
|
-
this.isCurrentNodeStopNode = this.isItStopNode(
|
|
499
|
+
this.isCurrentNodeStopNode = this.isItStopNode();
|
|
485
500
|
}
|
|
486
501
|
|
|
487
502
|
const startIndex = i;
|
|
@@ -493,7 +508,7 @@ const parseXml = function (xmlData) {
|
|
|
493
508
|
i = result.closeIndex;
|
|
494
509
|
}
|
|
495
510
|
//unpaired tag
|
|
496
|
-
else if (
|
|
511
|
+
else if (options.unpairedTagsSet.has(tagName)) {
|
|
497
512
|
i = result.closeIndex;
|
|
498
513
|
}
|
|
499
514
|
//normal tag
|
|
@@ -512,7 +527,7 @@ const parseXml = function (xmlData) {
|
|
|
512
527
|
}
|
|
513
528
|
|
|
514
529
|
// For stop nodes, store raw content as-is without any processing
|
|
515
|
-
childNode.add(
|
|
530
|
+
childNode.add(options.textNodeName, tagContent);
|
|
516
531
|
|
|
517
532
|
this.matcher.pop(); // Pop the stop node tag
|
|
518
533
|
this.isCurrentNodeStopNode = false; // Reset flag
|
|
@@ -521,7 +536,7 @@ const parseXml = function (xmlData) {
|
|
|
521
536
|
} else {
|
|
522
537
|
//selfClosing tag
|
|
523
538
|
if (isSelfClosing) {
|
|
524
|
-
({ tagName, tagExp } = transformTagName(
|
|
539
|
+
({ tagName, tagExp } = transformTagName(options.transformTagName, tagName, tagExp, options));
|
|
525
540
|
|
|
526
541
|
const childNode = new xmlNode(tagName);
|
|
527
542
|
if (prefixedAttrs) {
|
|
@@ -531,7 +546,7 @@ const parseXml = function (xmlData) {
|
|
|
531
546
|
this.matcher.pop(); // Pop self-closing tag
|
|
532
547
|
this.isCurrentNodeStopNode = false; // Reset flag
|
|
533
548
|
}
|
|
534
|
-
else if (
|
|
549
|
+
else if (options.unpairedTagsSet.has(tagName)) {//unpaired tag
|
|
535
550
|
const childNode = new xmlNode(tagName);
|
|
536
551
|
if (prefixedAttrs) {
|
|
537
552
|
childNode[":@"] = prefixedAttrs;
|
|
@@ -546,7 +561,7 @@ const parseXml = function (xmlData) {
|
|
|
546
561
|
//opening tag
|
|
547
562
|
else {
|
|
548
563
|
const childNode = new xmlNode(tagName);
|
|
549
|
-
if (this.tagsNodeStack.length >
|
|
564
|
+
if (this.tagsNodeStack.length > options.maxNestedTags) {
|
|
550
565
|
throw new Error("Maximum nested tags exceeded");
|
|
551
566
|
}
|
|
552
567
|
this.tagsNodeStack.push(currentNode);
|
|
@@ -618,7 +633,7 @@ function replaceEntitiesValue(val, tagName, jPath) {
|
|
|
618
633
|
}
|
|
619
634
|
|
|
620
635
|
// Replace DOCTYPE entities
|
|
621
|
-
for (const entityName of
|
|
636
|
+
for (const entityName of this.docTypeEntitiesKeys) {
|
|
622
637
|
const entity = this.docTypeEntities[entityName];
|
|
623
638
|
const matches = val.match(entity.regx);
|
|
624
639
|
|
|
@@ -652,7 +667,7 @@ function replaceEntitiesValue(val, tagName, jPath) {
|
|
|
652
667
|
}
|
|
653
668
|
if (val.indexOf('&') === -1) return val;
|
|
654
669
|
// Replace standard entities
|
|
655
|
-
for (const entityName of
|
|
670
|
+
for (const entityName of this.lastEntitiesKeys) {
|
|
656
671
|
const entity = this.lastEntities[entityName];
|
|
657
672
|
const matches = val.match(entity.regex);
|
|
658
673
|
if (matches) {
|
|
@@ -669,22 +684,20 @@ function replaceEntitiesValue(val, tagName, jPath) {
|
|
|
669
684
|
if (val.indexOf('&') === -1) return val;
|
|
670
685
|
|
|
671
686
|
// Replace HTML entities if enabled
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
);
|
|
684
|
-
}
|
|
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
|
+
);
|
|
685
698
|
}
|
|
686
|
-
val = val.replace(entity.regex, entity.val);
|
|
687
699
|
}
|
|
700
|
+
val = val.replace(entity.regex, entity.val);
|
|
688
701
|
}
|
|
689
702
|
|
|
690
703
|
// Replace ampersand entity last
|
|
@@ -712,20 +725,14 @@ function saveTextToParentTag(textData, parentNode, matcher, isLeafNode) {
|
|
|
712
725
|
return textData;
|
|
713
726
|
}
|
|
714
727
|
|
|
715
|
-
//TODO: use jPath to simplify the logic
|
|
716
728
|
/**
|
|
717
729
|
* @param {Array<Expression>} stopNodeExpressions - Array of compiled Expression objects
|
|
718
730
|
* @param {Matcher} matcher - Current path matcher
|
|
719
731
|
*/
|
|
720
|
-
function isItStopNode(
|
|
721
|
-
if (
|
|
732
|
+
function isItStopNode() {
|
|
733
|
+
if (this.stopNodeExpressionsSet.size === 0) return false;
|
|
722
734
|
|
|
723
|
-
|
|
724
|
-
if (matcher.matches(stopNodeExpressions[i])) {
|
|
725
|
-
return true;
|
|
726
|
-
}
|
|
727
|
-
}
|
|
728
|
-
return false;
|
|
735
|
+
return this.matcher.matchesAny(this.stopNodeExpressionsSet);
|
|
729
736
|
}
|
|
730
737
|
|
|
731
738
|
/**
|
|
@@ -735,32 +742,33 @@ function isItStopNode(stopNodeExpressions, matcher) {
|
|
|
735
742
|
* @returns
|
|
736
743
|
*/
|
|
737
744
|
function tagExpWithClosingIndex(xmlData, i, closingChar = ">") {
|
|
738
|
-
let attrBoundary;
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
745
|
+
let attrBoundary = 0;
|
|
746
|
+
const chars = [];
|
|
747
|
+
const len = xmlData.length;
|
|
748
|
+
const closeCode0 = closingChar.charCodeAt(0);
|
|
749
|
+
const closeCode1 = closingChar.length > 1 ? closingChar.charCodeAt(1) : -1;
|
|
750
|
+
|
|
751
|
+
for (let index = i; index < len; index++) {
|
|
752
|
+
const code = xmlData.charCodeAt(index);
|
|
753
|
+
|
|
742
754
|
if (attrBoundary) {
|
|
743
|
-
if (
|
|
744
|
-
} else if (
|
|
745
|
-
attrBoundary =
|
|
746
|
-
} else if (
|
|
747
|
-
if (
|
|
748
|
-
if (xmlData
|
|
749
|
-
return {
|
|
750
|
-
data: tagExp,
|
|
751
|
-
index: index
|
|
752
|
-
}
|
|
755
|
+
if (code === attrBoundary) attrBoundary = 0;
|
|
756
|
+
} else if (code === 34 || code === 39) { // " or '
|
|
757
|
+
attrBoundary = code;
|
|
758
|
+
} else if (code === closeCode0) {
|
|
759
|
+
if (closeCode1 !== -1) {
|
|
760
|
+
if (xmlData.charCodeAt(index + 1) === closeCode1) {
|
|
761
|
+
return { data: String.fromCharCode(...chars), index };
|
|
753
762
|
}
|
|
754
763
|
} else {
|
|
755
|
-
return {
|
|
756
|
-
data: tagExp,
|
|
757
|
-
index: index
|
|
758
|
-
}
|
|
764
|
+
return { data: String.fromCharCode(...chars), index };
|
|
759
765
|
}
|
|
760
|
-
} else if (
|
|
761
|
-
|
|
766
|
+
} else if (code === 9) { // \t
|
|
767
|
+
chars.push(32); // space
|
|
768
|
+
continue;
|
|
762
769
|
}
|
|
763
|
-
|
|
770
|
+
|
|
771
|
+
chars.push(code);
|
|
764
772
|
}
|
|
765
773
|
}
|
|
766
774
|
|
|
@@ -773,6 +781,12 @@ function findClosingIndex(xmlData, str, i, errMsg) {
|
|
|
773
781
|
}
|
|
774
782
|
}
|
|
775
783
|
|
|
784
|
+
function findClosingChar(xmlData, char, i, errMsg) {
|
|
785
|
+
const closingIndex = xmlData.indexOf(char, i);
|
|
786
|
+
if (closingIndex === -1) throw new Error(errMsg);
|
|
787
|
+
return closingIndex; // no offset needed
|
|
788
|
+
}
|
|
789
|
+
|
|
776
790
|
function readTagExp(xmlData, i, removeNSPrefix, closingChar = ">") {
|
|
777
791
|
const result = tagExpWithClosingIndex(xmlData, i + 1, closingChar);
|
|
778
792
|
if (!result) return;
|
|
@@ -814,10 +828,12 @@ function readStopNodeData(xmlData, tagName, i) {
|
|
|
814
828
|
// Starting at 1 since we already have an open tag
|
|
815
829
|
let openTagCount = 1;
|
|
816
830
|
|
|
817
|
-
|
|
831
|
+
const xmllen = xmlData.length;
|
|
832
|
+
for (; i < xmllen; i++) {
|
|
818
833
|
if (xmlData[i] === "<") {
|
|
819
|
-
|
|
820
|
-
|
|
834
|
+
const c1 = xmlData.charCodeAt(i + 1);
|
|
835
|
+
if (c1 === 47) {//close tag '/'
|
|
836
|
+
const closeIndex = findClosingChar(xmlData, ">", i, `${tagName} is not closed`);
|
|
821
837
|
let closeTagName = xmlData.substring(i + 2, closeIndex).trim();
|
|
822
838
|
if (closeTagName === tagName) {
|
|
823
839
|
openTagCount--;
|
|
@@ -829,13 +845,16 @@ function readStopNodeData(xmlData, tagName, i) {
|
|
|
829
845
|
}
|
|
830
846
|
}
|
|
831
847
|
i = closeIndex;
|
|
832
|
-
} else if (
|
|
848
|
+
} else if (c1 === 63) { //?
|
|
833
849
|
const closeIndex = findClosingIndex(xmlData, "?>", i + 1, "StopNode is not closed.")
|
|
834
850
|
i = closeIndex;
|
|
835
|
-
} else if (
|
|
851
|
+
} else if (c1 === 33
|
|
852
|
+
&& xmlData.charCodeAt(i + 2) === 45
|
|
853
|
+
&& xmlData.charCodeAt(i + 3) === 45) { // '!--'
|
|
836
854
|
const closeIndex = findClosingIndex(xmlData, "-->", i + 3, "StopNode is not closed.")
|
|
837
855
|
i = closeIndex;
|
|
838
|
-
} else if (
|
|
856
|
+
} else if (c1 === 33
|
|
857
|
+
&& xmlData.charCodeAt(i + 2) === 91) { // '!['
|
|
839
858
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "StopNode is not closed.") - 2;
|
|
840
859
|
i = closeIndex;
|
|
841
860
|
} else {
|