Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

XML Prolog support #34

Open
wants to merge 4 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 7 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Implementation of Mozilla's [JXON](https://developer.mozilla.org/en-US/docs/JXON
#### Example:

```js
{name: 'myportal'}
{name: 'myportal'}
<name>myportal</name>

{user: {
Expand Down Expand Up @@ -56,7 +56,9 @@ trueIsEmpty: false,
autoDate: false,
ignorePrefixedNodes: false,
parseValues: false,
parserErrorHandler: undefined
parserErrorHandler: undefined,
forceXmlProlog: false,
defaultXmlProlog: '<?xml version="1.0" encoding="UTF-8"?>'
```

### .stringToJs(xmlString)
Expand All @@ -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);
Expand All @@ -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);
Expand Down Expand Up @@ -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`
97 changes: 91 additions & 6 deletions jxon.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 <[email protected]>)
* adapted for nodejs and npm by @tyrasd (Martin Raifer <[email protected]>)
*/

(function(root, factory) {
Expand Down Expand Up @@ -54,11 +54,16 @@
trueIsEmpty: false,
autoDate: false,
ignorePrefixedNodes: false,
parseValues: false
parseValues: false,
forceXmlProlog: false,
defaultXmlProlog: '<?xml version="1.0" encoding="UTF-8"?>'
};
var aCache = [];
var rIsNull = /^\s*$/;
var rIsBool = /^(?:true|false)$/i;
var rXmlProlog = /^(<\?xml.*?\?>[\n]?)/;
var rXmlPrologAttributes = /\b(version|encoding|standalone)="([^"]+?)"/g;
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

these attributes could also be quoted in single quotes ' according to the specs

Copy link
Contributor Author

@laubstein laubstein Aug 17, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My mistake... that's easy to fix.

var oXmlProlog = {};
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

global variable :(

var DOMParser;

return new (function() {
Expand All @@ -76,6 +81,56 @@
}
};

/**
* Helper function: given a xml prolog (eg: <?xml version="1.0"?>),
* 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: <?xml version="1.0" encoding="UTF-8" standalone="true"?>).
*/
function getPrologFromObject(oProlog) {
var ret = [];
for (var prop in oProlog) {
if (oProlog.hasOwnProperty(prop)) {
ret.push(prop + '="' + oProlog[prop] + '"');
}
}

return ret.length ? '<?xml ' + ret.join(' ') + '?>' : '';
};

/**
* 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) {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why is this one needed?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is actually not needed... I will change de default value to undefined.

return JSON.parse(JSON.stringify(obj));
};

function parseText(sValue) {
if (!opts.parseValues) {
return sValue;
Expand Down Expand Up @@ -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') {
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should really exist outside the loadObjTree function. Otherwise, one cannot have an actual property named "xmlProlog", as in <foo xmlProlog="bar" />.

// 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) {
Expand Down Expand Up @@ -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 */ ) {
Expand All @@ -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) {
Expand Down
30 changes: 25 additions & 5 deletions jxon.min.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

29 changes: 23 additions & 6 deletions test/spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -76,7 +75,7 @@ describe('JXON', function() {
});

var strTwo = JXON.jsToString(JXON.stringToJs('<element><a>first position</a><a>second position</a></element>'));

assert.equal(strOne, strTwo);
});

Expand Down Expand Up @@ -123,7 +122,7 @@ describe('JXON', function() {
var str = JXON.jsToString(obj);
assert.equal(str, '<test xmlns="foo" xmlns:y="moo"/>');
});
it('as property', function() { // addigional test from #26
it('as property', function() { // additional test from #26
var strNull = JXON.jsToString({
'TrainingCenterDatabase': {
'$xmlns': '6',
Expand All @@ -145,4 +144,22 @@ describe('JXON', function() {
assert.equal(strNull, strEmptyObj);
});
});
describe('xml prolog', function() {
it('should be consistent', function() {
var originalString = '<?xml version="1.0"?><a>1</a>';
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: '<?xml version="1.0"?>'
});
assert.equal(originalString, JXON.xmlToString(xmlObject));
});
});
});