fast-xml-parser 4.0.0-beta.6 → 4.0.0-beta.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 +7 -0
- package/package.json +1 -1
- package/src/util.js +0 -17
- package/src/validator.js +3 -6
- package/src/xmlbuilder/json2xml.js +1 -24
- package/src/xmlbuilder/orderedJs2Xml.js +4 -6
- package/src/xmlparser/OptionsBuilder.js +2 -29
- package/src/xmlparser/OrderedObjParser.js +31 -51
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
Note: If you find missing information about particular minor version, that version must have been changed without any functional change in this library.
|
|
2
2
|
|
|
3
|
+
** 4.0.0-beta.7 / 2021-12-09**
|
|
4
|
+
* fix Validator bug when an attribute has no value but '=' only
|
|
5
|
+
* XML Builder should suppress unpaired tags by default.
|
|
6
|
+
* documents update for missing features
|
|
7
|
+
* refactoring to use Object.assign
|
|
8
|
+
* refactoring to remove repeated code
|
|
9
|
+
|
|
3
10
|
** 4.0.0-beta.6 / 2021-12-05**
|
|
4
11
|
* Support PI Tags processing
|
|
5
12
|
* Support `suppressBooleanAttributes` by XML Builder for attributes with value `true`.
|
package/package.json
CHANGED
package/src/util.js
CHANGED
|
@@ -67,23 +67,6 @@ exports.getValue = function(v) {
|
|
|
67
67
|
// const fakeCall = function(a) {return a;};
|
|
68
68
|
// const fakeCallNoReturn = function() {};
|
|
69
69
|
|
|
70
|
-
const buildOptions = function(options, defaultOptions, props) {
|
|
71
|
-
let newOptions = {};
|
|
72
|
-
if (!options) {
|
|
73
|
-
return defaultOptions; //if there are not options
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
for (let i = 0; i < props.length; i++) {
|
|
77
|
-
if (options[props[i]] !== undefined) {
|
|
78
|
-
newOptions[props[i]] = options[props[i]];
|
|
79
|
-
} else {
|
|
80
|
-
newOptions[props[i]] = defaultOptions[props[i]];
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
return newOptions;
|
|
84
|
-
};
|
|
85
|
-
|
|
86
|
-
exports.buildOptions = buildOptions;
|
|
87
70
|
exports.isName = isName;
|
|
88
71
|
exports.getAllMatches = getAllMatches;
|
|
89
72
|
exports.nameRegexp = nameRegexp;
|
package/src/validator.js
CHANGED
|
@@ -7,14 +7,9 @@ const defaultOptions = {
|
|
|
7
7
|
unpairedTags: []
|
|
8
8
|
};
|
|
9
9
|
|
|
10
|
-
const props = [
|
|
11
|
-
'allowBooleanAttributes',
|
|
12
|
-
'unpairedTags'
|
|
13
|
-
];
|
|
14
|
-
|
|
15
10
|
//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
|
|
16
11
|
exports.validate = function (xmlData, options) {
|
|
17
|
-
options =
|
|
12
|
+
options = Object.assign({}, defaultOptions, options);
|
|
18
13
|
|
|
19
14
|
//xmlData = xmlData.replace(/(\r\n|\n|\r)/gm,"");//make it single line
|
|
20
15
|
//xmlData = xmlData.replace(/(^\s*<\?xml.*?\?>)/g,"");//Remove XML starting tag
|
|
@@ -331,6 +326,8 @@ function validateAttributeString(attrStr, options) {
|
|
|
331
326
|
if (matches[i][1].length === 0) {
|
|
332
327
|
//nospace before attribute name: a="sd"b="saf"
|
|
333
328
|
return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' has no space in starting.", getPositionFromMatch(matches[i]))
|
|
329
|
+
} else if (matches[i][3] !== undefined && matches[i][4] === undefined) {
|
|
330
|
+
return getErrorObject('InvalidAttr', "Attribute '"+matches[i][2]+"' is without value.", getPositionFromMatch(matches[i]));
|
|
334
331
|
} else if (matches[i][3] === undefined && !options.allowBooleanAttributes) {
|
|
335
332
|
//independent attribute: ab
|
|
336
333
|
return getErrorObject('InvalidAttr', "boolean attribute '"+matches[i][2]+"' is not allowed.", getPositionFromMatch(matches[i]));
|
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
//parse Empty Node as self closing node
|
|
3
|
-
const buildOptions = require('../util').buildOptions;
|
|
4
3
|
const buildFromOrderedJs = require('./orderedJs2Xml');
|
|
5
4
|
|
|
6
5
|
const defaultOptions = {
|
|
@@ -32,30 +31,8 @@ const defaultOptions = {
|
|
|
32
31
|
stopNodes: []
|
|
33
32
|
};
|
|
34
33
|
|
|
35
|
-
const props = [
|
|
36
|
-
'attributeNamePrefix',
|
|
37
|
-
'attributesGroupName',
|
|
38
|
-
'textNodeName',
|
|
39
|
-
'ignoreAttributes',
|
|
40
|
-
'cdataPropName',
|
|
41
|
-
'format',
|
|
42
|
-
'indentBy',
|
|
43
|
-
'suppressEmptyNode',
|
|
44
|
-
'suppressBooleanAttributes',
|
|
45
|
-
'tagValueProcessor',
|
|
46
|
-
'attributeValueProcessor',
|
|
47
|
-
'arrayNodeName', //when array as root
|
|
48
|
-
'preserveOrder',
|
|
49
|
-
"commentPropName",
|
|
50
|
-
"unpairedTags",
|
|
51
|
-
"entities",
|
|
52
|
-
"processEntities",
|
|
53
|
-
"stopNodes",
|
|
54
|
-
// 'rootNodeName', //when jsObject have multiple properties on root level
|
|
55
|
-
];
|
|
56
|
-
|
|
57
34
|
function Builder(options) {
|
|
58
|
-
this.options =
|
|
35
|
+
this.options = Object.assign({}, defaultOptions, options);
|
|
59
36
|
if (this.options.ignoreAttributes || this.options.attributesGroupName) {
|
|
60
37
|
this.isAttribute = function(/*a*/) {
|
|
61
38
|
return false;
|
|
@@ -47,12 +47,10 @@ function arrToStr(arr, options, jPath, level){
|
|
|
47
47
|
const attStr = attr_to_str(tagObj[":@"], options);
|
|
48
48
|
let tagStart = indentation + `<${tagName}${attStr}`;
|
|
49
49
|
let tagValue = arrToStr(tagObj[tagName], options, newJPath, level + 1);
|
|
50
|
-
if(
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
xmlStr += tagStart + "/>";
|
|
55
|
-
}
|
|
50
|
+
if(options.unpairedTags.indexOf(tagName) !== -1){
|
|
51
|
+
xmlStr += tagStart + ">";
|
|
52
|
+
}else if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
|
|
53
|
+
xmlStr += tagStart + "/>";
|
|
56
54
|
}else{
|
|
57
55
|
//TODO: node with only text value should not parse the text value in next line
|
|
58
56
|
xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>` ;
|
|
@@ -31,36 +31,9 @@ const defaultOptions = {
|
|
|
31
31
|
htmlEntities: false,
|
|
32
32
|
};
|
|
33
33
|
|
|
34
|
-
const props = [
|
|
35
|
-
'preserveOrder',
|
|
36
|
-
'attributeNamePrefix',
|
|
37
|
-
'attributesGroupName',
|
|
38
|
-
'textNodeName',
|
|
39
|
-
'ignoreAttributes',
|
|
40
|
-
'removeNSPrefix',
|
|
41
|
-
'allowBooleanAttributes',
|
|
42
|
-
'parseTagValue',
|
|
43
|
-
'parseAttributeValue',
|
|
44
|
-
'trimValues',
|
|
45
|
-
'cdataPropName',
|
|
46
|
-
'tagValueProcessor',
|
|
47
|
-
'attributeValueProcessor',
|
|
48
|
-
'numberParseOptions',
|
|
49
|
-
'stopNodes',
|
|
50
|
-
'alwaysCreateTextNode',
|
|
51
|
-
'isArray',
|
|
52
|
-
'commentPropName',
|
|
53
|
-
'unpairedTags',
|
|
54
|
-
'processEntities',
|
|
55
|
-
'htmlEntities'
|
|
56
|
-
];
|
|
57
|
-
|
|
58
|
-
const util = require('../util');
|
|
59
|
-
|
|
60
34
|
const buildOptions = function(options) {
|
|
61
|
-
return
|
|
35
|
+
return Object.assign({}, defaultOptions, options);
|
|
62
36
|
};
|
|
63
37
|
|
|
64
38
|
exports.buildOptions = buildOptions;
|
|
65
|
-
exports.defaultOptions = defaultOptions;
|
|
66
|
-
exports.props = props;
|
|
39
|
+
exports.defaultOptions = defaultOptions;
|
|
@@ -49,6 +49,7 @@ class OrderedObjParser{
|
|
|
49
49
|
this.isItStopNode = isItStopNode;
|
|
50
50
|
this.replaceEntitiesValue = replaceEntitiesValue;
|
|
51
51
|
this.readStopNodeData = readStopNodeData;
|
|
52
|
+
this.saveTextToParentTag = saveTextToParentTag;
|
|
52
53
|
}
|
|
53
54
|
|
|
54
55
|
}
|
|
@@ -208,53 +209,26 @@ const parseXml = function(xmlData) {
|
|
|
208
209
|
textData = "";
|
|
209
210
|
i = closeIndex;
|
|
210
211
|
} else if( xmlData[i+1] === '?') {
|
|
211
|
-
let
|
|
212
|
-
if(!
|
|
213
|
-
|
|
214
|
-
let tagName= result.tagName;
|
|
215
|
-
let tagExp = result.tagExp;
|
|
216
|
-
let attrExpPresent = result.attrExpPresent;
|
|
217
|
-
let closeIndex = result.closeIndex;
|
|
218
|
-
|
|
219
|
-
//TODO: remove repeated code
|
|
220
|
-
if(textData){ //store previously collected data as textNode
|
|
221
|
-
textData = this.parseTextData(textData
|
|
222
|
-
, currentNode.tagname
|
|
223
|
-
, jPath
|
|
224
|
-
,false
|
|
225
|
-
, currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
|
|
226
|
-
, Object.keys(currentNode.child).length === 0);
|
|
227
|
-
|
|
228
|
-
if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
|
|
229
|
-
textData = "";
|
|
230
|
-
}
|
|
212
|
+
let tagData = readTagExp(xmlData,i, false, "?>");
|
|
213
|
+
if(!tagData) throw new Error("Pi Tag is not closed.");
|
|
214
|
+
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
|
231
215
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
216
|
+
const childNode = new xmlNode(tagData.tagName);
|
|
217
|
+
childNode.add(this.options.textNodeName, "");
|
|
218
|
+
|
|
219
|
+
if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
|
|
220
|
+
childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath);
|
|
221
|
+
}
|
|
222
|
+
currentNode.addChild(childNode);
|
|
239
223
|
|
|
240
|
-
i = closeIndex + 1;
|
|
224
|
+
i = tagData.closeIndex + 1;
|
|
241
225
|
} else if(xmlData.substr(i + 1, 3) === '!--') {
|
|
242
226
|
const endIndex = findClosingIndex(xmlData, "-->", i, "Comment is not closed.")
|
|
243
227
|
if(this.options.commentPropName){
|
|
244
228
|
const comment = xmlData.substring(i + 4, endIndex - 2);
|
|
245
229
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
textData = this.parseTextData(textData
|
|
249
|
-
, currentNode.tagname
|
|
250
|
-
, jPath
|
|
251
|
-
,false
|
|
252
|
-
, currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
|
|
253
|
-
, Object.keys(currentNode.child).length === 0);
|
|
254
|
-
|
|
255
|
-
if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
|
|
256
|
-
textData = "";
|
|
257
|
-
}
|
|
230
|
+
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
|
231
|
+
|
|
258
232
|
currentNode.add(this.options.commentPropName, [ { [this.options.textNodeName] : comment } ]);
|
|
259
233
|
}
|
|
260
234
|
i = endIndex;
|
|
@@ -266,17 +240,7 @@ const parseXml = function(xmlData) {
|
|
|
266
240
|
const closeIndex = findClosingIndex(xmlData, "]]>", i, "CDATA is not closed.") - 2;
|
|
267
241
|
const tagExp = xmlData.substring(i + 9,closeIndex);
|
|
268
242
|
|
|
269
|
-
|
|
270
|
-
textData = this.parseTextData(textData
|
|
271
|
-
, currentNode.tagname
|
|
272
|
-
, jPath
|
|
273
|
-
,false
|
|
274
|
-
, currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false
|
|
275
|
-
, Object.keys(currentNode.child).length === 0);
|
|
276
|
-
|
|
277
|
-
if(textData !== undefined && textData !== "") currentNode.add(this.options.textNodeName, textData);
|
|
278
|
-
textData = "";
|
|
279
|
-
}
|
|
243
|
+
textData = this.saveTextToParentTag(textData, currentNode, jPath);
|
|
280
244
|
|
|
281
245
|
//cdata should be set even if it is 0 length string
|
|
282
246
|
if(this.options.cdataPropName){
|
|
@@ -405,6 +369,22 @@ const replaceEntitiesValue = function(val){
|
|
|
405
369
|
}
|
|
406
370
|
return val;
|
|
407
371
|
}
|
|
372
|
+
function saveTextToParentTag(textData, currentNode, jPath) {
|
|
373
|
+
if (textData) { //store previously collected data as textNode
|
|
374
|
+
textData = this.parseTextData(textData,
|
|
375
|
+
currentNode.tagname,
|
|
376
|
+
jPath,
|
|
377
|
+
false,
|
|
378
|
+
currentNode[":@"] ? Object.keys(currentNode[":@"]).length !== 0 : false,
|
|
379
|
+
Object.keys(currentNode.child).length === 0);
|
|
380
|
+
|
|
381
|
+
if (textData !== undefined && textData !== "")
|
|
382
|
+
currentNode.add(this.options.textNodeName, textData);
|
|
383
|
+
textData = "";
|
|
384
|
+
}
|
|
385
|
+
return textData;
|
|
386
|
+
}
|
|
387
|
+
|
|
408
388
|
//TODO: use jPath to simplify the logic
|
|
409
389
|
/**
|
|
410
390
|
*
|