Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

jslint and Travis CI stuff.

  • Loading branch information...
commit 431d985b0da0af132ce5279e6a1ecab92f1b4964 1 parent 0b72d8d
@SaltwaterC authored
View
10 .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
View
1  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).
View
4 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.
View
171 lib/libxml-to-js.js
@@ -1,7 +1,10 @@
+'use strict';
+
+var lodash = require('lodash');
var libxmljs = require('libxmljs');
/**
- * Simple object merger
+ * Wraps the lodash object merger
*
* @param obj1
* @param obj2
@@ -9,48 +12,29 @@ var libxmljs = require('libxmljs');
*/
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();
}
}
@@ -58,107 +42,125 @@ var libxml2js = function (obj, recurse, namespaces) {
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);
@@ -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 {
View
3  package.json
@@ -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"
View
21 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": [{
@@ -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([
{
@@ -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);
View
17 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);
View
43 tests/ec2-describeimages.js
@@ -1,28 +1,35 @@
+'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 xml = fs.readFileSync('data/ec2-describeimages.xml').toString();
+var callbacks = {
+ parse: 0,
+ parseXpath: 0
+};
-parser(xml, function (err, res) {
- callback = true;
+fs.readFile('data/ec2-describeimages.xml', function (err, xml) {
assert.ifError(err);
- assert.equal(res.imagesSet.item[0].imageId, 'ami-be3adfd7');
- assert.equal(res.imagesSet.item[1].imageId, 'ami-be3adfd9');
+
+ parser(xml, function (err, res) {
+ callbacks.parse++;
+
+ assert.ifError(err);
+ assert.strictEqual(res.imagesSet.item[0].imageId, 'ami-be3adfd7');
+ assert.strictEqual(res.imagesSet.item[1].imageId, 'ami-be3adfd9');
+ });
+
+ parser(xml, '//xmlns:blockDeviceMapping', function (err, res) {
+ callbacks.parseXpath++;
+
+ assert.ifError(err);
+ assert.strictEqual(res.length, 2);
+ assert.strictEqual(res[0].item.deviceName, '/dev/sda');
+ });
});
-parser(xml, '//xmlns:blockDeviceMapping', function (err, res) {
- callbackXPath = true;
- assert.ifError(err);
- assert.strictEqual(res.length, 2);
- assert.strictEqual(res[0].item.deviceName, '/dev/sda');
-});
-
-process.on('exit', function () {
- assert.ok(callback);
- assert.ok(callbackXPath);
-});
+common.teardown(callbacks);
View
32 tests/ec2-describevolumes-large.js
@@ -1,19 +1,31 @@
+'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(fs.readFileSync('data/ec2-describevolumes-large.xml').toString(), function (err, res) {
- callback = true;
+fs.readFile('data/ec2-describevolumes-large.xml', function (err, xml) {
assert.ifError(err);
- for (var i in res.volumeSet.item) {
- var volume = res.volumeSet.item[i];
- assert.equal(volume.volumeId, 'vol-00000000');
- }
+
+ parser(xml, function (err, res) {
+ var i;
+ callbacks.parse++;
+
+ assert.ifError(err);
+ for (i in res.volumeSet.item) {
+ if (res.volumeSet.item.hasOwnProperty(i)) {
+ var volume = res.volumeSet.item[i];
+ assert.strictEqual(volume.volumeId, 'vol-00000000');
+ }
+ }
+ });
});
-process.on('exit', function () {
- assert.ok(callback);
-});
+common.teardown(callbacks);
View
27 tests/element-cdata.js
@@ -1,20 +1,25 @@
+'use strict';
+
var parser = require('../');
var fs = require('fs');
var assert = require('assert');
-var xml = fs.readFileSync('data/element-cdata.xml').toString();
+var common = require('./includes/common.js');
-var callback = false;
+var callbacks = {
+ parse: 0
+};
-parser(xml, function (err, res) {
- callback = true;
- assert.ifError(err);
- assert.strictEqual(res.phrase['#'], 'Home');
- assert.strictEqual(res.phrase['@'].section, 'default');
- assert.strictEqual(res.phrase['@'].code, 'widgethome');
+fs.readFile('data/element-cdata.xml', function (err, xml) {
+ parser(xml, function (err, res) {
+ callbacks.parse++;
+
+ assert.ifError(err);
+ assert.strictEqual(res.phrase['#'], 'Home');
+ assert.strictEqual(res.phrase['@'].section, 'default');
+ assert.strictEqual(res.phrase['@'].code, 'widgethome');
+ });
});
-process.on('exit', function () {
- assert.ok(callback);
-});
+common.teardown(callbacks);
View
22 tests/includes/common.js
@@ -0,0 +1,22 @@
+'use strict';
+
+var util = require('util');
+var assert = require('assert');
+
+/**
+ * The test teardown method
+ * @param {Object} callbacks
+ */
+exports.teardown = function (callbacks) {
+ assert.ok(callbacks instanceof Object);
+ process.on('exit', function (code) {
+ var i;
+ for (i in callbacks) {
+ if (callbacks.hasOwnProperty(i)) {
+ assert.strictEqual(callbacks[i], 1);
+ util.log(util.format('callback %s executed succesfully', i));
+ }
+ }
+ util.log('exiting with code ' + code);
+ });
+};
View
42 tests/namespace.js
@@ -1,23 +1,35 @@
+'use strict';
+
var parser = require('../');
var fs = require('fs');
var assert = require('assert');
-var callback = false;
+var common = require('./includes/common.js');
-parser(fs.readFileSync('data/namespace.xml').toString(), function (err, res) {
- callback = true;
- assert.ifError(err);
- assert.equal(res['@'].xmlns.atom, 'http://www.w3.org/2005/Atom');
- for (var i in res['atom:link']) {
- var atom = res['atom:link'][i];
- assert.equal(atom['@'].rel, 'self');
- assert.equal(atom['@'].type, 'application/rss+xml');
- }
- assert.equal(res['atom:link'][0]['@'].href, 'http://localhost/wordpress/?feed=rss');
- assert.equal(res['atom:link'][1]['@'].href, 'http://localhost/wordpress/?feed=rss2');
-});
+var callbacks = {
+ parse: 0
+};
-process.on('exit', function () {
- assert.ok(callback);
+fs.readFile('data/namespace.xml', function (err, xml) {
+ parser(xml, function (err, res) {
+ var i;
+ callbacks.parse++;
+
+ assert.ifError(err);
+ assert.strictEqual(res['@'].xmlns.atom, 'http://www.w3.org/2005/Atom');
+
+ for (i in res['atom:link']) {
+ if (res['atom:link'].hasOwnProperty(i)) {
+ var atom = res['atom:link'][i];
+ assert.strictEqual(atom['@'].rel, 'self');
+ assert.strictEqual(atom['@'].type, 'application/rss+xml');
+ }
+ }
+
+ assert.strictEqual(res['atom:link'][0]['@'].href, 'http://localhost/wordpress/?feed=rss');
+ assert.strictEqual(res['atom:link'][1]['@'].href, 'http://localhost/wordpress/?feed=rss2');
+ });
});
+
+common.teardown(callbacks);
View
27 tests/root-cdata.js
@@ -1,21 +1,26 @@
+'use strict';
+
var parser = require('../');
var fs = require('fs');
var assert = require('assert');
-var xml = fs.readFileSync('data/root-cdata.xml').toString();
+var common = require('./includes/common.js');
-var callback = false;
+var callbacks = {
+ parse: 0
+};
-parser(xml, function (err, res) {
- callback = true;
+fs.readFile('data/root-cdata.xml', function (err, xml) {
assert.ifError(err);
-
- assert.strictEqual(res['#'], 'Home');
- assert.strictEqual(res['@'].section, 'default');
- assert.strictEqual(res['@'].code, 'widgethome');
+ parser(xml, function (err, res) {
+ callbacks.parse++;
+
+ assert.ifError(err);
+ assert.strictEqual(res['#'], 'Home');
+ assert.strictEqual(res['@'].section, 'default');
+ assert.strictEqual(res['@'].code, 'widgethome');
+ });
});
-process.on('exit', function () {
- assert.ok(callback);
-});
+common.teardown(callbacks);
View
96 tests/test-text.js
@@ -1,58 +1,64 @@
+'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 xml = fs.readFileSync('data/text.xml').toString();
+var callbacks = {
+ parse: 0,
+ parseXpath: 0
+};
-parser(xml, function (err, res) {
- callback = true;
+fs.readFile('data/text.xml', function (err, xml) {
assert.ifError(err);
- assert.deepEqual({
- 'news': [{
+
+ parser(xml, function (err, res) {
+ callbacks.parse++;
+ assert.ifError(err);
+ assert.deepEqual({
+ 'news': [{
+ "auteur": "Bizzard5",
+ "date": "17 Août 2008",
+ "text": {}
+ }, {
+ "auteur": "Little",
+ "date": "18 Août 2007",
+ "text": {
+ "test": "test"
+ }
+ }, {
+ "auteur": "Bizzard5",
+ "date": "17 Août 2008",
+ "text": "C'est un teste"
+ }, {
+ "auteur": "Little",
+ "date": "18 Août 2007",
+ "text": "Allo"
+ }, {
+ "auteur": "Little",
+ "date": "18 Août 2007",
+ "text": {
+ "text": "test"
+ }
+ }]
+ },
+ res);
+ });
+
+ parser(xml, '//nouvelle/news', function (err, res) {
+ callbacks.parseXpath++;
+
+ assert.ifError(err);
+ assert.deepEqual({
"auteur": "Bizzard5",
"date": "17 Août 2008",
"text": {}
- }, {
- "auteur": "Little",
- "date": "18 Août 2007",
- "text": {
- "test": "test"
- }
- }, {
- "auteur": "Bizzard5",
- "date": "17 Août 2008",
- "text": "C'est un teste"
- }, {
- "auteur": "Little",
- "date": "18 Août 2007",
- "text": "Allo"
- }, {
- "auteur": "Little",
- "date": "18 Août 2007",
- "text": {
- "text": "test"
- }
- }]
- },
- res);
+ },
+ res[0]);
+ });
});
-parser(xml, '//nouvelle/news', function (err, res) {
- callbackXPath = true;
- assert.ifError(err);
- assert.deepEqual({
- "auteur": "Bizzard5",
- "date": "17 Août 2008",
- "text": {}
- },
- res[0]);
-});
-
-process.on('exit', function () {
- assert.ok(callback);
- assert.ok(callbackXPath);
-});
+common.teardown(callbacks);
View
51 tests/wordpress-rss2.js
@@ -1,32 +1,39 @@
+'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 xml = fs.readFileSync('data/wordpress-rss2.xml').toString();
+var callbacks = {
+ parse: 0,
+ parseXpath: 0
+};
-parser(xml, function (err, res) {
- callback = true;
+fs.readFile('data/wordpress-rss2.xml', function (err, xml) {
assert.ifError(err);
- assert.equal(res['@'].version, '2.0');
- assert.equal(res['@'].xmlns.atom, 'http://www.w3.org/2005/Atom');
- assert.equal(res.channel.title, 'WordPress');
- assert.equal(res.channel['atom:link']['@'].href, 'http://localhost/wordpress/?feed=rss2');
- assert.equal(res.channel.item.title, 'Hello world!');
- assert.equal(res.channel.item.category, 'Uncategorized'); // CDATA element
+
+ parser(xml, function (err, res) {
+ callbacks.parse++;
+
+ assert.ifError(err);
+ assert.strictEqual(res['@'].version, '2.0');
+ assert.strictEqual(res['@'].xmlns.atom, 'http://www.w3.org/2005/Atom');
+ assert.strictEqual(res.channel.title, 'WordPress');
+ assert.strictEqual(res.channel['atom:link']['@'].href, 'http://localhost/wordpress/?feed=rss2');
+ assert.strictEqual(res.channel.item.title, 'Hello world!');
+ assert.strictEqual(res.channel.item.category, 'Uncategorized'); // CDATA element
+ });
+
+ parser(xml, '//dc:creator', function (err, res) {
+ callbacks.parseXpath++;
+
+ assert.ifError(err);
+ assert.strictEqual(res.length, 1);
+ assert.strictEqual(res[0]['#'], 'admin');
+ });
});
-parser(xml, '//dc:creator', function (err, res) {
- callbackXPath = true;
- assert.ifError(err);
- assert.strictEqual(res.length, 1);
- assert.strictEqual(res[0]['#'], 'admin');
-});
-
-process.on('exit', function () {
- assert.ok(callback);
- assert.ok(callbackXPath);
-});
+common.teardown(callbacks);
Please sign in to comment.
Something went wrong with that request. Please try again.