Skip to content

Commit c13a961

Browse files
committed
handle non-array input for XML builder when preserveOrder is true
1 parent fc97a55 commit c13a961

File tree

2 files changed

+51
-10
lines changed

2 files changed

+51
-10
lines changed

spec/j2x_ordered_spec.js

Lines changed: 37 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
import {XMLParser, XMLBuilder} from "../src/fxp.js";
1+
import { format } from "path";
2+
import { XMLParser, XMLBuilder } from "../src/fxp.js";
23

34
describe("XMLBuilder", function () {
45

@@ -31,11 +32,11 @@ describe("XMLBuilder", function () {
3132
}
3233
const parser = new XMLParser(options);
3334
let result = parser.parse(XMLdata);
34-
// console.log(JSON.stringify(result, null,4));
35+
// console.log(JSON.stringify(result, null,4));
3536

3637
const builder = new XMLBuilder(options);
3738
result = builder.build(result);
38-
// console.log(result);
39+
// console.log(result);
3940

4041
expect(result).toEqual(expected);
4142
});
@@ -65,16 +66,16 @@ describe("XMLBuilder", function () {
6566
preserveOrder: true,
6667
cdataPropName: "#CDATA",
6768
allowBooleanAttributes: true,
68-
// format: true,
69+
// format: true,
6970

7071
}
7172
const parser = new XMLParser(options);
7273
let result = parser.parse(XMLdata);
73-
// console.log(JSON.stringify(result, null,4));
74+
// console.log(JSON.stringify(result, null,4));
7475

7576
const builder = new XMLBuilder(options);
7677
result = builder.build(result);
77-
// console.log(result);
78+
// console.log(result);
7879

7980
expect(result).toEqual(expected);
8081
});
@@ -300,7 +301,7 @@ describe("XMLBuilder", function () {
300301

301302
const options = {
302303
ignoreAttributes: false,
303-
isArray: (tagName, jpath, isLeafNode, isAttribute) => {
304+
isArray: (tagName, jpath, isLeafNode, isAttribute) => {
304305
if (isLeafNode === true) return true;
305306
},
306307
preserveOrder: true
@@ -319,4 +320,33 @@ describe("XMLBuilder", function () {
319320
// console.log(result);
320321
expect(result).toEqual(expected);
321322
});
323+
324+
322325
});
326+
327+
describe("XMLBuilder- array processing issue", function () {
328+
it("should not throw stack overflow when child value is a non-array (issue #781)", function () {
329+
const builder = new XMLBuilder({
330+
ignoreAttributes: false,
331+
attributeNamePrefix: '@_',
332+
preserveOrder: true,
333+
});
334+
const input = [
335+
{
336+
'foo': [
337+
{ 'bar': [{ '@_V': 'baz' }] },
338+
//{ 'fum': [{ 'qux': '' }] },
339+
{ 'hello': [{ '#text': 'world' }] }
340+
]
341+
}
342+
];
343+
expect(function () {
344+
builder.build(input);
345+
}).not.toThrow();
346+
347+
const result = builder.build(input);
348+
expect(result).toContain('<hello>world</hello>');
349+
expect(result).toContain('<foo>');
350+
});
351+
352+
});

src/xmlbuilder/orderedJs2Xml.js

Lines changed: 14 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,21 @@ function arrToStr(arr, options, jPath, indentation) {
1818
let xmlStr = "";
1919
let isPreviousElementTag = false;
2020

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+
2132
for (let i = 0; i < arr.length; i++) {
2233
const tagObj = arr[i];
2334
const tagName = propName(tagObj);
24-
if(tagName === undefined) continue;
35+
if (tagName === undefined) continue;
2536

2637
let newJPath = "";
2738
if (jPath.length === 0) newJPath = tagName
@@ -92,7 +103,7 @@ function propName(obj) {
92103
const keys = Object.keys(obj);
93104
for (let i = 0; i < keys.length; i++) {
94105
const key = keys[i];
95-
if(!obj.hasOwnProperty(key)) continue;
106+
if (!obj.hasOwnProperty(key)) continue;
96107
if (key !== ":@") return key;
97108
}
98109
}
@@ -101,7 +112,7 @@ function attr_to_str(attrMap, options) {
101112
let attrStr = "";
102113
if (attrMap && !options.ignoreAttributes) {
103114
for (let attr in attrMap) {
104-
if(!attrMap.hasOwnProperty(attr)) continue;
115+
if (!attrMap.hasOwnProperty(attr)) continue;
105116
let attrVal = options.attributeValueProcessor(attr, attrMap[attr]);
106117
attrVal = replaceEntitiesValue(attrVal, options);
107118
if (attrVal === true && options.suppressBooleanAttributes) {

0 commit comments

Comments
 (0)