diff --git a/README.md b/README.md
index 31d60f1..c46e799 100644
--- a/README.md
+++ b/README.md
@@ -9,7 +9,7 @@ Implementation of Mozilla's [JXON](https://developer.mozilla.org/en-US/docs/JXON
#### Example:
```js
-{name: 'myportal'}
+{name: 'myportal'}
myportal
{user: {
@@ -56,7 +56,9 @@ trueIsEmpty: false,
autoDate: false,
ignorePrefixedNodes: false,
parseValues: false,
-parserErrorHandler: undefined
+parserErrorHandler: undefined,
+forceXmlProlog: false,
+defaultXmlProlog: ''
```
### .stringToJs(xmlString)
@@ -73,7 +75,7 @@ parserErrorHandler: undefined
- verbosity - Optional verbosity level of conversion, from 0 to 3. It is almost equivalent to our algorithms from #4 to #1 (default value is 1, which is equivalent to the algorithm #3).
- freeze - Optional boolean expressing whether the created object must be freezed or not (default value is false).
- nestedAttributes - Optional boolean expressing whether the the nodeAttributes must be nested into a child-object named keyAttributes or not (default value is false for verbosity levels from 0 to 2; true for verbosity level 3).
-
+
Example:
```js
var myObject = JXON.build(xmlDoc);
@@ -84,7 +86,7 @@ var myObject = JXON.build(xmlDoc);
- namespaceURI - Optional DOMString containing the namespace URI of the document to be created, or null if the document doesn't belong to one.
- qualifiedNameStr - Optional DOMString containing the qualified name, that is an optional prefix and colon plus the local root element name, of the document to be created.
- documentType - Optional DocumentType of the document to be created. It defaults to null.
-
+
Example:
```js
var myObject = JXON.unbuild(myObject);
@@ -122,4 +124,5 @@ changes from version 1.x to 2.0 include:
* (breaking) more usefull default settings (see above)
* (breaking) stringify Dates to ISO format instead of GMT
* improved xml namespace handling on node and browsers
+* improved xml prolog handling
* renamed main source file to `jxon.js`
diff --git a/jxon.js b/jxon.js
index b099249..6945dec 100644
--- a/jxon.js
+++ b/jxon.js
@@ -20,7 +20,7 @@
* bugfixes and code cleanup by user @laubstein
* https://github.com/tyrasd/jxon/pull/32
*
- * adapted for nodejs and npm by @tyrasd (Martin Raifer )
+ * adapted for nodejs and npm by @tyrasd (Martin Raifer )
*/
(function(root, factory) {
@@ -54,11 +54,16 @@
trueIsEmpty: false,
autoDate: false,
ignorePrefixedNodes: false,
- parseValues: false
+ parseValues: false,
+ forceXmlProlog: false,
+ defaultXmlProlog: ''
};
var aCache = [];
var rIsNull = /^\s*$/;
var rIsBool = /^(?:true|false)$/i;
+ var rXmlProlog = /^(<\?xml.*?\?>[\n]?)/;
+ var rXmlPrologAttributes = /\b(version|encoding|standalone)="([^"]+?)"/g;
+ var oXmlProlog = {};
var DOMParser;
return new (function() {
@@ -76,6 +81,56 @@
}
};
+ /**
+ * Helper function: given a xml prolog (eg: ),
+ * returns a object (eg: { version: "1.0" }).
+ */
+ function getPrologFromString(sXmlProlog) {
+ var matches = sXmlProlog.match(rXmlPrologAttributes);
+ var oReturn = {};
+ for (var i = 0; i < matches.length; i++) {
+ var currentAttribute = matches[i].split('=');
+ oReturn[currentAttribute[0]] = currentAttribute[1].replace(/["]/g, '');
+ }
+
+ return oReturn;
+ };
+
+ /**
+ * Helper function: given an object representing a xml prolog (eg: { version: "1.0", encoding: "UTF-8", standalone: "true" }),
+ * returns a string (eg: ).
+ */
+ function getPrologFromObject(oProlog) {
+ var ret = [];
+ for (var prop in oProlog) {
+ if (oProlog.hasOwnProperty(prop)) {
+ ret.push(prop + '="' + oProlog[prop] + '"');
+ }
+ }
+
+ return ret.length ? '' : '';
+ };
+
+ /**
+ * Helper function: check for empty object
+ */
+ function isEmptyObject(obj) {
+ for (var prop in obj) {
+ if (obj.hasOwnProperty(prop)) {
+ return false;
+ }
+ }
+
+ return true && JSON.stringify(obj) === JSON.stringify({});
+ };
+
+ /**
+ * Helper function: clone a object
+ */
+ function cloneObject(obj) {
+ return JSON.parse(JSON.stringify(obj));
+ };
+
function parseText(sValue) {
if (!opts.parseValues) {
return sValue;
@@ -253,7 +308,12 @@
if (isNodeJs) {
oParentEl.setAttribute(sName.slice(1), vValue);
}
- // do nothing: special handling of xml namespaces is done via createElementNS()
+ // do nothing: special handling of xml namespaces is done via createElementNS()
+ } else if (sName === opts.attrPrefix + 'xmlProlog') {
+ // store xmlProlog
+ if (vValue) {
+ oXmlProlog = cloneObject(vValue);
+ }
} else if (sName.charAt(0) === opts.attrPrefix) {
oParentEl.setAttribute(sName.slice(1), vValue);
} else if (vValue.constructor === Array) {
@@ -289,7 +349,14 @@
}
this.xmlToJs = this.build = function(oXMLParent, nVerbosity /* optional */ , bFreeze /* optional */ , bNesteAttributes /* optional */ ) {
var _nVerb = arguments.length > 1 && typeof nVerbosity === 'number' ? nVerbosity & 3 : /* put here the default verbosity level: */ 1;
- return createObjTree(oXMLParent, _nVerb, bFreeze || false, arguments.length > 3 ? bNesteAttributes : _nVerb === 3);
+ var oTree = createObjTree(oXMLParent, _nVerb, bFreeze || false, arguments.length > 3 ? bNesteAttributes : _nVerb === 3);
+
+ if (!isEmptyObject(oXmlProlog)) {
+ oTree[opts.attrPrefix + 'xmlProlog'] = cloneObject(oXmlProlog);
+ oXmlProlog = {};
+ }
+
+ return oTree;
};
this.jsToXml = this.unbuild = function(oObjTree, sNamespaceURI /* optional */ , sQualifiedName /* optional */ , oDocumentType /* optional */ ) {
@@ -304,15 +371,33 @@
DOMParser = new xmlDom.DOMParser();
}
+ if (rXmlProlog.test(xmlStr)) {
+ oXmlProlog = getPrologFromString(xmlStr.match(rXmlProlog)[0]);
+ }
+
return DOMParser.parseFromString(xmlStr, 'application/xml');
};
this.xmlToString = function(xmlObj) {
+ var sReturn = '';
+
if (typeof xmlObj.xml !== 'undefined') {
- return xmlObj.xml;
+ sReturn = xmlObj.xml;
} else {
- return (new xmlDom.XMLSerializer()).serializeToString(xmlObj);
+ sReturn = (new xmlDom.XMLSerializer()).serializeToString(xmlObj);
}
+
+ // if the original string has a prolog or forceXmlProlog option is true, we include a new one or replace the existent
+ if (!isEmptyObject(oXmlProlog) || opts.forceXmlProlog) {
+ if (rXmlProlog.test(sReturn)) {
+ // replace the prolog
+ sReturn = sReturn.replace(rXmlProlog, (getPrologFromObject(oXmlProlog) || opts.defaultXmlProlog));
+ } else {
+ sReturn = (getPrologFromObject(oXmlProlog) || opts.defaultXmlProlog) + sReturn;
+ }
+ }
+
+ return sReturn;
};
this.stringToJs = function(str) {
diff --git a/jxon.min.js b/jxon.min.js
index ce7a673..d308603 100644
--- a/jxon.min.js
+++ b/jxon.min.js
@@ -20,7 +20,7 @@
* bugfixes and code cleanup by user @laubstein
* https://github.com/tyrasd/jxon/pull/32
*
- * adapted for nodejs and npm by @tyrasd (Martin Raifer )
+ * adapted for nodejs and npm by @tyrasd (Martin Raifer )
*/
(function(e,t){if(typeof define==="function"&&define.amd){
// AMD. Register as an anonymous module.
@@ -33,7 +33,27 @@ module.exports=t(window)}else{
// like Node.
module.exports=t(require("xmldom"),true)}}else{
// Browser globals (root is window)
-e.JXON=t(window)}})(this,function(e,t){var r={valueKey:"_",attrKey:"$",attrPrefix:"$",lowerCaseTags:false,trueIsEmpty:false,autoDate:false,ignorePrefixedNodes:false,parseValues:false};var n=[];var i=/^\s*$/;var o=/^(?:true|false)$/i;var a;return new function(){this.config=function(t){for(var n in t){r[n]=t[n]}if(r.parserErrorHandler){a=new e.DOMParser({errorHandler:r.parserErrorHandler,locator:{}})}};function s(e){if(!r.parseValues){return e}if(i.test(e)){return null}if(o.test(e)){return e.toLowerCase()==="true"}if(isFinite(e)){return parseFloat(e)}if(r.autoDate&&isFinite(Date.parse(e))){return new Date(e)}return e}function l(){}l.prototype.toString=function(){return"null"};l.prototype.valueOf=function(){return null};function f(e){return e===null?new l:e instanceof Object?e:new e.constructor(e)}function u(e,t,i,o){var a=4,l=3,c=1,d=n.length,p=e.hasChildNodes(),m=e.nodeType===e.ELEMENT_NODE&&e.hasAttributes(),h=Boolean(t&2),g=0,x="",v=h?{}:/* put here the default value for empty nodes: */r.trueIsEmpty?true:"",w,y;if(p){for(var T,S=0;S0)&&x){v[r.valueKey]=b}else if(!h&&g===0&&x){v=b}if(i&&(h||g>0)){Object.freeze(v)}n.length=d;return v}function c(e,n,i){var o,a,s;if(i.constructor===String||i.constructor===Number||i.constructor===Boolean){n.appendChild(e.createTextNode(i.toString()));/* verbosity level is 0 or 1 */
-if(i===i.valueOf()){return}}else if(i.constructor===Date){n.appendChild(e.createTextNode(i.toISOString()))}for(var l in i){o=i[l];if(o===null){o={}}if(isFinite(l)||o instanceof Function){continue}/* verbosity level is 0 */
-if(l===r.valueKey){if(o!==null&&o!==true){n.appendChild(e.createTextNode(o.constructor===Date?o.toISOString():String(o)))}}else if(l===r.attrKey){/* verbosity level is 3 */
-for(var f in o){n.setAttribute(f,o[f])}}else if(l===r.attrPrefix+"xmlns"){if(t){n.setAttribute(l.slice(1),o)}}else if(l.charAt(0)===r.attrPrefix){n.setAttribute(l.slice(1),o)}else if(o.constructor===Array){for(var u in o){s=o[u]&&o[u][r.attrPrefix+"xmlns"]||n.namespaceURI;if(s){a=e.createElementNS(s,l)}else{a=e.createElement(l)}c(e,a,o[u]||{});n.appendChild(a)}}else{s=(o||{})[r.attrPrefix+"xmlns"]||n.namespaceURI;if(s){a=e.createElementNS(s,l)}else{a=e.createElement(l)}if(o instanceof Object){c(e,a,o)}else if(o!==null&&o!==true){a.appendChild(e.createTextNode(o.toString()))}else if(!r.trueIsEmpty&&o===true){a.appendChild(e.createTextNode(o.toString()))}n.appendChild(a)}}}this.xmlToJs=this.build=function(e,t,r,n){var i=arguments.length>1&&typeof t==="number"?t&3:1;return u(e,i,r||false,arguments.length>3?n:i===3)};this.jsToXml=this.unbuild=function(t,r,n,i){var o=e.document&&e.document.implementation||new e.DOMImplementation;var a=o.createDocument(r||null,n||"",i||null);c(a,a.documentElement||a,t);return a};this.stringToXml=function(t){if(!a){a=new e.DOMParser}return a.parseFromString(t,"application/xml")};this.xmlToString=function(t){if(typeof t.xml!=="undefined"){return t.xml}else{return(new e.XMLSerializer).serializeToString(t)}};this.stringToJs=function(e){var t=this.stringToXml(e);return this.xmlToJs(t)};this.jsToString=this.stringify=function(e,t,r,n){return this.xmlToString(this.jsToXml(e,t,r,n))};this.each=function(e,t,r){if(e instanceof Array){e.forEach(t,r)}else{[e].forEach(t,r)}}}});
\ No newline at end of file
+e.JXON=t(window)}})(this,function(e,t){var r={valueKey:"_",attrKey:"$",attrPrefix:"$",lowerCaseTags:false,trueIsEmpty:false,autoDate:false,ignorePrefixedNodes:false,parseValues:false,forceXmlProlog:false,defaultXmlProlog:''};var n=[];var i=/^\s*$/;var o=/^(?:true|false)$/i;var a=/^(<\?xml.*?\?>[\n]?)/;var l=/\b(version|encoding|standalone)="([^"]+?)"/g;var s={};var f;return new function(){this.config=function(t){for(var n in t){r[n]=t[n]}if(r.parserErrorHandler){f=new e.DOMParser({errorHandler:r.parserErrorHandler,locator:{}})}};/**
+ * Helper function: given a xml prolog (eg: ),
+ * returns a object (eg: { version: "1.0" }).
+ */
+function u(e){var t=e.match(l);var r={};for(var n=0;n).
+ */
+function c(e){var t=[];for(var r in e){if(e.hasOwnProperty(r)){t.push(r+'="'+e[r]+'"')}}return t.length?"":""}/**
+ * Helper function: check for empty object
+ */
+function d(e){for(var t in e){if(e.hasOwnProperty(t)){return false}}return true&&JSON.stringify(e)===JSON.stringify({})}/**
+ * Helper function: clone a object
+ */
+function m(e){return JSON.parse(JSON.stringify(e))}function p(e){if(!r.parseValues){return e}if(i.test(e)){return null}if(o.test(e)){return e.toLowerCase()==="true"}if(isFinite(e)){return parseFloat(e)}if(r.autoDate&&isFinite(Date.parse(e))){return new Date(e)}return e}function h(){}h.prototype.toString=function(){return"null"};h.prototype.valueOf=function(){return null};function g(e){return e===null?new h:e instanceof Object?e:new e.constructor(e)}function v(e,t,i,o){var a=4,l=3,s=1,f=n.length,u=e.hasChildNodes(),c=e.nodeType===e.ELEMENT_NODE&&e.hasAttributes(),d=Boolean(t&2),m=0,h="",x=d?{}:/* put here the default value for empty nodes: */r.trueIsEmpty?true:"",y,w;if(u){for(var T,P=0;P0)&&h){x[r.valueKey]=N}else if(!d&&m===0&&h){x=N}if(i&&(d||m>0)){Object.freeze(x)}n.length=f;return x}function x(e,n,i){var o,a,l;if(i.constructor===String||i.constructor===Number||i.constructor===Boolean){n.appendChild(e.createTextNode(i.toString()));/* verbosity level is 0 or 1 */
+if(i===i.valueOf()){return}}else if(i.constructor===Date){n.appendChild(e.createTextNode(i.toISOString()))}for(var f in i){o=i[f];if(o===null){o={}}if(isFinite(f)||o instanceof Function){continue}/* verbosity level is 0 */
+if(f===r.valueKey){if(o!==null&&o!==true){n.appendChild(e.createTextNode(o.constructor===Date?o.toISOString():String(o)))}}else if(f===r.attrKey){/* verbosity level is 3 */
+for(var u in o){n.setAttribute(u,o[u])}}else if(f===r.attrPrefix+"xmlns"){if(t){n.setAttribute(f.slice(1),o)}}else if(f===r.attrPrefix+"xmlProlog"){
+// store xmlProlog
+if(o){s=m(o)}}else if(f.charAt(0)===r.attrPrefix){n.setAttribute(f.slice(1),o)}else if(o.constructor===Array){for(var c in o){l=o[c]&&o[c][r.attrPrefix+"xmlns"]||n.namespaceURI;if(l){a=e.createElementNS(l,f)}else{a=e.createElement(f)}x(e,a,o[c]||{});n.appendChild(a)}}else{l=(o||{})[r.attrPrefix+"xmlns"]||n.namespaceURI;if(l){a=e.createElementNS(l,f)}else{a=e.createElement(f)}if(o instanceof Object){x(e,a,o)}else if(o!==null&&o!==true){a.appendChild(e.createTextNode(o.toString()))}else if(!r.trueIsEmpty&&o===true){a.appendChild(e.createTextNode(o.toString()))}n.appendChild(a)}}}this.xmlToJs=this.build=function(e,t,n,i){var o=arguments.length>1&&typeof t==="number"?t&3:/* put here the default verbosity level: */1;var a=v(e,o,n||false,arguments.length>3?i:o===3);if(!d(s)){a[r.attrPrefix+"xmlProlog"]=m(s);s={}}return a};this.jsToXml=this.unbuild=function(t,r,n,i){var o=e.document&&e.document.implementation||new e.DOMImplementation;var a=o.createDocument(r||null,n||"",i||null);x(a,a.documentElement||a,t);return a};this.stringToXml=function(t){if(!f){f=new e.DOMParser}if(a.test(t)){s=u(t.match(a)[0])}return f.parseFromString(t,"application/xml")};this.xmlToString=function(t){var n="";if(typeof t.xml!=="undefined"){n=t.xml}else{n=(new e.XMLSerializer).serializeToString(t)}
+// if the original string has a prolog or forceXmlProlog option is true, we include a new one or replace the existent
+if(!d(s)||r.forceXmlProlog){if(a.test(n)){
+// replace the prolog
+n=n.replace(a,c(s)||r.defaultXmlProlog)}else{n=(c(s)||r.defaultXmlProlog)+n}}return n};this.stringToJs=function(e){var t=this.stringToXml(e);return this.xmlToJs(t)};this.jsToString=this.stringify=function(e,t,r,n){return this.xmlToString(this.jsToXml(e,t,r,n))};this.each=function(e,t,r){if(e instanceof Array){e.forEach(t,r)}else{[e].forEach(t,r)}}}});
\ No newline at end of file
diff --git a/test/spec.js b/test/spec.js
index 610dad5..7714859 100644
--- a/test/spec.js
+++ b/test/spec.js
@@ -7,19 +7,18 @@ var assert = require('chai').assert,
$attr: 'value',
_: 'root value'
}
- };
-
+};
describe('JXON', function() {
describe('.jsToXml > .xmlToJs', function() {
- it('should return return identical object', function() {
+ it('should return identical object', function() {
var xml = JXON.jsToXml(jsObj);
var newJs = JXON.xmlToJs(xml);
assert.deepEqual(jsObj, newJs);
});
});
describe('.jsToString > .stringToJs', function() {
- it('should return return identical object', function() {
+ it('should return identical object', function() {
var str = JXON.jsToString(jsObj);
var newJs = JXON.stringToJs(str);
assert.deepEqual(jsObj, newJs);
@@ -76,7 +75,7 @@ describe('JXON', function() {
});
var strTwo = JXON.jsToString(JXON.stringToJs('first positionsecond position'));
-
+
assert.equal(strOne, strTwo);
});
@@ -123,7 +122,7 @@ describe('JXON', function() {
var str = JXON.jsToString(obj);
assert.equal(str, '');
});
- it('as property', function() { // addigional test from #26
+ it('as property', function() { // additional test from #26
var strNull = JXON.jsToString({
'TrainingCenterDatabase': {
'$xmlns': '6',
@@ -145,4 +144,22 @@ describe('JXON', function() {
assert.equal(strNull, strEmptyObj);
});
});
+ describe('xml prolog', function() {
+ it('should be consistent', function() {
+ var originalString = '1';
+ var xmlObject = JXON.stringToXml(originalString);
+ var jsonFromXml = JXON.xmlToJs(xmlObject);
+ var jsonFromString = JXON.stringToJs(originalString);
+
+ assert.equal(originalString, JXON.jsToString(jsonFromXml));
+ assert.equal(originalString, JXON.jsToString(jsonFromString));
+
+ // PhantomJS do not serialize the prolog, so we are forcing a default
+ JXON.config({
+ forceXmlProlog: true,
+ defaultXmlProlog: ''
+ });
+ assert.equal(originalString, JXON.xmlToString(xmlObject));
+ });
+ });
});