diff --git a/spec/j2x_spec.js b/spec/j2x_spec.js index be0aa81e..2ab00f54 100644 --- a/spec/j2x_spec.js +++ b/spec/j2x_spec.js @@ -483,4 +483,117 @@ describe("XMLBuilder", function() { expect(result).toEqual(expected); }); + it("should suppress null attributes in the xml when format is true and ignoreAttributes is false", function () { + const jObj = { + "list": { + "item": [ + "one", + { "#text": "two" }, + { "#text": "three", "@_attr": null }, // only one null attr + { "#text": "four", "@_attr": "foo", "@_attr1": null }, // one defined attr and one null attr + { "#text": "five", "@_attr": null, "@_attr1": "baz" }, // one null attr and one defined attr + { "#text": "six", "@_attr": "foo", "@_attr1": "baz" }, // all defined attrs (more than one) + { "#text": "seven", "@_attr": null, "@_attr1": null }, // all null attrs (more than one) + ] + } + }; + const builder = new XMLBuilder({ + ignoreAttributes: false, + format: true + }); + const result = builder.build(jObj); + const expected = ` + + one + two + three + four + five + six + seven + `; + + expect(result.replace(/\s+/g, "")).toEqual(expected.replace(/\s+/g, "")); + }); + + it("should suppress null attributes in the xml when format is false and ignoreAttributes is false", function () { + const jObj = { + "list": { + "item": [ + "one", + { "#text": "two" }, + { "#text": "three", "@_attr": null }, // only one null attr + { "#text": "four", "@_attr": "foo", "@_attr1": null }, // one defined attr and one null attr + { "#text": "five", "@_attr": null, "@_attr1": "baz" }, // one null attr and one defined attr + { "#text": "six", "@_attr": "foo", "@_attr1": "baz" }, // all defined attrs (more than one) + { "#text": "seven", "@_attr": null, "@_attr1": null }, // all null attrs (more than one) + ] + } + }; + const builder = new XMLBuilder({ + ignoreAttributes: false, + format: false + }); + const result = builder.build(jObj); + const expected = `onetwothreefourfivesixseven`; + + expect(result).toEqual(expected); + }); + + it("should suppress undefined attributes in the xml when format is true and ignoreAttributes is false", function () { + const jObj = { + "list": { + "item": [ + "one", + { "#text": "two" }, + { "#text": "three", "@_attr": undefined }, // only one undefined attr + { "#text": "four", "@_attr": "foo", "@_attr1": undefined }, // one defined attr and one undefined attr + { "#text": "five", "@_attr": undefined, "@_attr1": "baz" }, // one undefined attr and one defined attr + { "#text": "six", "@_attr": "foo", "@_attr1": "baz" }, // all defined attrs (more than one) + { "#text": "seven", "@_attr": undefined, "@_attr1": undefined }, // all undefined attrs (more than one) + ] + } + }; + const builder = new XMLBuilder({ + ignoreAttributes: false, + format: true + }); + const result = builder.build(jObj); + const expected = ` + + one + two + three + four + five + six + seven + `; + + expect(result.replace(/\s+/g, "")).toEqual(expected.replace(/\s+/g, "")); + }); + + it("should suppress undefined attributes in the xml when format is false and ignoreAttributes is false", function () { + const jObj = { + "list": { + "item": [ + "one", + { "#text": "two" }, + { "#text": "three", "@_attr": undefined }, // only one undefined attr + { "#text": "four", "@_attr": "foo", "@_attr1": undefined }, // one defined attr and one undefined attr + { "#text": "five", "@_attr": undefined, "@_attr1": "baz" }, // one undefined attr and one defined attr + { "#text": "six", "@_attr": "foo", "@_attr1": "baz" }, // all defined attrs (more than one) + { "#text": "seven", "@_attr": undefined, "@_attr1": undefined }, // all undefined attrs (more than one) + ] + } + }; + const builder = new XMLBuilder({ + ignoreAttributes: false, + format: false + }); + const result = builder.build(jObj); + const expected = `onetwothreefourfivesixseven`; + + expect(result).toEqual(expected); + }); }); diff --git a/src/xmlbuilder/json2xml.js b/src/xmlbuilder/json2xml.js index 813043f3..9dae3472 100644 --- a/src/xmlbuilder/json2xml.js +++ b/src/xmlbuilder/json2xml.js @@ -80,10 +80,19 @@ Builder.prototype.j2x = function(jObj, level) { let val = ''; for (let key in jObj) { if (typeof jObj[key] === 'undefined') { - // supress undefined node + // supress undefined node only if it is not an attribute + if (this.isAttribute(key)) { + val += ''; + } } else if (jObj[key] === null) { - if(key[0] === "?") val += this.indentate(level) + '<' + key + '?' + this.tagEndChar; - else val += this.indentate(level) + '<' + key + '/' + this.tagEndChar; + // null attribute should be ignored by the attribute list, but should not cause the tag closing + if (this.isAttribute(key)) { + val += ''; + } else if (key[0] === '?') { + val += this.indentate(level) + '<' + key + '?' + this.tagEndChar; + } else { + val += this.indentate(level) + '<' + key + '/' + this.tagEndChar; + } // val += this.indentate(level) + '<' + key + '/' + this.tagEndChar; } else if (jObj[key] instanceof Date) { val += this.buildTextValNode(jObj[key], key, '', level); @@ -176,7 +185,8 @@ Builder.prototype.buildObjectNode = function(val, key, attrStr, level) { tagEndExp = ""; } - if (attrStr && val.indexOf('<') === -1) { + // attrStr is an empty string in case the attribute came as undefined or null + if ((attrStr || attrStr === '') && val.indexOf('<') === -1) { return ( this.indentate(level) + '<' + key + attrStr + piClosingChar + '>' + val + tagEndExp ); } else if (this.options.commentPropName !== false && key === this.options.commentPropName && piClosingChar.length === 0) { return this.indentate(level) + `` + this.newLine;