diff --git a/jxon.js b/jxon.js
index 27b3b2b..aaafdba 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) {
@@ -214,10 +214,60 @@
return vResult;
}
+
+ 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 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;
+
+ 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 */
@@ -250,41 +300,29 @@
} 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 === 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);
+ setAttribute(sName.slice(1), vValue, oParentEl);
} 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);
+ oParentEl.appendChild(oChild);
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);
+ 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);
}
}
}
diff --git a/test/spec.js b/test/spec.js
index cbccf7a..25054ec 100644
--- a/test/spec.js
+++ b/test/spec.js
@@ -173,5 +173,86 @@ 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');
+ });
+ 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');
+ });
+ 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);
+ });
+ 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, '');
+ });
});
});