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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "4.0.0-beta.0",
3
+ "version": "4.0.0-beta.2",
4
4
  "description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
5
5
  "main": "./src/fxp.js",
6
6
  "scripts": {
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
- cdataTagName: false | string;
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
- cdataTagName: false | string;
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 function XMLValidator(
64
- xmlData: string,
65
- options?: validationOptionsOptional
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 = ['allowBooleanAttributes'];
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
- cdataTagName: false,
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
- 'cdataTagName',
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 (val !== '') {
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
- function toXml(jObj, options){
4
- return arrToStr( [jObj], options, 0);
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.cdataTagName){
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
- xmlStr += tagStart + "/>";
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
- cdataTagName: false,
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
- 'cdataTagName',
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.attrsMap ? Object.keys(currentNode.attrsMap).length !== 0 : false
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
- i = findClosingIndex(xmlData, "-->", i, "Comment is not closed.")
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.attrsMap ? Object.keys(currentNode.attrsMap).length !== 0 : false
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.cdataTagName){
221
- let val = parseValue(tagExp, options, options.cdataTagName, jPath + "." + options.cdataTagName, true, false, true);
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.cdataTagName, [ { [options.textNodeName] : val } ]);
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.attrsMap ? Object.keys(currentNode.attrsMap).length !== 0 : false
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
- if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){//selfClosing tag
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.attrsMap = buildAttributesMap(tagExp, jPath , options);
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
- }else{//opening tag
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.attrsMap = buildAttributesMap(tagExp, jPath, options);
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[0];
341
+ return xmlObj.child;
308
342
  }
309
343
 
310
344
  //TODO: use jPath to simplify the logic
@@ -1,7 +1,13 @@
1
1
  'use strict';
2
2
 
3
+ /**
4
+ *
5
+ * @param {array} node
6
+ * @param {any} options
7
+ * @returns
8
+ */
3
9
  function prettify(node, options){
4
- return compress( [node], options);
10
+ return compress( node, options);
5
11
  }
6
12
 
7
13
  /**
@@ -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.attrsMap = {}; //attributes map
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.attrsMap && Object.keys(node.attrsMap).length > 0){
15
- this.child.push( { [node.tagname]: node.child, attributes: node.attrsMap });
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
  }