fast-xml-parser 5.3.8 → 5.4.0

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "fast-xml-parser",
3
- "version": "5.3.8",
3
+ "version": "5.4.0",
4
4
  "description": "Validate XML, Parse XML, Build XML without C/C++ based libraries",
5
5
  "main": "./lib/fxp.cjs",
6
6
  "type": "module",
@@ -87,6 +87,7 @@
87
87
  }
88
88
  ],
89
89
  "dependencies": {
90
+ "fast-xml-builder": "^1.0.0",
90
91
  "strnum": "^2.1.2"
91
92
  }
92
93
  }
package/src/fxp.d.ts CHANGED
@@ -285,6 +285,13 @@ export type X2jOptions = {
285
285
  * Defaults to `100`
286
286
  */
287
287
  maxNestedTags?: number;
288
+
289
+ /**
290
+ * Whether to strictly validate tag names
291
+ *
292
+ * Defaults to `true`
293
+ */
294
+ strictReservedNames?: boolean;
288
295
  };
289
296
 
290
297
 
package/src/fxp.js CHANGED
@@ -1,6 +1,6 @@
1
1
  'use strict';
2
2
 
3
- import {validate} from './validator.js';
3
+ import { validate } from './validator.js';
4
4
  import XMLParser from './xmlparser/XMLParser.js';
5
5
  import XMLBuilder from './xmlbuilder/json2xml.js';
6
6
 
