From 94cdc0efbf3267b55c3f36f2dcb5aa5c370b74a3 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Tue, 2 Mar 2021 17:05:26 +0100 Subject: [PATCH 1/6] Refactor duplicate createElement code and add a namespace test for the Array case --- jxon.js | 41 +++++++++++++++++++++++++++-------------- test/spec.js | 19 +++++++++++++++++++ 2 files changed, 46 insertions(+), 14 deletions(-) diff --git a/jxon.js b/jxon.js index 27b3b2b..9c69a5f 100644 --- a/jxon.js +++ b/jxon.js @@ -214,10 +214,33 @@ return vResult; } + + function getElementNS(value, oParentEl) { + var elementNS; + + if (value && typeof value === 'object') { + elementNS = value[opts.attrPrefix + 'xmlns']; + } + + return elementNS || oParentEl.namespaceURI; + } + + function createElement(sName, value, oParentEl, oXMLDoc) { + var elementNS = getElementNS(value, oParentEl), + element; + + if (elementNS) { + element = oXMLDoc.createElementNS(elementNS, sName); + } else { + element = oXMLDoc.createElement(sName); + } + + return element; + } + function loadObjTree(oXMLDoc, oParentEl, oParentObj) { var vValue, - oChild, - elementNS; + oChild; if (oParentObj.constructor === String || oParentObj.constructor === Number || oParentObj.constructor === Boolean) { oParentEl.appendChild(oXMLDoc.createTextNode(oParentObj.toString())); /* verbosity level is 0 or 1 */ @@ -262,23 +285,13 @@ } else if (vValue.constructor === Array) { for (var nItem in vValue) { if (!vValue.hasOwnProperty(nItem)) continue; - elementNS = (vValue[nItem] && vValue[nItem][opts.attrPrefix + 'xmlns']) || oParentEl.namespaceURI; - if (elementNS) { - oChild = oXMLDoc.createElementNS(elementNS, sName); - } else { - oChild = oXMLDoc.createElement(sName); - } + oChild = createElement(sName, vValue[nItem], oParentEl, oXMLDoc); loadObjTree(oXMLDoc, oChild, vValue[nItem] || {}); oParentEl.appendChild(oChild); } } else { - elementNS = (vValue || {})[opts.attrPrefix + 'xmlns'] || oParentEl.namespaceURI; - if (elementNS) { - oChild = oXMLDoc.createElementNS(elementNS, sName); - } else { - oChild = oXMLDoc.createElement(sName); - } + oChild = createElement(sName, vValue, oParentEl, oXMLDoc); if (vValue instanceof Object) { loadObjTree(oXMLDoc, oChild, vValue); } else if (vValue !== null && (vValue !== true || !opts.trueIsEmpty)) { diff --git a/test/spec.js b/test/spec.js index cbccf7a..d4f4ca2 100644 --- a/test/spec.js +++ b/test/spec.js @@ -173,5 +173,24 @@ describe('JXON', function() { assert.equal(strNull, strEmptyObj); }); + it('sets the namespace for array elements', function() { + var obj = { + "element": { + "a": [ + { + "$xmlns": "foo", + "_": "1" + }, + null, + { + "$xmlns": "foo", + "_": "3" + } + ] + } + }; + var str = JXON.jsToString(obj); + assert.equal(str, '13'); + }); }); }); From d569ab39c1c5a47748db1c4eeefb210b5a17bf29 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Thu, 4 Mar 2021 09:08:26 +0100 Subject: [PATCH 2/6] Add failing tests for namespace prefixes in JSON --- test/spec.js | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/test/spec.js b/test/spec.js index d4f4ca2..de5254f 100644 --- a/test/spec.js +++ b/test/spec.js @@ -192,5 +192,42 @@ describe('JXON', function() { var str = JXON.jsToString(obj); assert.equal(str, '13'); }); + it('sets the corresponding parent namespace for prefix', function() { + var obj = { + root: { + "$xmlns": "urn:default", + "$xmlns:foo": "urn:foo", + element: { + "foo:a": "bar" + } + } + }; + var xml = JXON.jsToXml(obj); + var a = xml.documentElement.firstChild.firstChild; + var str = JXON.xmlToString(xml); + + assert.equal(a.localName, 'a', 'localName'); + assert.equal(a.prefix, 'foo', 'prefix'); + assert.equal(a.namespaceURI, 'urn:foo', 'namespaceURI'); + + assert.equal(str, 'bar'); + }); + it('sets the attribute namespace for prefix', function() { + var obj = { + "foo:element": { + "$xmlns:foo": "urn:foo", + "_": "bar" + } + }; + var xml = JXON.jsToXml(obj); + var element = xml.documentElement; + var str = JXON.xmlToString(xml); + + assert.equal(element.localName, 'element', 'localName'); + assert.equal(element.prefix, 'foo', 'prefix'); + assert.equal(element.namespaceURI, 'urn:foo', 'namespaceURI'); + + assert.equal(str, 'bar'); + }); }); }); From 71a5b0989d1a40350a6d7767cb3f1bdbaedd0891 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Thu, 4 Mar 2021 09:17:16 +0100 Subject: [PATCH 3/6] Support namespace prefixes in JSON --- jxon.js | 40 ++++++++++++++++++++++++++-------------- 1 file changed, 26 insertions(+), 14 deletions(-) diff --git a/jxon.js b/jxon.js index 9c69a5f..17e4099 100644 --- a/jxon.js +++ b/jxon.js @@ -215,18 +215,32 @@ return vResult; } - function getElementNS(value, oParentEl) { - var elementNS; - - if (value && typeof value === 'object') { - elementNS = value[opts.attrPrefix + 'xmlns']; + function getElementNS(sName, vValue, oParentEl) { + var xmlns = opts.attrPrefix + 'xmlns', + isObject = vValue && vValue instanceof Object, + elementNS, + prefix; + + if (sName.indexOf(':') !== -1) { + prefix = sName.split(':')[0]; + + if (isObject) { + elementNS = vValue[xmlns + ':' + prefix]; + if (elementNS) return elementNS; + } + + elementNS = oParentEl.lookupNamespaceURI(prefix); + if (elementNS) return elementNS; + } + if (isObject) { + elementNS = vValue[xmlns]; } return elementNS || oParentEl.namespaceURI; } - function createElement(sName, value, oParentEl, oXMLDoc) { - var elementNS = getElementNS(value, oParentEl), + function createElement(sName, vValue, oParentEl, oXMLDoc) { + var elementNS = getElementNS(sName, vValue, oParentEl), element; if (elementNS) { @@ -275,29 +289,27 @@ for (var sAttrib in vValue) { oParentEl.setAttribute(sAttrib, vValue[sAttrib]); } - } else if (sName === opts.attrPrefix + 'xmlns') { - if (isNodeJs) { - oParentEl.setAttribute(sName.slice(1), vValue); - } - // do nothing: special handling of xml namespaces is done via createElementNS() + } else if (sName.indexOf(opts.attrPrefix + 'xmlns') === 0) { + // explicitly set xmlns and xmlns:* attributes, so they can be set anywhere in the tag hierarchy + oParentEl.setAttributeNS('http://www.w3.org/2000/xmlns/', sName.slice(1), vValue); } else if (sName.charAt(0) === opts.attrPrefix) { oParentEl.setAttribute(sName.slice(1), vValue); } else if (vValue.constructor === Array) { for (var nItem in vValue) { if (!vValue.hasOwnProperty(nItem)) continue; oChild = createElement(sName, vValue[nItem], oParentEl, oXMLDoc); + oParentEl.appendChild(oChild); loadObjTree(oXMLDoc, oChild, vValue[nItem] || {}); - oParentEl.appendChild(oChild); } } else { oChild = createElement(sName, vValue, oParentEl, oXMLDoc); + oParentEl.appendChild(oChild); if (vValue instanceof Object) { loadObjTree(oXMLDoc, oChild, vValue); } else if (vValue !== null && (vValue !== true || !opts.trueIsEmpty)) { oChild.appendChild(oXMLDoc.createTextNode(vValue.toString())); } - oParentEl.appendChild(oChild); } } } From 5b3c12bb757382a4968f27dde4e8e2b2f7822515 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Thu, 4 Mar 2021 11:14:36 +0100 Subject: [PATCH 4/6] Add failing test for XML round trip with prefixes --- test/spec.js | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/test/spec.js b/test/spec.js index de5254f..b1c1b0c 100644 --- a/test/spec.js +++ b/test/spec.js @@ -229,5 +229,11 @@ describe('JXON', function() { assert.equal(str, 'bar'); }); + it('keeps prefix and namespace on XML round trip', function() { + var str1 = 'bar'; + var obj = JXON.stringToJs(str1); + var str2 = JXON.jsToString(obj); + assert.equal(str1, str2); + }); }); }); From 24eb1aaaf3a58248bab421afa4dca6240d7991f0 Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Thu, 4 Mar 2021 11:22:43 +0100 Subject: [PATCH 5/6] Build attributes before elements from XML to be able to lookup xmlns namespace in parent hierarchy for element with prefix from JS later --- jxon.js | 42 +++++++++++++++++++++--------------------- 1 file changed, 21 insertions(+), 21 deletions(-) diff --git a/jxon.js b/jxon.js index 17e4099..991d866 100644 --- a/jxon.js +++ b/jxon.js @@ -151,27 +151,6 @@ vResult = nVerb === 0 ? objectify(vBuiltVal) : {}; } - for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) { - - sProp = aCache[nElId].nodeName; - if (opts.lowerCaseTags) { - sProp = sProp.toLowerCase(); - } - - vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr); - if (vResult.hasOwnProperty(sProp)) { - if (vResult[sProp].constructor !== Array) { - vResult[sProp] = [vResult[sProp]]; - } - - vResult[sProp].push(vContent); - } else { - vResult[sProp] = vContent; - - nLength++; - } - } - if (bAttributes) { var nAttrLen = oParentNode.attributes.length, sAPrefix = bNesteAttr ? '' : opts.attrPrefix, @@ -201,6 +180,27 @@ } + for (var nElId = nLevelStart; nElId < nLevelEnd; nElId++) { + + sProp = aCache[nElId].nodeName; + if (opts.lowerCaseTags) { + sProp = sProp.toLowerCase(); + } + + vContent = createObjTree(aCache[nElId], nVerb, bFreeze, bNesteAttr); + if (vResult.hasOwnProperty(sProp)) { + if (vResult[sProp].constructor !== Array) { + vResult[sProp] = [vResult[sProp]]; + } + + vResult[sProp].push(vContent); + } else { + vResult[sProp] = vContent; + + nLength++; + } + } + if (nVerb === 3 || (nVerb === 2 || nVerb === 1 && nLength > 0) && sCollectedTxt) { vResult[opts.valueKey] = vBuiltVal; } else if (!bHighVerb && nLength === 0 && sCollectedTxt) { From af66999240656a0f691a78bf719c7b1e3aee293e Mon Sep 17 00:00:00 2001 From: Norbert Renner Date: Fri, 5 Mar 2021 13:49:47 +0100 Subject: [PATCH 6/6] Support namespace prefixes for attributes --- jxon.js | 17 +++++++++++++++-- test/spec.js | 19 +++++++++++++++++++ 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/jxon.js b/jxon.js index 991d866..aaafdba 100644 --- a/jxon.js +++ b/jxon.js @@ -239,6 +239,19 @@ return elementNS || oParentEl.namespaceURI; } + function setAttribute(sAttrib, vValue, oParentEl) { + var attributeNS, + prefix; + + if (sAttrib.indexOf(':') !== -1) { + prefix = sAttrib.split(':')[0]; + attributeNS = oParentEl.lookupNamespaceURI(prefix) || oParentEl.namespaceURI; + oParentEl.setAttributeNS(attributeNS, sAttrib, vValue); + } else { + oParentEl.setAttribute(sAttrib, vValue); + } + } + function createElement(sName, vValue, oParentEl, oXMLDoc) { var elementNS = getElementNS(sName, vValue, oParentEl), element; @@ -287,13 +300,13 @@ } else if (sName === opts.attrKey) { /* verbosity level is 3 */ for (var sAttrib in vValue) { - oParentEl.setAttribute(sAttrib, vValue[sAttrib]); + setAttribute(sAttrib, vValue[sAttrib], oParentEl); } } else if (sName.indexOf(opts.attrPrefix + 'xmlns') === 0) { // explicitly set xmlns and xmlns:* attributes, so they can be set anywhere in the tag hierarchy oParentEl.setAttributeNS('http://www.w3.org/2000/xmlns/', sName.slice(1), vValue); } else if (sName.charAt(0) === opts.attrPrefix) { - oParentEl.setAttribute(sName.slice(1), vValue); + setAttribute(sName.slice(1), vValue, oParentEl); } else if (vValue.constructor === Array) { for (var nItem in vValue) { if (!vValue.hasOwnProperty(nItem)) continue; diff --git a/test/spec.js b/test/spec.js index b1c1b0c..25054ec 100644 --- a/test/spec.js +++ b/test/spec.js @@ -235,5 +235,24 @@ describe('JXON', function() { var str2 = JXON.jsToString(obj); assert.equal(str1, str2); }); + it('sets the namespace for prefixed attributes', function() { + var obj = { + "element": { + "$xmlns": "urn:default", + "$xmlns:foo": "urn:foo", + "$foo:a": "bar" + } + }; + var xml = JXON.jsToXml(obj); + var element = xml.documentElement; + var str = JXON.xmlToString(xml); + var attr = element.attributes[2]; + + assert.equal(attr.localName, 'a', 'localName'); + assert.equal(attr.prefix, 'foo', 'prefix'); + assert.equal(attr.namespaceURI, 'urn:foo', 'namespaceURI'); + + assert.equal(str, ''); + }); }); });