fast-xml-parser 4.0.1 → 4.0.4

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,18 @@
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.4 / 2022-03-03**
4
+ * fix #435: should skip unpaired and self-closing nodes when set as stopnodes
5
+
6
+ **4.0.3 / 2022-02-15**
7
+ * fix: ReferenceError when Bundled with Strict (#431) (By [Andreas Heissenberger](https://github.com/aheissenberger))
8
+
9
+
10
+ **4.0.2 / 2022-02-04**
11
+ * builder supports `suppressUnpairedNode`
12
+ * parser supports `ignoreDeclaration` and `ignorePiTags`
13
+ * fix: when comment is parsed as text value if given as `<!--> ...` #423
14
+ * builder supports decoding `&`
15
+
3
16
  **4.0.1 / 2022-01-08**
4
17
  * fix builder for pi tag
5
18
  * fix: support suppressBooleanAttrs by builder
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "4.0.1",
3
+ "version": "4.0.4",
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
@@ -20,6 +20,8 @@ type X2jOptions = {
20
20
  isArray: (tagName: string, jPath: string, isLeafNode: boolean, isAttribute: boolean) => boolean;
21
21
  processEntities: boolean;
22
22
  htmlEntities: boolean;
23
+ ignoreDeclaration: boolean;
24
+ ignorePiTags: boolean;
23
25
  };
24
26
  type strnumOptions = {
25
27
  hex: boolean;
@@ -44,6 +46,7 @@ type XmlBuilderOptions = {
44
46
  indentBy: string;
45
47
  arrayNodeName: string;
46
48
  suppressEmptyNode: boolean;
49
+ suppressUnpairedNode: boolean;
47
50
  suppressBooleanAttributes: boolean;
48
51
  preserveOrder: boolean;
49
52
  unpairedTags: string[];
@@ -11,6 +11,7 @@ const defaultOptions = {
11
11
  format: false,
12
12
  indentBy: ' ',
13
13
  suppressEmptyNode: false,
14
+ suppressUnpairedNode: true,
14
15
  suppressBooleanAttributes: true,
15
16
  tagValueProcessor: function(key, a) {
16
17
  return a;
@@ -21,12 +22,13 @@ const defaultOptions = {
21
22
  preserveOrder: false,
22
23
  commentPropName: false,
23
24
  unpairedTags: [],
24
- entities: {
25
- ">" : { regex: new RegExp(">", "g"), val: "&gt;" },
26
- "<" : { regex: new RegExp("<", "g"), val: "&lt;" },
27
- "sQuot" : { regex: new RegExp("\'", "g"), val: "&apos;" },
28
- "dQuot" : { regex: new RegExp("\"", "g"), val: "&quot;" }
29
- },
25
+ entities: [
26
+ { regex: new RegExp("&", "g"), val: "&amp;" },//it must be on top
27
+ { regex: new RegExp(">", "g"), val: "&gt;" },
28
+ { regex: new RegExp("<", "g"), val: "&lt;" },
29
+ { regex: new RegExp("\'", "g"), val: "&apos;" },
30
+ { regex: new RegExp("\"", "g"), val: "&quot;" }
31
+ ],
30
32
  processEntities: true,
31
33
  stopNodes: []
32
34
  };
@@ -192,19 +194,26 @@ function buildEmptyObjNode(val, key, attrStr, level) {
192
194
  }
193
195
 
194
196
  function buildTextValNode(val, key, attrStr, level) {
195
- let textValue = this.options.tagValueProcessor(key, val);
196
- textValue = this.replaceEntitiesValue(textValue);
197
-
198
- return (
199
- this.indentate(level) + '<' + key + attrStr + '>' +
200
- textValue +
201
- '</' + key + this.tagEndChar );
197
+ const textValue = this.replaceEntitiesValue(val);
198
+
199
+ if( textValue === '' && this.options.unpairedTags.indexOf(key) !== -1){ //unpaired
200
+ if(this.options.suppressUnpairedNode){
201
+ return this.indentate(level) + '<' + key + this.tagEndChar;
202
+ }else{
203
+ return this.indentate(level) + '<' + key + "/" + this.tagEndChar;
204
+ }
205
+ }else{
206
+ return (
207
+ this.indentate(level) + '<' + key + attrStr + '>' +
208
+ textValue +
209
+ '</' + key + this.tagEndChar );
210
+ }
202
211
  }
203
212
 
204
213
  function replaceEntitiesValue(textValue){
205
214
  if(textValue && textValue.length > 0 && this.options.processEntities){
206
- for (const entityName in this.options.entities) {
207
- const entity = this.options.entities[entityName];
215
+ for (let i=0; i<this.options.entities.length; i++) {
216
+ const entity = this.options.entities[i];
208
217
  textValue = textValue.replace(entity.regex, entity.val);
209
218
  }
210
219
  }
@@ -212,13 +221,17 @@ function replaceEntitiesValue(textValue){
212
221
  }
213
222
 
214
223
  function buildEmptyTextNode(val, key, attrStr, level) {
215
- if( val === '' && this.options.unpairedTags.indexOf(key) !== -1){
216
- return this.indentate(level) + '<' + key + attrStr + this.tagEndChar;
217
- }else if (val !== '') {
224
+ if( val === '' && this.options.unpairedTags.indexOf(key) !== -1){ //unpaired
225
+ if(this.options.suppressUnpairedNode){
226
+ return this.indentate(level) + '<' + key + this.tagEndChar;
227
+ }else{
228
+ return this.indentate(level) + '<' + key + "/" + this.tagEndChar;
229
+ }
230
+ }else if (val !== '') { //empty
218
231
  return this.buildTextValNode(val, key, attrStr, level);
219
232
  } else {
220
- if(key[0] === "?") return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar;
221
- else return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
233
+ if(key[0] === "?") return this.indentate(level) + '<' + key + attrStr+ '?' + this.tagEndChar; //PI tag
234
+ else return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar; //normal
222
235
  }
223
236
  }
224
237
 
@@ -48,7 +48,8 @@ function arrToStr(arr, options, jPath, level){
48
48
  let tagStart = indentation + `<${tagName}${attStr}`;
49
49
  let tagValue = arrToStr(tagObj[tagName], options, newJPath, level + 1);
50
50
  if(options.unpairedTags.indexOf(tagName) !== -1){
51
- xmlStr += tagStart + ">";
51
+ if(options.suppressUnpairedNode) xmlStr += tagStart + ">";
52
+ else xmlStr += tagStart + "/>";
52
53
  }else if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
53
54
  xmlStr += tagStart + "/>";
54
55
  }else{
@@ -71,7 +72,7 @@ function propName(obj){
71
72
  function attr_to_str(attrMap, options){
72
73
  let attrStr = "";
73
74
  if(attrMap && !options.ignoreAttributes){
74
- for( attr in attrMap){
75
+ for (let attr in attrMap){
75
76
  let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
76
77
  attrVal = replaceEntitiesValue(attrVal, options);
77
78
  if(attrVal === true && options.suppressBooleanAttributes){
@@ -95,8 +96,8 @@ function isStopNode(jPath, options){
95
96
 
96
97
  function replaceEntitiesValue(textValue, options){
97
98
  if(textValue && textValue.length > 0 && options.processEntities){
98
- for (const entityName in options.entities) {
99
- const entity = options.entities[entityName];
99
+ for (let i=0; i< options.entities.length; i++) {
100
+ const entity = options.entities[i];
100
101
  textValue = textValue.replace(entity.regex, entity.val);
101
102
  }
102
103
  }
@@ -29,6 +29,8 @@ const defaultOptions = {
29
29
  unpairedTags: [],
30
30
  processEntities: true,
31
31
  htmlEntities: false,
32
+ ignoreDeclaration: false,
33
+ ignorePiTags: false
32
34
  };
33
35
 
34
36
  const buildOptions = function(options) {
@@ -203,21 +203,29 @@ const parseXml = function(xmlData) {
203
203
  textData = "";
204
204
  i = closeIndex;
205
205
  } else if( xmlData[i+1] === '?') {
206
+
206
207
  let tagData = readTagExp(xmlData,i, false, "?>");
207
208
  if(!tagData) throw new Error("Pi Tag is not closed.");
209
+
208
210
  textData = this.saveTextToParentTag(textData, currentNode, jPath);
211
+ if( (this.options.ignoreDeclaration && tagData.tagName === "?xml") || this.options.ignorePiTags){
212
+
213
+ }else{
214
+
215
+ const childNode = new xmlNode(tagData.tagName);
216
+ childNode.add(this.options.textNodeName, "");
217
+
218
+ if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
219
+ childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath);
220
+ }
221
+ currentNode.addChild(childNode);
209
222
 
210
- const childNode = new xmlNode(tagData.tagName);
211
- childNode.add(this.options.textNodeName, "");
212
-
213
- if(tagData.tagName !== tagData.tagExp && tagData.attrExpPresent){
214
- childNode[":@"] = this.buildAttributesMap(tagData.tagExp, jPath);
215
223
  }
216
- currentNode.addChild(childNode);
224
+
217
225
 
218
226
  i = tagData.closeIndex + 1;
219
227
  } else if(xmlData.substr(i + 1, 3) === '!--') {
220
- const endIndex = findClosingIndex(xmlData, "-->", i, "Comment is not closed.")
228
+ const endIndex = findClosingIndex(xmlData, "-->", i+4, "Comment is not closed.")
221
229
  if(this.options.commentPropName){
222
230
  const comment = xmlData.substring(i + 4, endIndex - 2);
223
231
 
@@ -277,9 +285,13 @@ const parseXml = function(xmlData) {
277
285
  if (this.isItStopNode(this.options.stopNodes, jPath, tagName)) { //TODO: namespace
278
286
  let tagContent = "";
279
287
  //self-closing tag
280
- if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){}
288
+ if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
289
+ i = result.closeIndex;
290
+ }
281
291
  //boolean tag
282
- else if(this.options.unpairedTags.indexOf(tagName) !== -1){}
292
+ else if(this.options.unpairedTags.indexOf(tagName) !== -1){
293
+ i = result.closeIndex;
294
+ }
283
295
  //normal tag
284
296
  else{
285
297
  //read until closing tag is found
@@ -304,7 +316,6 @@ const parseXml = function(xmlData) {
304
316
  }else{
305
317
  //selfClosing tag
306
318
  if(tagExp.length > 0 && tagExp.lastIndexOf("/") === tagExp.length - 1){
307
-
308
319
  if(tagName[tagName.length - 1] === "/"){ //remove trailing '/'
309
320
  tagName = tagName.substr(0, tagName.length - 1);
310
321
  tagExp = tagName;