fast-xml-parser 4.0.11 → 4.0.13

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.13 / 2023-01-07**
4
+ * preserveorder formatting (By [mdeknowis](https://github.com/mdeknowis))
5
+ * support `transformAttributeName` (By [Erik Rothoff Andersson](https://github.com/erkie))
6
+
7
+ **4.0.12 / 2022-11-19**
8
+ * fix typescript
9
+
3
10
  **4.0.11 / 2022-10-05**
4
11
  * fix #501: parse for entities only once
5
12
 
package/README.md CHANGED
@@ -13,16 +13,32 @@ Validate XML, Parse XML to JS Object, or Build XML from JS Object without C/C++
13
13
 
14
14
  > Looking for maintainers
15
15
 
16
+ Support this project by becoming a **Sponsor**.
17
+
18
+ Click on Sponsor button above or
16
19
  <a href="https://opencollective.com/fast-xml-parser/donate" target="_blank">
17
20
  <img src="https://opencollective.com/fast-xml-parser/donate/button@2x.png?color=blue" width=200 />
18
21
  </a>
19
22
  <a href="https://paypal.me/naturalintelligence"> <img src="static/img/support_paypal.svg" alt="Stubmatic donate button" width="200"/></a>
20
23
 
21
- Check [ThankYouBackers](https://github.com/NaturalIntelligence/ThankYouBackers) for our contributors
24
+
25
+ ### Sponsors
26
+
27
+ <a href="https://github.com/getsentry" target="_blank"><img src="https://user-images.githubusercontent.com/7692328/204701653-ce369b29-6fdb-48f2-9848-593d04628a2b.png" width="60px"></a>
28
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/0/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/0/avatar.svg"></a>
29
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/1/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/1/avatar.svg"></a>
30
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/2/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/2/avatar.svg"></a>
31
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/3/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/3/avatar.svg"></a>
32
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/4/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/4/avatar.svg"></a>
33
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/5/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/5/avatar.svg"></a>
34
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/6/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/6/avatar.svg"></a>
35
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/7/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/7/avatar.svg"></a>
36
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/8/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/8/avatar.svg"></a>
37
+ <a href="https://opencollective.com/fast-xml-parser/sponsor/9/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/9/avatar.svg"></a>
22
38
 
23
39
 
40
+ Check [ThankYouBackers](https://github.com/NaturalIntelligence/ThankYouBackers) for our supporters
24
41
 
25
- [![](static/img/ni_ads_ads.gif)](https://github.com/NaturalIntelligence/ads/)
26
42
  ## Users
27
43
 
28
44
  <a href="https://github.com/renovatebot/renovate" title="renovate" ><img src="https://avatars1.githubusercontent.com/u/38656520" width="60px" ></a>
@@ -36,12 +52,14 @@ Check [ThankYouBackers](https://github.com/NaturalIntelligence/ThankYouBackers)
36
52
  <a href="https://github.com/aws" title="AWS SDK" > <img src="https://avatars.githubusercontent.com/u/2232217" width="60px" ></a>
37
53
  <a href="http://www.fda.gov/" title="Food and Drug Administration " > <img src="https://avatars2.githubusercontent.com/u/6471964" width="60px" ></a>
38
54
  <a href="http://www.magento.com/" title="Magento" > <img src="https://avatars2.githubusercontent.com/u/168457" width="60px" ></a>
55
+ <a href="https://github.com/SAP" title="SAP" > <img src="https://user-images.githubusercontent.com/7692328/204835214-d9d25b58-e3df-408d-87a3-c7d36b578ee4.png" width="60px" ></a>
56
+ <a href="https://github.com/postmanlabs" title="postman" > <img src="https://user-images.githubusercontent.com/7692328/204835529-e9e290ad-696a-49ad-9d34-08e955704715.png" width="60px" ></a>
39
57
 
40
58
  Check the list of all known users [here](./USERs.md);
41
59
 
42
60
  <small>The list of users is collected either from the list published by Github, cummunicated directly through mails/chat , or from other resources. If you feel that your name in the above list is incorrectly published or you're not the user of this library anymore then you can inform us to remove it. We'll do the necessary changes ASAP.</small>
43
61
 
44
- ### Main Features
62
+ ## Main Features
45
63
 
46
64
  <img align="right" src="static/img/fxp_logo.png" width="180px" alt="FXP logo"/>
47
65
 
@@ -82,7 +100,7 @@ $ fxparser some.xml
82
100
 
83
101
  In a node js project
84
102
  ```js
85
- const { XMLParser, XMLBuilder, XMLValidator} = require("../src/fxp");
103
+ const { XMLParser, XMLBuilder, XMLValidator} = require("fast-xml-parser");
86
104
 
87
105
  const parser = new XMLParser();
88
106
  let jObj = parser.parse(XMLdata);
@@ -128,16 +146,22 @@ Check lib folder for different browser bundles
128
146
  ### XML Parser
129
147
 
130
148
  ![](./docs/imgs/XMLParser_v4.png)
149
+ * Y-axis: requests per second
150
+ * X-axis: File size
131
151
 
132
152
  **Large files**
133
153
  ![](./docs/imgs/XMLParser_large_v4.png)
134
-
154
+ * Y-axis: requests per second
155
+ * X-axis: File size
135
156
  ### XML Builder
136
157
 
137
158
  ![](./docs/imgs/XMLBuilder_v4.png)
159
+ * Y-axis: requests per second
138
160
 
139
161
  <small>negative means error</small>
140
162
 
163
+ [![](static/img/ni_ads_ads.gif)](https://github.com/NaturalIntelligence/ads/)
164
+
141
165
  ## Our other projects and research you must try
142
166
 
143
167
  * **[BigBit standard](https://github.com/amitguptagwl/bigbit)** :
@@ -172,20 +196,6 @@ Thank you to all our backers! 🙏 [[Become a backer](https://opencollective.com
172
196
  <a href="https://opencollective.com/fast-xml-parser#backers" target="_blank"><img src="https://opencollective.com/fast-xml-parser/backers.svg?width=890"></a>
173
197
 
174
198
 
175
- ### Sponsors
176
-
177
- <small>[[Become a sponsor](https://opencollective.com/fast-xml-parser#sponsor)] Support this project by becoming a sponsor. Your logo will show up here with a link to your website. Please also share your detail so we can thankyou on SocialMedia.</small>
178
-
179
- <a href="https://opencollective.com/fast-xml-parser/sponsor/0/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/0/avatar.svg"></a>
180
- <a href="https://opencollective.com/fast-xml-parser/sponsor/1/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/1/avatar.svg"></a>
181
- <a href="https://opencollective.com/fast-xml-parser/sponsor/2/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/2/avatar.svg"></a>
182
- <a href="https://opencollective.com/fast-xml-parser/sponsor/3/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/3/avatar.svg"></a>
183
- <a href="https://opencollective.com/fast-xml-parser/sponsor/4/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/4/avatar.svg"></a>
184
- <a href="https://opencollective.com/fast-xml-parser/sponsor/5/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/5/avatar.svg"></a>
185
- <a href="https://opencollective.com/fast-xml-parser/sponsor/6/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/6/avatar.svg"></a>
186
- <a href="https://opencollective.com/fast-xml-parser/sponsor/7/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/7/avatar.svg"></a>
187
- <a href="https://opencollective.com/fast-xml-parser/sponsor/8/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/8/avatar.svg"></a>
188
- <a href="https://opencollective.com/fast-xml-parser/sponsor/9/website" target="_blank"><img src="https://opencollective.com/fast-xml-parser/sponsor/9/avatar.svg"></a>
189
199
 
190
200
  # License
191
201
  * MIT License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "4.0.11",
3
+ "version": "4.0.13",
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,7 +20,7 @@ Control how tag value should be parsed. Called only if tag value is not empty
20
20
  2. Same value to set parsed value if `parseTagValue: true`.
21
21
  */
22
22
  tagValueProcessor: (tagName: string, tagValue: string, jPath: string, hasAttributes: boolean, isLeafNode: boolean) => unknown;
23
- attributeValueProcessor: (attrName: string, attrValue: string, jPath: string) => string;
23
+ attributeValueProcessor: (attrName: string, attrValue: string, jPath: string) => unknown;
24
24
  numberParseOptions: strnumOptions;
25
25
  stopNodes: string[];
26
26
  unpairedTags: string[];
@@ -31,6 +31,7 @@ Control how tag value should be parsed. Called only if tag value is not empty
31
31
  ignoreDeclaration: boolean;
32
32
  ignorePiTags: boolean;
33
33
  transformTagName: ((tagName: string) => string) | false;
34
+ transformAttributeName: ((attributeName: string) => string) | false;
34
35
  };
35
36
  type strnumOptions = {
36
37
  hex: boolean;
@@ -60,8 +61,8 @@ type XmlBuilderOptions = {
60
61
  preserveOrder: boolean;
61
62
  unpairedTags: string[];
62
63
  stopNodes: string[];
63
- tagValueProcessor: (name: string, value: string) => string;
64
- attributeValueProcessor: (name: string, value: string) => string;
64
+ tagValueProcessor: (name: string, value: unknown) => string;
65
+ attributeValueProcessor: (name: string, value: unknown) => string;
65
66
  processEntities: boolean;
66
67
  };
67
68
  type XmlBuilderOptionsOptional = Partial<XmlBuilderOptions>;
@@ -31,7 +31,8 @@ const defaultOptions = {
31
31
  ],
32
32
  processEntities: true,
33
33
  stopNodes: [],
34
- transformTagName: false,
34
+ // transformTagName: false,
35
+ // transformAttributeName: false,
35
36
  };
36
37
 
37
38
  function Builder(options) {
@@ -6,104 +6,126 @@ const EOL = "\n";
6
6
  * @param {any} options
7
7
  * @returns
8
8
  */
9
- function toXml(jArray, options){
10
- return arrToStr( jArray, options, "", 0);
9
+ function toXml(jArray, options) {
10
+ let indentation = "";
11
+ if (options.format && options.indentBy.length > 0) {
12
+ indentation = EOL;
13
+ }
14
+ return arrToStr(jArray, options, "", indentation);
11
15
  }
12
16
 
13
- function arrToStr(arr, options, jPath, level){
17
+ function arrToStr(arr, options, jPath, indentation) {
14
18
  let xmlStr = "";
15
-
16
- let indentation = "";
17
- if(options.format && options.indentBy.length > 0){//TODO: this logic can be avoided for each call
18
- indentation = EOL + "" + options.indentBy.repeat(level);
19
- }
19
+ let isPreviousElementTag = false;
20
20
 
21
21
  for (let i = 0; i < arr.length; i++) {
22
22
  const tagObj = arr[i];
23
23
  const tagName = propName(tagObj);
24
24
  let newJPath = "";
25
- if(jPath.length === 0) newJPath = tagName
25
+ if (jPath.length === 0) newJPath = tagName
26
26
  else newJPath = `${jPath}.${tagName}`;
27
27
 
28
- if(tagName === options.textNodeName){
28
+ if (tagName === options.textNodeName) {
29
29
  let tagText = tagObj[tagName];
30
- if(!isStopNode(newJPath, options)){
31
- tagText = options.tagValueProcessor( tagName, tagText);
30
+ if (!isStopNode(newJPath, options)) {
31
+ tagText = options.tagValueProcessor(tagName, tagText);
32
32
  tagText = replaceEntitiesValue(tagText, options);
33
33
  }
34
- xmlStr += indentation + tagText;
34
+ if (isPreviousElementTag) {
35
+ xmlStr += indentation;
36
+ }
37
+ xmlStr += tagText;
38
+ isPreviousElementTag = false;
35
39
  continue;
36
- }else if( tagName === options.cdataPropName){
37
- xmlStr += indentation + `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
40
+ } else if (tagName === options.cdataPropName) {
41
+ if (isPreviousElementTag) {
42
+ xmlStr += indentation;
43
+ }
44
+ xmlStr += `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
45
+ isPreviousElementTag = false;
38
46
  continue;
39
- }else if( tagName === options.commentPropName){
47
+ } else if (tagName === options.commentPropName) {
40
48
  xmlStr += indentation + `<!--${tagObj[tagName][0][options.textNodeName]}-->`;
49
+ isPreviousElementTag = true;
41
50
  continue;
42
- }else if( tagName[0] === "?"){
51
+ } else if (tagName[0] === "?") {
43
52
  const attStr = attr_to_str(tagObj[":@"], options);
44
53
  const tempInd = tagName === "?xml" ? "" : indentation;
45
54
  let piTextNodeName = tagObj[tagName][0][options.textNodeName];
46
55
  piTextNodeName = piTextNodeName.length !== 0 ? " " + piTextNodeName : ""; //remove extra spacing
47
56
  xmlStr += tempInd + `<${tagName}${piTextNodeName}${attStr}?>`;
57
+ isPreviousElementTag = true;
48
58
  continue;
49
59
  }
60
+ let newIdentation = indentation;
61
+ if (newIdentation !== "") {
62
+ newIdentation += options.indentBy;
63
+ }
50
64
  const attStr = attr_to_str(tagObj[":@"], options);
51
- let tagStart = indentation + `<${tagName}${attStr}`;
52
- let tagValue = arrToStr(tagObj[tagName], options, newJPath, level + 1);
53
- if(options.unpairedTags.indexOf(tagName) !== -1){
54
- if(options.suppressUnpairedNode) xmlStr += tagStart + ">";
55
- else xmlStr += tagStart + "/>";
56
- }else if( (!tagValue || tagValue.length === 0) && options.suppressEmptyNode){
57
- xmlStr += tagStart + "/>";
58
- }else{
59
- //TODO: node with only text value should not parse the text value in next line
60
- xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>` ;
65
+ const tagStart = indentation + `<${tagName}${attStr}`;
66
+ const tagValue = arrToStr(tagObj[tagName], options, newJPath, newIdentation);
67
+ if (options.unpairedTags.indexOf(tagName) !== -1) {
68
+ if (options.suppressUnpairedNode) xmlStr += tagStart + ">";
69
+ else xmlStr += tagStart + "/>";
70
+ } else if ((!tagValue || tagValue.length === 0) && options.suppressEmptyNode) {
71
+ xmlStr += tagStart + "/>";
72
+ } else if (tagValue && tagValue.endsWith(">")) {
73
+ xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>`;
74
+ } else {
75
+ xmlStr += tagStart + ">";
76
+ if (tagValue && indentation !== "" && (tagValue.includes("/>") || tagValue.includes("</"))) {
77
+ xmlStr += indentation + options.indentBy + tagValue + indentation;
78
+ } else {
79
+ xmlStr += tagValue;
80
+ }
81
+ xmlStr += `</${tagName}>`;
61
82
  }
83
+ isPreviousElementTag = true;
62
84
  }
63
-
85
+
64
86
  return xmlStr;
65
87
  }
66
88
 
67
- function propName(obj){
89
+ function propName(obj) {
68
90
  const keys = Object.keys(obj);
69
91
  for (let i = 0; i < keys.length; i++) {
70
- const key = keys[i];
71
- if(key !== ":@") return key;
92
+ const key = keys[i];
93
+ if (key !== ":@") return key;
72
94
  }
73
- }
95
+ }
74
96
 
75
- function attr_to_str(attrMap, options){
97
+ function attr_to_str(attrMap, options) {
76
98
  let attrStr = "";
77
- if(attrMap && !options.ignoreAttributes){
78
- for (let attr in attrMap){
99
+ if (attrMap && !options.ignoreAttributes) {
100
+ for (let attr in attrMap) {
79
101
  let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
80
102
  attrVal = replaceEntitiesValue(attrVal, options);
81
- if(attrVal === true && options.suppressBooleanAttributes){
82
- attrStr+= ` ${attr.substr(options.attributeNamePrefix.length)}`;
83
- }else{
84
- attrStr+= ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
103
+ if (attrVal === true && options.suppressBooleanAttributes) {
104
+ attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
105
+ } else {
106
+ attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
85
107
  }
86
108
  }
87
109
  }
88
110
  return attrStr;
89
111
  }
90
112
 
91
- function isStopNode(jPath, options){
92
- jPath = jPath.substr(0,jPath.length - options.textNodeName.length - 1);
113
+ function isStopNode(jPath, options) {
114
+ jPath = jPath.substr(0, jPath.length - options.textNodeName.length - 1);
93
115
  let tagName = jPath.substr(jPath.lastIndexOf(".") + 1);
94
- for(let index in options.stopNodes){
95
- if(options.stopNodes[index] === jPath || options.stopNodes[index] === "*."+tagName) return true;
116
+ for (let index in options.stopNodes) {
117
+ if (options.stopNodes[index] === jPath || options.stopNodes[index] === "*." + tagName) return true;
96
118
  }
97
119
  return false;
98
120
  }
99
121
 
100
- function replaceEntitiesValue(textValue, options){
101
- if(textValue && textValue.length > 0 && options.processEntities){
102
- for (let i=0; i< options.entities.length; i++) {
103
- const entity = options.entities[i];
104
- textValue = textValue.replace(entity.regex, entity.val);
105
- }
122
+ function replaceEntitiesValue(textValue, options) {
123
+ if (textValue && textValue.length > 0 && options.processEntities) {
124
+ for (let i = 0; i < options.entities.length; i++) {
125
+ const entity = options.entities[i];
126
+ textValue = textValue.replace(entity.regex, entity.val);
127
+ }
106
128
  }
107
129
  return textValue;
108
- }
109
- module.exports = toXml;
130
+ }
131
+ module.exports = toXml;
@@ -32,6 +32,7 @@ const defaultOptions = {
32
32
  ignoreDeclaration: false,
33
33
  ignorePiTags: false,
34
34
  transformTagName: false,
35
+ transformAttributeName: false,
35
36
  };
36
37
 
37
38
  const buildOptions = function(options) {
@@ -132,8 +132,11 @@ function buildAttributesMap(attrStr, jPath) {
132
132
  for (let i = 0; i < len; i++) {
133
133
  const attrName = this.resolveNameSpace(matches[i][1]);
134
134
  let oldVal = matches[i][4];
135
- const aName = this.options.attributeNamePrefix + attrName;
135
+ let aName = this.options.attributeNamePrefix + attrName;
136
136
  if (attrName.length) {
137
+ if (this.options.transformAttributeName) {
138
+ aName = this.options.transformAttributeName(aName);
139
+ }
137
140
  if (oldVal !== undefined) {
138
141
  if (this.options.trimValues) {
139
142
  oldVal = oldVal.trim();
@@ -261,7 +264,7 @@ const parseXml = function(xmlData) {
261
264
 
262
265
  i = closeIndex + 2;
263
266
  }else {//Opening tag
264
- let result = readTagExp(xmlData,i, this. options.removeNSPrefix);
267
+ let result = readTagExp(xmlData,i, this.options.removeNSPrefix);
265
268
  let tagName= result.tagName;
266
269
  let tagExp = result.tagExp;
267
270
  let attrExpPresent = result.attrExpPresent;