Skip to content

Commit

Permalink
Browse files Browse the repository at this point in the history
jslint and Travis CI stuff.
  • Loading branch information
SaltwaterC committed Jan 30, 2013
1 parent 0b72d8d commit 431d985
Show file tree
Hide file tree
Showing 15 changed files with 335 additions and 232 deletions.
10 changes: 10 additions & 0 deletions .travis.yml
@@ -0,0 +1,10 @@
before_install:
- sudo apt-get update -qq
- sudo apt-get install -qq bc
- npm -g install jslint
language: node_js
node_js:
- 0.4
- 0.6
- 0.8
- 0.9
1 change: 1 addition & 0 deletions CHANGELOG.md
@@ -1,5 +1,6 @@
## v0.3.11
* Adds more CDATA support [#13](https://github.com/SaltwaterC/libxml-to-js/issues/13). Thanking XApp-Studio for the patch.
* jslint compliant.

## v0.3.10
* Fixes a couple of global variable leaks [#10](https://github.com/SaltwaterC/libxml-to-js/pull/10).
Expand Down
4 changes: 2 additions & 2 deletions README.md
@@ -1,6 +1,6 @@
## About
## About [![build status](https://secure.travis-ci.org/SaltwaterC/libxml-to-js.png?branch=master)](http://travis-ci.org/SaltwaterC/libxml-to-js) ![still maintained](http://stillmaintained.com/SaltwaterC/libxml-to-js.png)

This is a XML to JavaScript object parser. It uses the [libxmljs](https://github.com/polotek/libxmljs) module for the actual XML parsing. It aims to be an easy [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) replacement, but it doesn't follow the xml2js API. I used xml2js for my own needs, but the error reporting of the underlying SAX parser is quite broken. This is how libxml-to-js saw the day light.
This is a XML to JavaScript object parser. It uses the [libxmljs](https://github.com/polotek/libxmljs) module for the actual XML parsing. It aims to be an easy [xml2js](https://github.com/Leonidas-from-XIV/node-xml2js) v1 replacement, but it doesn't follow the xml2js API.

libxml-to-js uses the string parser method of libxmljs. Basically a modified version of the algorithm from [here](http://mscdex.net/code-snippets/) in order to fit the formal specifications of xml2js output.

Expand Down
171 changes: 88 additions & 83 deletions lib/libxml-to-js.js
@@ -1,164 +1,166 @@
'use strict';

var lodash = require('lodash');
var libxmljs = require('libxmljs');

/**
* Simple object merger
* Wraps the lodash object merger
*
* @param obj1
* @param obj2
* @returns obj3
*/
var merge = function (obj1, obj2) {
var obj3 = {};

for (var attrname in obj1) {
obj3[attrname] = obj1[attrname];
}

for (var attrname in obj2) {
obj3[attrname] = obj2[attrname];
}

lodash.merge(obj3, obj1, obj2);
return obj3;
};

/**
* Checks if an object is empty
* @param obj
* @returns bool
*/
var isEmpty = function (obj) {
for (var prop in obj) {
if(obj.hasOwnProperty(prop)) {
return false;
}
}
return true;
}

/**
* The core function of this module
* @param obj
* @param recurse
* @param namespaces
* @returns parsedObj
*
* @param {Object} obj
* @param {Boolean} recurse
* @param {Object} namespaces
* @returns {Object} parsedObj
*/
var libxml2js = function (obj, recurse, namespaces) {
if (namespaces == undefined) {
var i, j, k, atlen, chlen, chtlen, val, old, recValue;

if (namespaces === undefined) {
namespaces = {};
}

if ( ! recurse) {
obj = obj.root();
if (obj.namespace()) {
namespaces['xmlns'] = obj.namespace().href();
namespaces.xmlns = obj.namespace().href();
}
}

var jsobj = {}, children = obj.childNodes(), attributes = obj.attrs();

if (attributes.length > 0) {
jsobj['@'] = {};
for (var i = 0, atlen = attributes.length; i < atlen; i++) {
for (i = 0, atlen = attributes.length; i < atlen; i++) {
jsobj['@'][attributes[i].name()] = attributes[i].value();
}
}

for (var i = 0, chlen = children.length; i < chlen; i++) {
for (i = 0, chlen = children.length; i < chlen; i++) {
// <"text" kludge>
if (children[i].name() == 'text' && children[i].type() == 'text') {
if (children[i].name() === 'text' && children[i].type() === 'text') {
jsobj['#'] = children[i].text().trim();

if (jsobj['#'].match(/^\s*$/)) {
delete(jsobj['#']);
}
for (var j = 0, chtlen = children[i].childNodes().length; j < chtlen; j++) {
if (children[i].child(j).name() == 'text') {

for (j = 0, chtlen = children[i].childNodes().length; j < chtlen; j++) {
if (children[i].child(j).name() === 'text') {
var text = {}, textattrs = children[i].child(j).attrs();
text['#'] = children[i].child(j).text();
if (textattrs.length > 0) {
text['@'] = {};
}

for (var k = 0, atlen = textattrs.length; k < atlen; i++) {
for (k = 0, atlen = textattrs.length; k < atlen; i++) {
text['@'][textattrs[k].name()] = textattrs[k].value();
}
jsobj['text'] = text;

jsobj.text = text;
break; // only allow one "<text></text>" element for now
}
}
continue;
} else if (children[i].type()=='cdata') {
var val = children[i].toString().trim();
val = val.replace(/^\<\!\[CDATA\[/, '').replace(/\]\]\>$/, '');
} else if (children[i].type() === 'cdata') {
val = children[i].toString().trim();
val = val.replace(/^<\!\[CDATA\[/, '').replace(/\]\]\>$/, '');
jsobj['#']=val;
}

// </"text" kludge>
var ns = '';
var namespace = children[i].namespace();
if (namespace && namespace.prefix() != null) {
ns = namespace.prefix() + ':';
namespaces[namespace.prefix()] = namespace.href();
}
var key = ns + children[i].name();

if (typeof jsobj[key] == 'undefined') {
if (children[i].childNodes().length == 1 && children[i].attrs().length == 0 && (children[i].childNodes()[0].type() == 'text' || children[i].childNodes()[0].type() == 'cdata')) {
var val = children[i].childNodes()[0].toString().trim();
if (children[i].childNodes()[0].type() == 'cdata') {
val = val.replace(/^\<\!\[CDATA\[/, '').replace(/\]\]\>$/, '');
} else {
// </"text" kludge>
var ns = '';
var namespace = children[i].namespace();

if (namespace && namespace.prefix() !== null) {
ns = namespace.prefix() + ':';
namespaces[namespace.prefix()] = namespace.href();
}
var key = ns + children[i].name();

if (typeof jsobj[key] === 'undefined') {
if (children[i].childNodes().length === 1 && children[i].attrs().length === 0 && (children[i].childNodes()[0].type() === 'text' || children[i].childNodes()[0].type() === 'cdata')) {
val = children[i].childNodes()[0].toString().trim();

if (children[i].childNodes()[0].type() === 'cdata') {
val = val.replace(/^<\!\[CDATA\[/, '').replace(/\]\]\>$/, '');
}

jsobj[key] = val;
} else {
if (children[i].name() !== undefined) {
recValue = libxml2js(children[i], true, namespaces);
jsobj[key] = recValue.jsobj;
merge(namespaces, recValue.namespaces);
}
}
jsobj[key] = val;
} else {
if (children[i].name() !== undefined) {
var recValue = libxml2js(children[i], true, namespaces);
jsobj[key] = recValue.jsobj;
merge(namespaces, recValue.namespaces);
if (typeof jsobj[key] === 'string') {
old = jsobj[key];
jsobj[key] = [];
jsobj[key].push({'#': old});
} else if (typeof jsobj[key] === 'object' && jsobj[key].push === undefined) {
old = jsobj[key];
jsobj[key] = [];
jsobj[key].push(old);
}
}
} else {
if (typeof jsobj[key] == 'string') {
var old = jsobj[key];
jsobj[key] = [];
jsobj[key].push({'#': old});
} else if (typeof jsobj[key] == 'object' && ! ('push' in jsobj[key])) {
var old = jsobj[key];
jsobj[key] = [];
jsobj[key].push(old);
}
var recValue = libxml2js(children[i], true, namespaces);
jsobj[key].push(recValue.jsobj);
merge(namespaces, recValue.namespaces);

recValue = libxml2js(children[i], true, namespaces);
jsobj[key].push(recValue.jsobj);
merge(namespaces, recValue.namespaces);
}
}
}

if ( ! recurse) {
if (namespaces && ! isEmpty(namespaces)) {
if (namespaces && ! lodash.isEmpty(namespaces)) {
if ( ! jsobj['@']) {
jsobj['@'] = {};
}
jsobj['@'].xmlns = namespaces;
}

return jsobj;
} else {
return {
jsobj: jsobj,
namespaces: namespaces
}
}

return {
jsobj: jsobj,
namespaces: namespaces
};
};

/**
* The module wrapper, with XPath support
*
* @param {String} xml
* @param {String} xpath
* @param {Function} callback
*/
module.exports = function (xml, xpath, callback) {
if ( ! callback) {
callback = xpath;
xpath = null;
}

var xmlDocument, jsDocument, selected = [], xmlns = null, error, result;

try {
xmlDocument = libxmljs.parseXmlString(xml);
jsDocument = libxml2js(xmlDocument);

if (jsDocument['@'] && jsDocument['@'].xmlns) {
xmlns = jsDocument['@'].xmlns;
}

if ( !! xpath) {
xmlDocument.find(xpath, xmlns).forEach(function(item) {
selected.push(libxml2js(item, true).jsobj);
Expand All @@ -170,14 +172,17 @@ module.exports = function (xml, xpath, callback) {
} catch (err) {
var message = 'libxml error';
var code = 0;

if (err && err.message) {
message = err.message;
}

error = new Error(message);
if (err && err.code) {
error.code = err.code;
}
}

if ( ! error) {
callback(null, result);
} else {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Expand Up @@ -4,7 +4,8 @@
"main": "./lib/libxml-to-js.js",
"description": "XML to JavaScript object parser based on libxmljs",
"dependencies": {
"libxmljs": ">=0.5.x"
"libxmljs": ">=0.5.x",
"lodash": "*"
},
"engines": {
"node": ">=0.4.x"
Expand Down
21 changes: 13 additions & 8 deletions tests/attributes.js
@@ -1,15 +1,22 @@
'use strict';

var parser = require('../');

var fs = require('fs');
var assert = require('assert');

var callback = false;
var callbackXPath = false;
var common = require('./includes/common.js');

var callbacks = {
parse: 0,
parseXpath: 0
};

var xml = '<thing><real id="width">300</real><real id="height">200</real></thing>';

parser(xml, function (err, res) {
callback = true;
callbacks.parse++;

assert.ifError(err);
assert.deepEqual({
"real": [{
Expand All @@ -28,7 +35,8 @@ parser(xml, function (err, res) {
});

parser(xml, '//thing/real', function (err, res) {
callbackXPath = true;
callbacks.parseXpath++;

assert.ifError(err);
assert.deepEqual([
{
Expand All @@ -46,7 +54,4 @@ parser(xml, '//thing/real', function (err, res) {
res);
});

process.on('exit', function () {
assert.ok(callback);
assert.ok(callbackXPath);
});
common.teardown(callbacks);
17 changes: 11 additions & 6 deletions tests/broken.js
@@ -1,16 +1,21 @@
'use strict';

var parser = require('../');

var fs = require('fs');
var assert = require('assert');

var callback = false;
var common = require('./includes/common.js');

var callbacks = {
parse: 0
};

parser('This is a broken XML file.', function (err, res) {
callback = true;
callbacks.parse++;

assert.ok(err instanceof Error);
assert.equal(err.code, 4);
assert.strictEqual(err.code, 4);
});

process.on('exit', function () {
assert.ok(callback);
});
common.teardown(callbacks);

0 comments on commit 431d985

Please sign in to comment.