@@ -1,285 +1,6 @@
1
- 'use strict';
2
- //parse Empty Node as self closing node
3
- import buildFromOrderedJs from './orderedJs2Xml.js';
4
- import getIgnoreAttributesFn from "../ignoreAttributes.js";
5
-
6
- const defaultOptions = {
7
- attributeNamePrefix: '@_',
8
- attributesGroupName: false,
9
- textNodeName: '#text',
10
- ignoreAttributes: true,
11
- cdataPropName: false,
12
- format: false,
13
- indentBy: ' ',
14
- suppressEmptyNode: false,
15
- suppressUnpairedNode: true,
16
- suppressBooleanAttributes: true,
17
- tagValueProcessor: function (key, a) {
18
- return a;
19
- },
20
- attributeValueProcessor: function (attrName, a) {
21
- return a;
22
- },
23
- preserveOrder: false,
24
- commentPropName: false,
25
- unpairedTags: [],
26
- entities: [
27
- { regex: new RegExp("&", "g"), val: "&" },//it must be on top
28
- { regex: new RegExp(">", "g"), val: ">" },
29
- { regex: new RegExp("<", "g"), val: "&lt;" },
30
- { regex: new RegExp("\'", "g"), val: "&apos;" },
31
- { regex: new RegExp("\"", "g"), val: "&quot;" }
32
- ],
33
- processEntities: true,
34
- stopNodes: [],
35
- // transformTagName: false,
36
- // transformAttributeName: false,
37
- oneListGroup: false
38
- };
39
-
40
- export default function Builder(options) {
41
- this.options = Object.assign({}, defaultOptions, options);
42
- if (this.options.ignoreAttributes === true || this.options.attributesGroupName) {
43
- this.isAttribute = function (/*a*/) {
44
- return false;
45
- };
46
- } else {
47
- this.ignoreAttributesFn = getIgnoreAttributesFn(this.options.ignoreAttributes)
48
- this.attrPrefixLen = this.options.attributeNamePrefix.length;
49
- this.isAttribute = isAttribute;
50
- }
51
-
52
- this.processTextOrObjNode = processTextOrObjNode
53
-
54
- if (this.options.format) {
55
- this.indentate = indentate;
56
- this.tagEndChar = '>\n';
57
- this.newLine = '\n';
58
- } else {
59
- this.indentate = function () {
60
- return '';
61
- };
62
- this.tagEndChar = '>';
63
- this.newLine = '';
64
- }
65
- }
66
-
67
- Builder.prototype.build = function (jObj) {
68
- if (this.options.preserveOrder) {
69
- return buildFromOrderedJs(jObj, this.options);
70
- } else {
71
- if (Array.isArray(jObj) && this.options.arrayNodeName && this.options.arrayNodeName.length > 1) {
72
- jObj = {
73
- [this.options.arrayNodeName]: jObj
74
- }
75
- }
76
- return this.j2x(jObj, 0, []).val;
77
- }
78
- };
79
-
80
- Builder.prototype.j2x = function (jObj, level, ajPath) {
81
- let attrStr = '';
82
- let val = '';
83
- const jPath = ajPath.join('.')
84
- for (let key in jObj) {
85
- if (!Object.prototype.hasOwnProperty.call(jObj, key)) continue;
86
- if (typeof jObj[key] === 'undefined') {
87
- // supress undefined node only if it is not an attribute
88
- if (this.isAttribute(key)) {
89
- val += '';
90
- }
91
- } else if (jObj[key] === null) {
92
- // null attribute should be ignored by the attribute list, but should not cause the tag closing
93
- if (this.isAttribute(key)) {
94
- val += '';
95
- } else if (key === this.options.cdataPropName) {
96
- val += '';
97
- } else if (key[0] === '?') {
98
- val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
99
- } else {
100
- val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
101
- }
102
- // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
103
- } else if (jObj[key] instanceof Date) {
104
- val += this.buildTextValNode(jObj[key], key, '', level);
105
- } else if (typeof jObj[key] !== 'object') {
106
- //premitive type
107
- const attr = this.isAttribute(key);
108
- if (attr && !this.ignoreAttributesFn(attr, jPath)) {
109
- attrStr += this.buildAttrPairStr(attr, '' + jObj[key]);
110
- } else if (!attr) {
111
- //tag value
112
- if (key === this.options.textNodeName) {
113
- let newval = this.options.tagValueProcessor(key, '' + jObj[key]);
114
- val += this.replaceEntitiesValue(newval);
115
- } else {
116
- val += this.buildTextValNode(jObj[key], key, '', level);
117
- }
118
- }
119
- } else if (Array.isArray(jObj[key])) {
120
- //repeated nodes
121
- const arrLen = jObj[key].length;
122
- let listTagVal = "";
123
- let listTagAttr = "";
124
- for (let j = 0; j < arrLen; j++) {
125
- const item = jObj[key][j];
126
- if (typeof item === 'undefined') {
127
- // supress undefined node
128
- } else if (item === null) {
129
- if (key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar;
130
- else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
131
- // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar;
132
- } else if (typeof item === 'object') {
133
- if (this.options.oneListGroup) {
134
- const result = this.j2x(item, level + 1, ajPath.concat(key));
135
- listTagVal += result.val;
136
- if (this.options.attributesGroupName && item.hasOwnProperty(this.options.attributesGroupName)) {
137
- listTagAttr += result.attrStr
138
- }
139
- } else {
140
- listTagVal += this.processTextOrObjNode(item, key, level, ajPath)
141
- }
142
- } else {
143
- if (this.options.oneListGroup) {
144
- let textValue = this.options.tagValueProcessor(key, item);
145
- textValue = this.replaceEntitiesValue(textValue);
146
- listTagVal += textValue;
147
- } else {
148
- listTagVal += this.buildTextValNode(item, key, '', level);
149
- }
150
- }
151
- }
152
- if (this.options.oneListGroup) {
153
- listTagVal = this.buildObjectNode(listTagVal, key, listTagAttr, level);
154
- }
155
- val += listTagVal;
156
- } else {
157
- //nested node
158
- if (this.options.attributesGroupName && key === this.options.attributesGroupName) {
159
- const Ks = Object.keys(jObj[key]);
160
- const L = Ks.length;
161
- for (let j = 0; j < L; j++) {
162
- attrStr += this.buildAttrPairStr(Ks[j], '' + jObj[key][Ks[j]]);
163
- }
164
- } else {
165
- val += this.processTextOrObjNode(jObj[key], key, level, ajPath)
166
- }
167
- }
168
- }
169
- return { attrStr: attrStr, val: val };
170
- };
171
-
172
- Builder.prototype.buildAttrPairStr = function (attrName, val) {
173
- val = this.options.attributeValueProcessor(attrName, '' + val);
174
- val = this.replaceEntitiesValue(val);
175
- if (this.options.suppressBooleanAttributes && val === "true") {
176
- return ' ' + attrName;
177
- } else return ' ' + attrName + '="' + val + '"';
178
- }
179
-
180
- function processTextOrObjNode(object, key, level, ajPath) {
181
- const result = this.j2x(object, level + 1, ajPath.concat(key));
182
- if (object[this.options.textNodeName] !== undefined && Object.keys(object).length === 1) {
183
- return this.buildTextValNode(object[this.options.textNodeName], key, result.attrStr, level);
184
- } else {
185
- return this.buildObjectNode(result.val, key, result.attrStr, level);
186
- }
187
- }
188
-
189
- Builder.prototype.buildObjectNode = function (val, key, attrStr, level) {
190
- if (val === "") {
191
- if (key[0] === "?") return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
192
- else {
193
- return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
194
- }
195
- } else {
196
-
197
- let tagEndExp = '</' + key + this.tagEndChar;
198
- let piClosingChar = "";
199
-
200
- if (key[0] === "?") {
201
- piClosingChar = "?";
202
- tagEndExp = "";
203
- }
204
-
205
- // attrStr is an empty string in case the attribute came as undefined or null
206
- if ((attrStr || attrStr === '') && val.indexOf('<') === -1) {
207
- return (this.indentate(level) + '<' + key + attrStr + piClosingChar + '>' + val + tagEndExp);
208
- } else if (this.options.commentPropName !== false && key === this.options.commentPropName && piClosingChar.length === 0) {
209
- return this.indentate(level) + `<!--${val}-->` + this.newLine;
210
- } else {
211
- return (
212
- this.indentate(level) + '<' + key + attrStr + piClosingChar + this.tagEndChar +
213
- val +
214
- this.indentate(level) + tagEndExp);
215
- }
216
- }
217
- }
218
-
219
- Builder.prototype.closeTag = function (key) {
220
- let closeTag = "";
221
- if (this.options.unpairedTags.indexOf(key) !== -1) { //unpaired
222
- if (!this.options.suppressUnpairedNode) closeTag = "/"
223
- } else if (this.options.suppressEmptyNode) { //empty
224
- closeTag = "/";
225
- } else {
226
- closeTag = `></${key}`
227
- }
228
- return closeTag;
229
- }
230
-
231
- function buildEmptyObjNode(val, key, attrStr, level) {
232
- if (val !== '') {
233
- return this.buildObjectNode(val, key, attrStr, level);
234
- } else {
235
- if (key[0] === "?") return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
236
- else {
237
- return this.indentate(level) + '<' + key + attrStr + '/' + this.tagEndChar;
238
- // return this.buildTagStr(level,key, attrStr);
239
- }
240
- }
241
- }
242
-
243
- Builder.prototype.buildTextValNode = function (val, key, attrStr, level) {
244
- if (this.options.cdataPropName !== false && key === this.options.cdataPropName) {
245
- return this.indentate(level) + `<![CDATA[${val}]]>` + this.newLine;
246
- } else if (this.options.commentPropName !== false && key === this.options.commentPropName) {
247
- return this.indentate(level) + `<!--${val}-->` + this.newLine;
248
- } else if (key[0] === "?") {//PI tag
249
- return this.indentate(level) + '<' + key + attrStr + '?' + this.tagEndChar;
250
- } else {
251
- let textValue = this.options.tagValueProcessor(key, val);
252
- textValue = this.replaceEntitiesValue(textValue);
253
-
254
- if (textValue === '') {
255
- return this.indentate(level) + '<' + key + attrStr + this.closeTag(key) + this.tagEndChar;
256
- } else {
257
- return this.indentate(level) + '<' + key + attrStr + '>' +
258
- textValue +
259
- '</' + key + this.tagEndChar;
260
- }
261
- }
262
- }
263
-
264
- Builder.prototype.replaceEntitiesValue = function (textValue) {
265
- if (textValue && textValue.length > 0 && this.options.processEntities) {
266
- for (let i = 0; i < this.options.entities.length; i++) {
267
- const entity = this.options.entities[i];
268
- textValue = textValue.replace(entity.regex, entity.val);
269
- }
270
- }
271
- return textValue;
272
- }
273
-
274
- function indentate(level) {
275
- return this.options.indentBy.repeat(level);
276
- }
277
-
278
- function isAttribute(name /*, options*/) {
279
- if (name.startsWith(this.options.attributeNamePrefix) && name !== this.options.textNodeName) {
280
- return name.substr(this.attrPrefixLen);
281
- } else {
282
- return false;
283
- }
284
- }
1
+ // Re-export from fast-xml-builder for backward compatibility
2
+ import XMLBuilder from 'fast-xml-builder';
3
+ export default XMLBuilder;
285
4
 
5
+ // If there are any named exports you also want to re-export:
6
+ export * from 'fast-xml-builder';
@@ -39,6 +39,7 @@ export const defaultOptions = {
39
39
  // skipEmptyListItem: false
40
40
  captureMetaData: false,
41
41
  maxNestedTags: 100,
42
+ strictReservedNames: true,
42
43
  };
43
44
 
44
45
  /**
@@ -163,6 +163,7 @@ function buildAttributesMap(attrStr, jPath, tagName) {
163
163
  aName = this.options.transformAttributeName(aName);
164
164
  }
165
165
  if (aName === "__proto__") aName = "#__proto__";
166
+
166
167
  if (oldVal !== undefined) {
167
168
  if (this.options.trimValues) {
168
169
  oldVal = oldVal.trim();
@@ -322,6 +323,13 @@ const parseXml = function (xmlData) {
322
323
  tagName = newTagName;
323
324
  }
324
325
 
326
+ if (this.options.strictReservedNames &&
327
+ (tagName === this.options.commentPropName
328
+ || tagName === this.options.cdataPropName
329
+ )) {
330
+ throw new Error(`Invalid tag name: ${tagName}`);
331
+ }
332
+
325
333
  //save text as child node
326
334
  if (currentNode && textData) {
327
335
  if (currentNode.tagname !== '!xml') {
@@ -1,145 +0,0 @@
1
- const EOL = "\n";
2
-
3
- /**
4
- *
5
- * @param {array} jArray
6
- * @param {any} options
7
- * @returns
8
- */
9
- export default 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);
15
- }
16
-
17
- function arrToStr(arr, options, jPath, indentation) {
18
- let xmlStr = "";
19
- let isPreviousElementTag = false;
20
-
21
-
22
- if (!Array.isArray(arr)) {
23
- // Non-array values (e.g. string tag values) should be treated as text content
24
- if (arr !== undefined && arr !== null) {
25
- let text = arr.toString();
26
- text = replaceEntitiesValue(text, options);
27
- return text;
28
- }
29
- return "";
30
- }
31
-
32
- for (let i = 0; i < arr.length; i++) {
33
- const tagObj = arr[i];
34
- const tagName = propName(tagObj);
35
- if (tagName === undefined) continue;
36
-
37
- let newJPath = "";
38
- if (jPath.length === 0) newJPath = tagName
39
- else newJPath = `${jPath}.${tagName}`;
40
-
41
- if (tagName === options.textNodeName) {
42
- let tagText = tagObj[tagName];
43
- if (!isStopNode(newJPath, options)) {
44
- tagText = options.tagValueProcessor(tagName, tagText);
45
- tagText = replaceEntitiesValue(tagText, options);
46
- }
47
- if (isPreviousElementTag) {
48
- xmlStr += indentation;
49
- }
50
- xmlStr += tagText;
51
- isPreviousElementTag = false;
52
- continue;
53
- } else if (tagName === options.cdataPropName) {
54
- if (isPreviousElementTag) {
55
- xmlStr += indentation;
56
- }
57
- xmlStr += `<![CDATA[${tagObj[tagName][0][options.textNodeName]}]]>`;
58
- isPreviousElementTag = false;
59
- continue;
60
- } else if (tagName === options.commentPropName) {
61
- xmlStr += indentation + `<!--${tagObj[tagName][0][options.textNodeName]}-->`;
62
- isPreviousElementTag = true;
63
- continue;
64
- } else if (tagName[0] === "?") {
65
- const attStr = attr_to_str(tagObj[":@"], options);
66
- const tempInd = tagName === "?xml" ? "" : indentation;
67
- let piTextNodeName = tagObj[tagName][0][options.textNodeName];
68
- piTextNodeName = piTextNodeName.length !== 0 ? " " + piTextNodeName : ""; //remove extra spacing
69
- xmlStr += tempInd + `<${tagName}${piTextNodeName}${attStr}?>`;
70
- isPreviousElementTag = true;
71
- continue;
72
- }
73
- let newIdentation = indentation;
74
- if (newIdentation !== "") {
75
- newIdentation += options.indentBy;
76
- }
77
- const attStr = attr_to_str(tagObj[":@"], options);
78
- const tagStart = indentation + `<${tagName}${attStr}`;
79
- const tagValue = arrToStr(tagObj[tagName], options, newJPath, newIdentation);
80
- if (options.unpairedTags.indexOf(tagName) !== -1) {
81
- if (options.suppressUnpairedNode) xmlStr += tagStart + ">";
82
- else xmlStr += tagStart + "/>";
83
- } else if ((!tagValue || tagValue.length === 0) && options.suppressEmptyNode) {
84
- xmlStr += tagStart + "/>";
85
- } else if (tagValue && tagValue.endsWith(">")) {
86
- xmlStr += tagStart + `>${tagValue}${indentation}</${tagName}>`;
87
- } else {
88
- xmlStr += tagStart + ">";
89
- if (tagValue && indentation !== "" && (tagValue.includes("/>") || tagValue.includes("</"))) {
90
- xmlStr += indentation + options.indentBy + tagValue + indentation;
91
- } else {
92
- xmlStr += tagValue;
93
- }
94
- xmlStr += `</${tagName}>`;
95
- }
96
- isPreviousElementTag = true;
97
- }
98
-
99
- return xmlStr;
100
- }
101
-
102
- function propName(obj) {
103
- const keys = Object.keys(obj);
104
- for (let i = 0; i < keys.length; i++) {
105
- const key = keys[i];
106
- if (!Object.prototype.hasOwnProperty.call(obj, key)) continue;
107
- if (key !== ":@") return key;
108
- }
109
- }
110
-
111
- function attr_to_str(attrMap, options) {
112
- let attrStr = "";
113
- if (attrMap && !options.ignoreAttributes) {
114
- for (let attr in attrMap) {
115
- if (!Object.prototype.hasOwnProperty.call(attrMap, attr)) continue;
116
- let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
117
- attrVal = replaceEntitiesValue(attrVal, options);
118
- if (attrVal === true && options.suppressBooleanAttributes) {
119
- attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}`;
120
- } else {
121
- attrStr += ` ${attr.substr(options.attributeNamePrefix.length)}="${attrVal}"`;
122
- }
123
- }
124
- }
125
- return attrStr;
126
- }
127
-
128
- function isStopNode(jPath, options) {
129
- jPath = jPath.substr(0, jPath.length - options.textNodeName.length - 1);
130
- let tagName = jPath.substr(jPath.lastIndexOf(".") + 1);
131
- for (let index in options.stopNodes) {
132
- if (options.stopNodes[index] === jPath || options.stopNodes[index] === "*." + tagName) return true;
133
- }
134
- return false;
135
- }
136
-
137
- function replaceEntitiesValue(textValue, options) {
138
- if (textValue && textValue.length > 0 && options.processEntities) {
139
- for (let i = 0; i < options.entities.length; i++) {
140
- const entity = options.entities[i];
141
- textValue = textValue.replace(entity.regex, entity.val);
142
- }
143
- }
144
- return textValue;
145
- }
File without changes