fast-xml-parser 4.0.0-beta.0 → 4.0.0-beta.2
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/package.json +1 -1
- package/src/fxp.d.ts +11 -8
- package/src/validator.js +7 -1
- package/src/xmlbuilder/json2xml.js +10 -8
- package/src/xmlbuilder/orderedJs2Xml.js +17 -4
- package/src/xmlparser/OptionsBuilder.js +7 -3
- package/src/xmlparser/OrderedObjParser.js +47 -13
- package/src/xmlparser/node2json.js +7 -1
- package/src/xmlparser/xmlNode.js +3 -3
package/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,17 @@
|
|
|
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.2 / 2021-11-19**
|
|
4
|
+
* rename `attrMap` to `attibutes` in parser output when `preserveOrder:true`
|
|
5
|
+
* supports unpairedTags
|
|
6
|
+
|
|
7
|
+
**⚠️ 4.0.0-beta.1 / 2021-11-18**
|
|
8
|
+
* Parser returns an array now
|
|
9
|
+
* to make the structure common
|
|
10
|
+
* and to return root level detail
|
|
11
|
+
* renamed `cdataTagName` to `cdataPropName`
|
|
12
|
+
* Added `commentPropName`
|
|
13
|
+
* fix typings
|
|
14
|
+
|
|
3
15
|
**⚠️ 4.0.0-beta.0 / 2021-11-16**
|
|
4
16
|
* Name change of many configuration properties.
|
|
5
17
|
* `attrNodeName` to `attributesGroupName`
|
package/package.json
CHANGED
package/src/fxp.d.ts
CHANGED
|
@@ -9,11 +9,13 @@ type X2jOptions = {
|
|
|
9
9
|
parseTagValue: boolean;
|
|
10
10
|
parseAttributeValue: boolean;
|
|
11
11
|
trimValues: boolean;
|
|
12
|
-
|
|
12
|
+
cdataPropName: false | string;
|
|
13
|
+
commentPropName: false | string;
|
|
13
14
|
tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) => string;
|
|
14
15
|
attributeValueProcessor: (attrName: string, attrValue: string, jPath: string) => string;
|
|
15
16
|
numberParseOptions: strnumOptions;
|
|
16
17
|
stopNodes: string[];
|
|
18
|
+
unpairedTags: string[];
|
|
17
19
|
alwaysCreateTextNode: boolean;
|
|
18
20
|
isArray: (tagName: string, jPath: string, isLeafNode: boolean, isAttribute: boolean) => boolean;
|
|
19
21
|
};
|
|
@@ -25,6 +27,7 @@ type strnumOptions = {
|
|
|
25
27
|
type X2jOptionsOptional = Partial<X2jOptions>;
|
|
26
28
|
type validationOptions = {
|
|
27
29
|
allowBooleanAttributes: boolean;
|
|
30
|
+
unpairedTags: string[];
|
|
28
31
|
};
|
|
29
32
|
type validationOptionsOptional = Partial<validationOptions>;
|
|
30
33
|
|
|
@@ -33,12 +36,14 @@ type XmlBuilderOptions = {
|
|
|
33
36
|
attributesGroupName: false | string;
|
|
34
37
|
textNodeName: string;
|
|
35
38
|
ignoreAttributes: boolean;
|
|
36
|
-
|
|
39
|
+
cdataPropName: false | string;
|
|
40
|
+
commentPropName: false | string;
|
|
37
41
|
format: boolean;
|
|
38
42
|
indentBy: string;
|
|
39
43
|
arrayNodeName: string;
|
|
40
44
|
suppressEmptyNode: boolean;
|
|
41
45
|
preserveOrder: boolean;
|
|
46
|
+
unpairedTags: string[];
|
|
42
47
|
tagValueProcessor: (name: string, value: string) => string;
|
|
43
48
|
attributeValueProcessor: (name: string, value: string) => string;
|
|
44
49
|
};
|
|
@@ -57,14 +62,12 @@ type ValidationError = {
|
|
|
57
62
|
|
|
58
63
|
export class XMLParser {
|
|
59
64
|
constructor(options?: X2jOptionsOptional);
|
|
60
|
-
parse(xmlData: string | Buffer ,validationOptions?: validationOptionsOptional | boolean);
|
|
65
|
+
parse(xmlData: string | Buffer ,validationOptions?: validationOptionsOptional | boolean): any;
|
|
61
66
|
}
|
|
62
67
|
|
|
63
|
-
export
|
|
64
|
-
xmlData: string,
|
|
65
|
-
|
|
66
|
-
): true | ValidationError;
|
|
67
|
-
|
|
68
|
+
export class XMLValidator{
|
|
69
|
+
static validate( xmlData: string, options?: validationOptionsOptional): true | ValidationError;
|
|
70
|
+
}
|
|
68
71
|
export class XMLBuilder {
|
|
69
72
|
constructor(options: XmlBuilderOptionsOptional);
|
|
70
73
|
parse(options: any): any;
|
package/src/validator.js
CHANGED
|
@@ -4,9 +4,13 @@ const util = require('./util');
|
|
|
4
4
|
|
|
5
5
|
const defaultOptions = {
|
|
6
6
|
allowBooleanAttributes: false, //A tag can have attributes without any value
|
|
7
|
+
unpairedTags: []
|
|
7
8
|
};
|
|
8
9
|
|
|
9
|
-
const props = [
|
|
10
|
+
const props = [
|
|
11
|
+
'allowBooleanAttributes',
|
|
12
|
+
'unpairedTags'
|
|
13
|
+
];
|
|
10
14
|
|
|
11
15
|
//const tagsPattern = new RegExp("<\\/?([\\w:\\-_\.]+)\\s*\/?>","g");
|
|
12
16
|
exports.validate = function (xmlData, options) {
|
|
@@ -130,6 +134,8 @@ exports.validate = function (xmlData, options) {
|
|
|
130
134
|
//if the root level has been reached before ...
|
|
131
135
|
if (reachedRoot === true) {
|
|
132
136
|
return getErrorObject('InvalidXml', 'Multiple possible root nodes found.', getLineNumberForPosition(xmlData, i));
|
|
137
|
+
} else if(options.unpairedTags.indexOf(tagName) !== -1){
|
|
138
|
+
//don't push into stack
|
|
133
139
|
} else {
|
|
134
140
|
tags.push({tagName, tagStartPos});
|
|
135
141
|
}
|
|
@@ -8,7 +8,7 @@ const defaultOptions = {
|
|
|
8
8
|
attributesGroupName: false,
|
|
9
9
|
textNodeName: '#text',
|
|
10
10
|
ignoreAttributes: true,
|
|
11
|
-
|
|
11
|
+
cdataPropName: false,
|
|
12
12
|
format: false,
|
|
13
13
|
indentBy: ' ',
|
|
14
14
|
suppressEmptyNode: false,
|
|
@@ -18,7 +18,9 @@ const defaultOptions = {
|
|
|
18
18
|
attributeValueProcessor: function(attrName, a) {
|
|
19
19
|
return a;
|
|
20
20
|
},
|
|
21
|
-
preserveOrder: false
|
|
21
|
+
preserveOrder: false,
|
|
22
|
+
commentPropName: false,
|
|
23
|
+
unpairedTags: [],
|
|
22
24
|
};
|
|
23
25
|
|
|
24
26
|
const props = [
|
|
@@ -26,7 +28,7 @@ const props = [
|
|
|
26
28
|
'attributesGroupName',
|
|
27
29
|
'textNodeName',
|
|
28
30
|
'ignoreAttributes',
|
|
29
|
-
'
|
|
31
|
+
'cdataPropName',
|
|
30
32
|
'format',
|
|
31
33
|
'indentBy',
|
|
32
34
|
'suppressEmptyNode',
|
|
@@ -34,6 +36,8 @@ const props = [
|
|
|
34
36
|
'attributeValueProcessor',
|
|
35
37
|
'arrayNodeName', //when array as root
|
|
36
38
|
'preserveOrder',
|
|
39
|
+
"commentPropName",
|
|
40
|
+
"unpairedTags",
|
|
37
41
|
// 'rootNodeName', //when jsObject have multiple properties on root level
|
|
38
42
|
];
|
|
39
43
|
|
|
@@ -206,7 +210,9 @@ function buildTextValNode(val, key, attrStr, level) {
|
|
|
206
210
|
}
|
|
207
211
|
|
|
208
212
|
function buildEmptyTextNode(val, key, attrStr, level) {
|
|
209
|
-
if
|
|
213
|
+
if( val === '' && this.options.unpairedTags.indexOf(key) !== -1){
|
|
214
|
+
return this.indentate(level) + '<' + key + attrStr + this.tagEndChar;
|
|
215
|
+
}else if (val !== '') {
|
|
210
216
|
return this.buildTextValNode(val, key, attrStr, level);
|
|
211
217
|
} else {
|
|
212
218
|
return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
|
|
@@ -225,8 +231,4 @@ function isAttribute(name /*, options*/) {
|
|
|
225
231
|
}
|
|
226
232
|
}
|
|
227
233
|
|
|
228
|
-
//formatting
|
|
229
|
-
//indentation
|
|
230
|
-
//\n after each closing or self closing tag
|
|
231
|
-
|
|
232
234
|
module.exports = Builder;
|
|
@@ -1,7 +1,13 @@
|
|
|
1
1
|
const {EOL} = require('os');
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/**
|
|
4
|
+
*
|
|
5
|
+
* @param {array} jArray
|
|
6
|
+
* @param {any} options
|
|
7
|
+
* @returns
|
|
8
|
+
*/
|
|
9
|
+
function toXml(jArray, options){
|
|
10
|
+
return arrToStr( jArray, options, 0);
|
|
5
11
|
}
|
|
6
12
|
|
|
7
13
|
function arrToStr(arr, options, level){
|
|
@@ -19,15 +25,22 @@ function arrToStr(arr, options, level){
|
|
|
19
25
|
if(tagName === options.textNodeName){
|
|
20
26
|
xmlStr += indentation + options.tagValueProcessor( tagName, tagObj[tagName]);
|
|
21
27
|
continue;
|
|
22
|
-
}else if( tagName === options.
|
|
28
|
+
}else if( tagName === options.cdataPropName){
|
|
23
29
|
xmlStr += indentation + `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
|
|
24
30
|
continue;
|
|
31
|
+
}else if( tagName === options.commentPropName){
|
|
32
|
+
xmlStr += indentation + `<!--${tagObj[tagName][0][options.textNodeName]}-->`;
|
|
33
|
+
continue;
|
|
25
34
|
}
|
|
26
35
|
const attStr = attr_to_str(tagObj.attributes, options);
|
|
27
36
|
let tagStart = indentation + `<${tagName}${attStr}`;
|
|
28
37
|
let tagValue = arrToStr(tagObj[tagName], options, level + 1);
|
|
29
38
|
if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
|
|
30
|
-
|
|
39
|
+
if(options.unpairedTags.indexOf(tagName) !== -1){
|
|
40
|
+
xmlStr += tagStart + ">";
|
|
41
|
+
}else{
|
|
42
|
+
xmlStr += tagStart + "/>";
|
|
43
|
+
}
|
|
31
44
|
}else{
|
|
32
45
|
//TODO: node with only text value should not parse the text value in next line
|
|
33
46
|
xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>` ;
|
|
@@ -11,7 +11,7 @@ const defaultOptions = {
|
|
|
11
11
|
parseTagValue: true,
|
|
12
12
|
parseAttributeValue: false,
|
|
13
13
|
trimValues: true, //Trim string values of tag and attributes
|
|
14
|
-
|
|
14
|
+
cdataPropName: false,
|
|
15
15
|
numberParseOptions: {
|
|
16
16
|
hex: true,
|
|
17
17
|
leadingZeros: true
|
|
@@ -24,7 +24,9 @@ const defaultOptions = {
|
|
|
24
24
|
},
|
|
25
25
|
stopNodes: [], //nested tags will not be parsed even for errors
|
|
26
26
|
alwaysCreateTextNode: false,
|
|
27
|
-
isArray: () => false
|
|
27
|
+
isArray: () => false,
|
|
28
|
+
commentPropName: false,
|
|
29
|
+
unpairedTags: [],
|
|
28
30
|
};
|
|
29
31
|
|
|
30
32
|
const props = [
|
|
@@ -38,13 +40,15 @@ const props = [
|
|
|
38
40
|
'parseTagValue',
|
|
39
41
|
'parseAttributeValue',
|
|
40
42
|
'trimValues',
|
|
41
|
-
'
|
|
43
|
+
'cdataPropName',
|
|
42
44
|
'tagValueProcessor',
|
|
43
45
|
'attributeValueProcessor',
|
|
44
46
|
'numberParseOptions',
|
|
45
47
|
'stopNodes',
|
|
46
48
|
'alwaysCreateTextNode',
|
|
47
49
|
'isArray',
|
|
50
|
+
'commentPropName',
|
|
51
|
+
'unpairedTags',
|
|
48
52
|
];
|
|
49
53
|
|
|
50
54
|
const util = require('../util');
|
|
@@ -141,7 +141,7 @@ function buildAttributesMap(attrStr, jPath, options) {
|
|
|
141
141
|
}
|
|
142
142
|
|
|
143
143
|
const parseToOrderedJsObj = function(xmlData, options) {
|
|
144
|
-
xmlData = xmlData.replace(/\r\n?/g, "\n");
|
|
144
|
+
xmlData = xmlData.replace(/\r\n?/g, "\n"); //TODO: remove this line
|
|
145
145
|
const xmlObj = new xmlNode('!xml');
|
|
146
146
|
let currentNode = xmlObj;
|
|
147
147
|
let textData = "";
|
|
@@ -170,7 +170,7 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
170
170
|
, currentNode.tagname
|
|
171
171
|
, jPath
|
|
172
172
|
,false
|
|
173
|
-
, currentNode.
|
|
173
|
+
, currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
|
|
174
174
|
, Object.keys(currentNode.child).length === 0);
|
|
175
175
|
if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
|
|
176
176
|
textData = "";
|
|
@@ -190,7 +190,26 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
190
190
|
} else if( xmlData[i+1] === '?') {
|
|
191
191
|
i = findClosingIndex(xmlData, "?>", i, "Pi Tag is not closed.")
|
|
192
192
|
} else if(xmlData.substr(i + 1, 3) === '!--') {
|
|
193
|
-
|
|
193
|
+
const endIndex = findClosingIndex(xmlData, "-->", i, "Comment is not closed.")
|
|
194
|
+
if(options.commentPropName){
|
|
195
|
+
const comment = xmlData.substring(i + 4, endIndex - 2);
|
|
196
|
+
|
|
197
|
+
//TODO: remove repeated code
|
|
198
|
+
if(textData){ //store previously collected data as textNode
|
|
199
|
+
textData = parseValue(textData
|
|
200
|
+
, options
|
|
201
|
+
, currentNode.tagname
|
|
202
|
+
, jPath
|
|
203
|
+
,false
|
|
204
|
+
, currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
|
|
205
|
+
, Object.keys(currentNode.child).length === 0);
|
|
206
|
+
|
|
207
|
+
if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
|
|
208
|
+
textData = "";
|
|
209
|
+
}
|
|
210
|
+
currentNode.add(options.commentPropName, [ { [options.textNodeName] : comment } ]);
|
|
211
|
+
}
|
|
212
|
+
i = endIndex;
|
|
194
213
|
} else if( xmlData.substr(i + 1, 2) === '!D') {
|
|
195
214
|
const closeIndex = findClosingIndex(xmlData, ">", i, "DOCTYPE is not closed.")
|
|
196
215
|
const tagExp = xmlData.substring(i, closeIndex);
|
|
@@ -209,7 +228,7 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
209
228
|
, currentNode.tagname
|
|
210
229
|
, jPath
|
|
211
230
|
,false
|
|
212
|
-
, currentNode.
|
|
231
|
+
, currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
|
|
213
232
|
, Object.keys(currentNode.child).length === 0);
|
|
214
233
|
|
|
215
234
|
if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
|
|
@@ -217,10 +236,10 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
217
236
|
}
|
|
218
237
|
|
|
219
238
|
//cdata should be set even if it is 0 length string
|
|
220
|
-
if(options.
|
|
221
|
-
let val = parseValue(tagExp, options, options.
|
|
239
|
+
if(options.cdataPropName){
|
|
240
|
+
let val = parseValue(tagExp, options, options.cdataPropName, jPath + "." + options.cdataPropName, true, false, true);
|
|
222
241
|
if(!val) val = "";
|
|
223
|
-
currentNode.add(options.
|
|
242
|
+
currentNode.add(options.cdataPropName, [ { [options.textNodeName] : val } ]);
|
|
224
243
|
}else{
|
|
225
244
|
let val = parseValue(tagExp, options, currentNode.tagname, jPath, true, false, true);
|
|
226
245
|
if(!val) val = "";
|
|
@@ -257,7 +276,7 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
257
276
|
, currentNode.tagname
|
|
258
277
|
, jPath
|
|
259
278
|
, false
|
|
260
|
-
, currentNode.
|
|
279
|
+
, currentNode.attributes ? Object.keys(currentNode.attributes).length !== 0 : false
|
|
261
280
|
, false);
|
|
262
281
|
if(textData !== undefined && textData !== "") currentNode.add(options.textNodeName, textData);
|
|
263
282
|
textData = "";
|
|
@@ -268,7 +287,8 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
268
287
|
jPath += jPath ? "." + tagName : tagName;
|
|
269
288
|
}
|
|
270
289
|
|
|
271
|
-
|
|
290
|
+
//selfClosing tag
|
|
291
|
+
if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
|
|
272
292
|
|
|
273
293
|
if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
|
|
274
294
|
tagName = tagName.substr(0, tagName.length - 1);
|
|
@@ -279,12 +299,26 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
279
299
|
|
|
280
300
|
const childNode = new xmlNode(tagName);
|
|
281
301
|
if(tagName !== tagExp && shouldBuildAttributesMap){
|
|
282
|
-
childNode.
|
|
302
|
+
childNode.attributes = buildAttributesMap(tagExp, jPath , options);
|
|
283
303
|
}
|
|
284
304
|
jPath = jPath.substr(0, jPath.lastIndexOf("."));
|
|
285
305
|
// tagsNodeStack.push(currentNode);
|
|
286
306
|
currentNode.addChild(childNode);
|
|
287
|
-
}
|
|
307
|
+
}
|
|
308
|
+
//boolean tags
|
|
309
|
+
else if(options.unpairedTags.indexOf(tagName) !== -1){
|
|
310
|
+
// tagExp = tagExp.substr(0, tagExp.length - 1);
|
|
311
|
+
|
|
312
|
+
const childNode = new xmlNode(tagName);
|
|
313
|
+
if(tagName !== tagExp && shouldBuildAttributesMap){
|
|
314
|
+
childNode.attributes = buildAttributesMap(tagExp, jPath , options);
|
|
315
|
+
}
|
|
316
|
+
jPath = jPath.substr(0, jPath.lastIndexOf("."));
|
|
317
|
+
// tagsNodeStack.push(currentNode);
|
|
318
|
+
currentNode.addChild(childNode);
|
|
319
|
+
}
|
|
320
|
+
//opening tag
|
|
321
|
+
else{
|
|
288
322
|
|
|
289
323
|
const childNode = new xmlNode( tagName);
|
|
290
324
|
tagsNodeStack.push(currentNode);
|
|
@@ -292,7 +326,7 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
292
326
|
childNode.startIndex=closeIndex; //for further processing
|
|
293
327
|
|
|
294
328
|
if(tagName !== tagExp && shouldBuildAttributesMap){
|
|
295
|
-
childNode.
|
|
329
|
+
childNode.attributes = buildAttributesMap(tagExp, jPath, options);
|
|
296
330
|
}
|
|
297
331
|
currentNode.addChild(childNode);
|
|
298
332
|
currentNode = childNode;
|
|
@@ -304,7 +338,7 @@ const parseToOrderedJsObj = function(xmlData, options) {
|
|
|
304
338
|
textData += xmlData[i];
|
|
305
339
|
}
|
|
306
340
|
}
|
|
307
|
-
return xmlObj.child
|
|
341
|
+
return xmlObj.child;
|
|
308
342
|
}
|
|
309
343
|
|
|
310
344
|
//TODO: use jPath to simplify the logic
|
package/src/xmlparser/xmlNode.js
CHANGED
|
@@ -4,15 +4,15 @@ class XmlNode{
|
|
|
4
4
|
constructor(tagname) {
|
|
5
5
|
this.tagname = tagname;
|
|
6
6
|
this.child = []; //nested tags, text, cdata, comments in order
|
|
7
|
-
this.
|
|
7
|
+
this.attributes = {}; //attributes map
|
|
8
8
|
}
|
|
9
9
|
add(key,val){
|
|
10
10
|
// this.child.push( {name : key, val: val, isCdata: isCdata });
|
|
11
11
|
this.child.push( {[key]: val });
|
|
12
12
|
}
|
|
13
13
|
addChild(node) {
|
|
14
|
-
if(node.
|
|
15
|
-
this.child.push( { [node.tagname]: node.child, attributes: node.
|
|
14
|
+
if(node.attributes && Object.keys(node.attributes).length > 0){
|
|
15
|
+
this.child.push( { [node.tagname]: node.child, attributes: node.attributes });
|
|
16
16
|
}else{
|
|
17
17
|
this.child.push( { [node.tagname]: node.child });
|
|
18
18
|
}
|