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 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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "4.0.0-beta.6",
3
+ "version": "4.0.0-beta.7",
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/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 = util.buildOptions(options, defaultOptions, props);
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 = buildOptions(options, defaultOptions, props);
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( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
51
- if(options.unpairedTags.indexOf(tagName) !== -1){
52
- xmlStr += tagStart + ">";
53
- }else{
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 util.buildOptions(options, defaultOptions, props);
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 result = readTagExp(xmlData,i, false, "?>");
212
- if(!result) throw new Error("Pi Tag is not closed.");
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
- const childNode = new xmlNode(tagName);
233
- childNode.add(this.options.textNodeName, "");
234
-
235
- if(tagName !== tagExp && attrExpPresent){
236
- childNode[":@"] = this.buildAttributesMap(tagExp, jPath);
237
- }
238
- currentNode.addChild(childNode);
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
- //TODO: remove repeated code
247
- if(textData){ //store previously collected data as textNode
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
- if(textData){ //store previously collected data as textNode
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
  *