[Pkg-javascript-commits] [node-xml2js] 01/12: Imported Upstream version 0.2.8
Jérémy Lal
kapouer at alioth.debian.org
Mon Sep 23 00:32:29 UTC 2013
This is an automated email from the git hooks/post-receive script.
kapouer pushed a commit to branch master
in repository node-xml2js.
commit 132db0b84817852690e550cee2dc0d397d8415f1
Author: Jérémy Lal <kapouer at melix.org>
Date: Mon Sep 23 01:53:51 2013 +0200
Imported Upstream version 0.2.8
---
.npmignore => .gitignore | 0
.npmignore | 4 +
.travis.yml | 5 +
LICENSE | 2 +-
README.md | 171 ++++++++++++++++++----
lib/xml2js.js | 211 ++++++++++++++++++++++-----
package.json | 20 ++-
src/xml2js.coffee | 171 ++++++++++++++++++----
test/fixtures/sample.xml | 30 +++-
test/xml2js.test.coffee | 359 +++++++++++++++++++++++++++++++++-------------
10 files changed, 772 insertions(+), 201 deletions(-)
diff --git a/.npmignore b/.gitignore
similarity index 100%
copy from .npmignore
copy to .gitignore
diff --git a/.npmignore b/.npmignore
index c3c1388..ef7b9b9 100644
--- a/.npmignore
+++ b/.npmignore
@@ -1,2 +1,6 @@
*.swp
+.idea
node_modules
+src
+test
+Cakefile
\ No newline at end of file
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..755a6b7
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,5 @@
+language: node_js
+
+node_js:
+ - "0.8"
+ - "0.10"
diff --git a/LICENSE b/LICENSE
index 83c3140..e3b4222 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,4 +1,4 @@
-Copyright 2010, 2011. All rights reserved.
+Copyright 2010, 2011, 2012, 2013. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to
diff --git a/README.md b/README.md
index 2765d8c..b009724 100644
--- a/README.md
+++ b/README.md
@@ -23,16 +23,46 @@ install xml2js` which will download xml2js and all dependencies.
Usage
=====
-This will have to do, unless you're looking for some fancy extensive
-documentation. If you're looking for every single option and usage, see the
-unit tests.
+No extensive tutorials required because you are a smart developer! The task of
+parsing XML should be an easy one, so let's make it so! Here's some examples.
+
+Shoot-and-forget usage
+----------------------
+
+You want to parse XML as simple and easy as possible? It's dangerous to go
+alone, take this:
+
+```javascript
+var parseString = require('xml2js').parseString;
+var xml = "<root>Hello xml2js!</root>"
+parseString(xml, function (err, result) {
+ console.dir(result);
+});
+```
+
+Can't get easier than this, right? This works starting with `xml2js` 0.2.3.
+With CoffeeScript it looks like this:
+
+```coffeescript
+parseString = require('xml2js').parseString
+xml = "<root>Hello xml2js!</root>"
+parseString xml, (err, result) ->
+ console.dir result
+```
+
+If you need some special options, fear not, `xml2js` supports a number of
+options (see below), you can specify these as second argument:
+
+```javascript
+parseString(xml, {trim: true}, function (err, result) {
+});
+```
Simple as pie usage
-------------------
-The simplest way to use it, is to use the optional callback interface added in
-0.1.11. That's right, if you have been using xml-simple or a home-grown
-wrapper, this is for you:
+That's right, if you have been using xml-simple or a home-grown
+wrapper, this is was added in 0.1.11 just for you:
```javascript
var fs = require('fs'),
@@ -47,8 +77,34 @@ fs.readFile(__dirname + '/foo.xml', function(err, data) {
});
```
-Look ma, no event listeners! Alternatively you can still use the traditional
-`addListener` variant:
+Look ma, no event listeners!
+
+You can also use `xml2js` from
+[CoffeeScript](http://jashkenas.github.com/coffee-script/), further reducing
+the clutter:
+
+```coffeescript
+fs = require 'fs',
+xml2js = require 'xml2js'
+
+parser = new xml2js.Parser()
+fs.readFile __dirname + '/foo.xml', (err, data) ->
+ parser.parseString data, (err, result) ->
+ console.dir result
+ console.log 'Done.'
+```
+
+But what happens if you forget the `new` keyword to create a new `Parser`? In
+the middle of a nightly coding session, it might get lost, after all. Worry
+not, we got you covered! Starting with 0.2.8 you can also leave it out, in
+which case `xml2js` will helpfully add it for you, no bad surprises and
+inexplicable bugs!
+
+"Traditional" usage
+-------------------
+
+Alternatively you can still use the traditional `addListener` variant that was
+supported since forever:
```javascript
var fs = require('fs'),
@@ -64,20 +120,13 @@ fs.readFile(__dirname + '/foo.xml', function(err, data) {
});
```
-You can also use xml2js from
-[CoffeeScript](http://jashkenas.github.com/coffee-script/), further reducing
-the clutter:
-
-```coffeescript
-fs = require 'fs',
-xml2js = require 'xml2js'
+If you want to parse multiple files, you have multiple possibilites:
-parser = new xml2js.Parser()
-fs.readFile __dirname + '/foo.xml', (err, data) ->
- parser.parseString data, (err, result) ->
- console.dir result
- console.log 'Done.'
-```
+ * You can create one `xml2js.Parser` per file. That's the recommended one
+ and is promised to always *just work*.
+ * You can call `reset()` on your parser object.
+ * You can hope everything goes well anyway. This behaviour is not
+ guaranteed work always, if ever. Use option #1 if possible. Thanks!
So you wanna some JSON?
-----------------------
@@ -111,29 +160,89 @@ Apart from the default settings, there is a number of options that can be
specified for the parser. Options are specified by ``new Parser({optionName:
value})``. Possible options are:
+ * `attrkey` (default: `$`): Prefix that is used to access the attributes.
+ Version 0.1 default was `@`.
+ * `charkey` (default: `_`): Prefix that is used to access the character
+ content. Version 0.1 default was `#`.
* `explicitCharkey` (default: `false`)
- * `trim` (default: `true`): Trim the whitespace at the beginning and end of
+ * `trim` (default: `false`): Trim the whitespace at the beginning and end of
text nodes.
- * `normalize` (default: `true`): Trim whitespaces inside text nodes.
- * `explicitRoot` (default: `false`): Set this if you want to get the root
+ * `normalizeTags` (default: `false`): Normalize all tag names to lowercase.
+ * `normalize` (default: `false`): Trim whitespaces inside text nodes.
+ * `explicitRoot` (default: `true`): Set this if you want to get the root
node in the resulting object.
* `emptyTag` (default: `undefined`): what will the value of empty nodes be.
Default is `{}`.
- * `explicitArray` (default: `false`): Always put child nodes in an array if
+ * `explicitArray` (default: `true`): Always put child nodes in an array if
true; otherwise an array is created only if there is more than one.
* `ignoreAttrs` (default: `false`): Ignore all XML attributes and only create
text nodes.
* `mergeAttrs` (default: `false`): Merge attributes and child elements as
properties of the parent, instead of keying attributes off a child
attribute object. This option is ignored if `ignoreAttrs` is `false`.
+ * `validator` (default `null`): You can specify a callable that validates
+ the resulting structure somehow, however you want. See unit tests
+ for an example.
+ * `xmlns` (default `false`): Give each element a field usually called '$ns'
+ (the first character is the same as attrkey) that contains its local name
+ and namespace URI.
+ * `explicitChildren` (default `false`): Put child elements to separate
+ property. Doesn't work with `mergeAttrs = true`. If element has no children
+ then "children" won't be created. Added in 0.2.5.
+ * `childkey` (default `$$`): Prefix that is used to access child elements if
+ `explicitChildren` is set to `true`. Added in 0.2.5.
+ * `charsAsChildren` (default `false`): Determines whether chars should be
+ considered children if `explicitChildren` is on. Added in 0.2.5.
+ * `async` (default `false`): Should the callbacks be async? This *might* be
+ an incompatible change if your code depends on sync execution of callbacks.
+ xml2js 0.3 might change this default, so the recommendation is to not
+ depend on sync execution anyway. Added in 0.2.6.
+ * `strict` (default `true`): Set sax-js to strict or non-strict parsing mode.
+ Defaults to `true` which is *highly* recommended, since parsing HTML which
+ is not well-formed XML might yield just about anything. Added in 0.2.7.
+
+Updating to new version
+=======================
+
+Version 0.2 changed the default parsing settings, but version 0.1.14 introduced
+the default settings for version 0.2, so these settings can be tried before the
+migration.
+
+```javascript
+var xml2js = require('xml2js');
+var parser = new xml2js.Parser(xml2js.defaults["0.2"]);
+```
-These default settings are for backward-compatibility (and might change in the
-future). For the most 'clean' parsing, you should disable `normalize` and
-`trimming` and enable `explicitRoot`.
+To get the 0.1 defaults in version 0.2 you can just use
+`xml2js.defaults["0.1"]` in the same place. This provides you with enough time
+to migrate to the saner way of parsing in xml2js 0.2. We try to make the
+migration as simple and gentle as possible, but some breakage cannot be
+avoided.
+
+So, what exactly did change and why? In 0.2 we changed some defaults to parse
+the XML in a more universal and sane way. So we disabled `normalize` and `trim`
+so xml2js does not cut out any text content. You can reenable this at will of
+course. A more important change is that we return the root tag in the resulting
+JavaScript structure via the `explicitRoot` setting, so you need to access the
+first element. This is useful for anybody who wants to know what the root node
+is and preserves more information. The last major change was to enable
+`explicitArray`, so everytime it is possible that one might embed more than one
+sub-tag into a tag, xml2js >= 0.2 returns an array even if the array just
+includes one element. This is useful when dealing with APIs that return
+variable amounts of subtags.
Running tests, development
==========================
-The development requirements are handled by npm, you just need to install
-them. We also have a number of unit tests, they can be run using `zap`
-directly from the project root.
+[![Build Status](https://secure.travis-ci.org/Leonidas-from-XIV/node-xml2js.png?branch=master)](https://travis-ci.org/Leonidas-from-XIV/node-xml2js)
+
+The development requirements are handled by npm, you just need to install them.
+We also have a number of unit tests, they can be run using `npm test` directly
+from the project root. This runs zap to discover all the tests and execute
+them.
+
+If you like to contribute, keep in mind that xml2js is written in CoffeeScript,
+so don't develop on the JavaScript files that are checked into the repository
+for convenience reasons. Also, please write some unit test to check your
+behaviour and if it is some user-facing thing, add some documentation to this
+README, so people will know it exists. Thanks in advance!
diff --git a/lib/xml2js.js b/lib/xml2js.js
index 0938746..bec8a21 100644
--- a/lib/xml2js.js
+++ b/lib/xml2js.js
@@ -1,6 +1,9 @@
+// Generated by CoffeeScript 1.6.3
(function() {
- var events, isEmpty, sax;
- var __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; }, __hasProp = Object.prototype.hasOwnProperty, __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor; child.__super__ = parent.prototype; return child; };
+ var events, isEmpty, sax,
+ __hasProp = {}.hasOwnProperty,
+ __extends = function(child, parent) { for (var key in parent) { if (__hasProp.call(parent, key)) child[key] = parent[key]; } function ctor() { this.constructor = child; } ctor.prototype = parent.prototype; child.prototype = new ctor(); child.__super__ = parent.prototype; return child; },
+ __bind = function(fn, me){ return function(){ return fn.apply(me, arguments); }; };
sax = require('sax');
@@ -10,39 +13,94 @@
return typeof thing === "object" && (thing != null) && Object.keys(thing).length === 0;
};
- exports.Parser = (function() {
+ exports.defaults = {
+ "0.1": {
+ explicitCharkey: false,
+ trim: true,
+ normalize: true,
+ normalizeTags: false,
+ attrkey: "@",
+ charkey: "#",
+ explicitArray: false,
+ ignoreAttrs: false,
+ mergeAttrs: false,
+ explicitRoot: false,
+ validator: null,
+ xmlns: false,
+ explicitChildren: false,
+ childkey: '@@',
+ charsAsChildren: false,
+ async: false,
+ strict: true
+ },
+ "0.2": {
+ explicitCharkey: false,
+ trim: false,
+ normalize: false,
+ normalizeTags: false,
+ attrkey: "$",
+ charkey: "_",
+ explicitArray: true,
+ ignoreAttrs: false,
+ mergeAttrs: false,
+ explicitRoot: true,
+ validator: null,
+ xmlns: false,
+ explicitChildren: false,
+ childkey: '$$',
+ charsAsChildren: false,
+ async: false,
+ strict: true
+ }
+ };
+
+ exports.ValidationError = (function(_super) {
+ __extends(ValidationError, _super);
+
+ function ValidationError(message) {
+ this.message = message;
+ }
- __extends(Parser, events.EventEmitter);
+ return ValidationError;
+
+ })(Error);
+
+ exports.Parser = (function(_super) {
+ __extends(Parser, _super);
function Parser(opts) {
this.parseString = __bind(this.parseString, this);
this.reset = __bind(this.reset, this);
- var key, value;
- this.options = {
- explicitCharkey: false,
- trim: true,
- normalize: true,
- attrkey: "@",
- charkey: "#",
- explicitArray: false,
- ignoreAttrs: false,
- mergeAttrs: false
- };
+ var key, value, _ref;
+ if (!(this instanceof exports.Parser)) {
+ return new exports.Parser(opts);
+ }
+ this.options = {};
+ _ref = exports.defaults["0.2"];
+ for (key in _ref) {
+ if (!__hasProp.call(_ref, key)) continue;
+ value = _ref[key];
+ this.options[key] = value;
+ }
for (key in opts) {
if (!__hasProp.call(opts, key)) continue;
value = opts[key];
this.options[key] = value;
}
+ if (this.options.xmlns) {
+ this.options.xmlnskey = this.options.attrkey + "ns";
+ }
this.reset();
}
Parser.prototype.reset = function() {
- var attrkey, charkey, err, stack;
- var _this = this;
+ var attrkey, charkey, err, ontext, stack,
+ _this = this;
this.removeAllListeners();
- this.saxParser = sax.parser(true, {
+ this.saxParser = sax.parser(this.options.strict, {
trim: false,
- normalize: false
+ normalize: false,
+ xmlns: this.options.xmlns
});
err = false;
this.saxParser.onerror = function(error) {
@@ -64,7 +122,9 @@
_ref = node.attributes;
for (key in _ref) {
if (!__hasProp.call(_ref, key)) continue;
- if (!(attrkey in obj) && !_this.options.mergeAttrs) obj[attrkey] = {};
+ if (!(attrkey in obj) && !_this.options.mergeAttrs) {
+ obj[attrkey] = {};
+ }
if (_this.options.mergeAttrs) {
obj[key] = node.attributes[key];
} else {
@@ -72,19 +132,30 @@
}
}
}
- obj["#name"] = node.name;
+ obj["#name"] = _this.options.normalizeTags ? node.name.toLowerCase() : node.name;
+ if (_this.options.xmlns) {
+ obj[_this.options.xmlnskey] = {
+ uri: node.uri,
+ local: node.local
+ };
+ }
return stack.push(obj);
};
this.saxParser.onclosetag = function() {
- var nodeName, obj, old, s;
+ var cdata, emptyStr, node, nodeName, obj, old, s, xpath;
obj = stack.pop();
nodeName = obj["#name"];
delete obj["#name"];
+ cdata = obj.cdata;
+ delete obj.cdata;
s = stack[stack.length - 1];
- if (obj[charkey].match(/^\s*$/)) {
+ if (obj[charkey].match(/^\s*$/) && !cdata) {
+ emptyStr = obj[charkey];
delete obj[charkey];
} else {
- if (_this.options.trim) obj[charkey] = obj[charkey].trim();
+ if (_this.options.trim) {
+ obj[charkey] = obj[charkey].trim();
+ }
if (_this.options.normalize) {
obj[charkey] = obj[charkey].replace(/\s{2,}/g, " ").trim();
}
@@ -92,8 +163,40 @@
obj = obj[charkey];
}
}
- if (_this.options.emptyTag !== void 0 && isEmpty(obj)) {
- obj = _this.options.emptyTag;
+ if (isEmpty(obj)) {
+ obj = _this.options.emptyTag !== void 0 ? _this.options.emptyTag : emptyStr;
+ }
+ if (_this.options.validator != null) {
+ xpath = "/" + ((function() {
+ var _i, _len, _results;
+ _results = [];
+ for (_i = 0, _len = stack.length; _i < _len; _i++) {
+ node = stack[_i];
+ _results.push(node["#name"]);
+ }
+ return _results;
+ })()).concat(nodeName).join("/");
+ try {
+ obj = _this.options.validator(xpath, s && s[nodeName], obj);
+ } catch (_error) {
+ err = _error;
+ _this.emit("error", err);
+ }
+ }
+ if (_this.options.explicitChildren && !_this.options.mergeAttrs && typeof obj === 'object') {
+ node = {};
+ if (_this.options.attrkey in obj) {
+ node[_this.options.attrkey] = obj[_this.options.attrkey];
+ delete obj[_this.options.attrkey];
+ }
+ if (!_this.options.charsAsChildren && _this.options.charkey in obj) {
+ node[_this.options.charkey] = obj[_this.options.charkey];
+ delete obj[_this.options.charkey];
+ }
+ if (Object.getOwnPropertyNames(obj).length > 0) {
+ node[_this.options.childkey] = obj;
+ }
+ obj = node;
}
if (stack.length > 0) {
if (!_this.options.explicitArray) {
@@ -107,7 +210,9 @@
return s[nodeName].push(obj);
}
} else {
- if (!(s[nodeName] instanceof Array)) s[nodeName] = [];
+ if (!(s[nodeName] instanceof Array)) {
+ s[nodeName] = [];
+ }
return s[nodeName].push(obj);
}
} else {
@@ -120,10 +225,21 @@
return _this.emit("end", _this.resultObject);
}
};
- return this.saxParser.ontext = this.saxParser.oncdata = function(text) {
+ ontext = function(text) {
var s;
s = stack[stack.length - 1];
- if (s) return s[charkey] += text;
+ if (s) {
+ s[charkey] += text;
+ return s;
+ }
+ };
+ this.saxParser.ontext = ontext;
+ return this.saxParser.oncdata = function(text) {
+ var s;
+ s = ontext(text);
+ if (s) {
+ return s.cdata = true;
+ }
};
};
@@ -131,11 +247,23 @@
if ((cb != null) && typeof cb === "function") {
this.on("end", function(result) {
this.reset();
- return cb(null, result);
+ if (this.options.async) {
+ return process.nextTick(function() {
+ return cb(null, result);
+ });
+ } else {
+ return cb(null, result);
+ }
});
this.on("error", function(err) {
this.reset();
- return cb(err);
+ if (this.options.async) {
+ return process.nextTick(function() {
+ return cb(err);
+ });
+ } else {
+ return cb(err);
+ }
});
}
if (str.toString().trim() === '') {
@@ -147,6 +275,25 @@
return Parser;
- })();
+ })(events.EventEmitter);
+
+ exports.parseString = function(str, a, b) {
+ var cb, options, parser;
+ if (b != null) {
+ if (typeof b === 'function') {
+ cb = b;
+ }
+ if (typeof a === 'object') {
+ options = a;
+ }
+ } else {
+ if (typeof a === 'function') {
+ cb = a;
+ }
+ options = {};
+ }
+ parser = new exports.Parser(options);
+ return parser.parseString(str, cb);
+ };
}).call(this);
diff --git a/package.json b/package.json
index bed779d..6d5929c 100644
--- a/package.json
+++ b/package.json
@@ -3,7 +3,7 @@
"description" : "Simple XML to JavaScript object converter.",
"keywords" : ["xml", "json"],
"homepage" : "https://github.com/Leonidas-from-XIV/node-xml2js",
- "version" : "0.1.13",
+ "version" : "0.2.8",
"author" : "Marek Kubica <marek at xivilization.net> (http://xivilization.net)",
"contributors" : [
"maqr <maqr.lollerskates at gmail.com> (https://github.com/maqr)",
@@ -17,21 +17,31 @@
"Marcelo Diniz <marudiniz at gmail.com> (https://github.com/mdiniz)",
"Michael Hart (https://github.com/mhart)",
"Zachary Scott <zachary at zacharyscott.net> (http://zacharyscott.net/)",
- "Raoul Millais (https://github.com/raoulmillais)"
+ "Raoul Millais (https://github.com/raoulmillais)",
+ "Salsita Software (http://www.salsitasoft.com/)",
+ "Mike Schilling <mike at emotive.com> (http://www.emotive.com/)",
+ "Jackson Tian <shyvo1987 at gmail.com> (http://weibo.com/shyvo)",
+ "Mikhail Zyatin <mikhail.zyatin at gmail.com> (https://github.com/Sitin)",
+ "Chris Tavares <ctavares at microsoft.com> (https://github.com/christav)",
+ "Frank Xu <yyfrankyy at gmail.com> (http://f2e.us/)"
],
"main" : "./lib/xml2js",
"directories" : {
"lib": "./lib"
},
+ "scripts" : {
+ "test": "zap"
+ },
"repository" : {
"type" : "git",
"url" : "https://github.com/Leonidas-from-XIV/node-xml2js.git"
},
"dependencies" : {
- "sax" : ">=0.1.1"
+ "sax" : "0.5.x"
},
"devDependencies" : {
- "coffee-script" : ">=1.0.1",
- "zap" : ">=0.2.3"
+ "coffee-script" : ">=1.6.3",
+ "zap" : ">=0.2.5",
+ "docco" : ">=0.6.2"
}
}
diff --git a/src/xml2js.coffee b/src/xml2js.coffee
index d0ebf9e..78cde6d 100644
--- a/src/xml2js.coffee
+++ b/src/xml2js.coffee
@@ -5,28 +5,72 @@ events = require 'events'
isEmpty = (thing) ->
return typeof thing is "object" && thing? && Object.keys(thing).length is 0
+exports.defaults =
+ "0.1":
+ explicitCharkey: false
+ trim: true
+ # normalize implicates trimming, just so you know
+ normalize: true
+ # normalize tag names to lower case
+ normalizeTags: false
+ # set default attribute object key
+ attrkey: "@"
+ # set default char object key
+ charkey: "#"
+ # always put child nodes in an array
+ explicitArray: false
+ # ignore all attributes regardless
+ ignoreAttrs: false
+ # merge attributes and child elements onto parent object. this may
+ # cause collisions.
+ mergeAttrs: false
+ explicitRoot: false
+ validator: null
+ xmlns : false
+ # fold children elements into dedicated property (works only in 0.2)
+ explicitChildren: false
+ childkey: '@@'
+ charsAsChildren: false
+ # callbacks are async? not in 0.1 mode
+ async: false
+ strict: true
+
+ "0.2":
+ explicitCharkey: false
+ trim: false
+ normalize: false
+ normalizeTags: false
+ attrkey: "$"
+ charkey: "_"
+ explicitArray: true
+ ignoreAttrs: false
+ mergeAttrs: false
+ explicitRoot: true
+ validator: null
+ xmlns : false
+ explicitChildren: false
+ childkey: '$$'
+ charsAsChildren: false
+ # not async in 0.2 mode either
+ async: false
+ strict: true
+
+class exports.ValidationError extends Error
+ constructor: (message) ->
+ @message = message
+
class exports.Parser extends events.EventEmitter
constructor: (opts) ->
- # default options. for compatibility's sake set to some
- # sub-optimal settings. might change in the future.
- @options =
- explicitCharkey: false
- trim: true
- # normalize implicates trimming, just so you know
- normalize: true
- # set default attribute object key
- attrkey: "@"
- # set default char object key
- charkey: "#"
- # always put child nodes in an array
- explicitArray: false
- # ignore all attributes regardless
- ignoreAttrs: false
- # merge attributes and child elements onto parent object. this may
- # cause collisions.
- mergeAttrs: false
+ # if this was called without 'new', create an instance with new and return
+ return new exports.Parser opts unless @ instanceof exports.Parser
+ # copy this versions default options
+ @options = {}
+ @options[key] = value for own key, value of exports.defaults["0.2"]
# overwrite them with the specified options, if any
@options[key] = value for own key, value of opts
+ # define the key used for namespaces
+ if @options.xmlns
+ @options.xmlnskey = @options.attrkey + "ns"
@reset()
@@ -36,11 +80,12 @@ class exports.Parser extends events.EventEmitter
@removeAllListeners()
# make the SAX parser. tried trim and normalize, but they are not
# very helpful
- @saxParser = sax.parser true, {
+ @saxParser = sax.parser @options.strict, {
trim: false,
- normalize: false
+ normalize: false,
+ xmlns: @options.xmlns
}
-
+
# emit one error event if the sax parser fails. this is mostly a hack, but
# the sax parser isn't state of the art either.
err = false
@@ -48,7 +93,7 @@ class exports.Parser extends events.EventEmitter
if ! err
err = true
@emit "error", error
-
+
# always use the '#' key, even if there are no subkeys
# setting this property by and is deprecated, yet still supported.
# better pass it as explicitCharkey option to the constructor
@@ -72,17 +117,23 @@ class exports.Parser extends events.EventEmitter
obj[attrkey][key] = node.attributes[key]
# need a place to store the node name
- obj["#name"] = node.name
+ obj["#name"] = if @options.normalizeTags then node.name.toLowerCase() else node.name
+ if (@options.xmlns)
+ obj[@options.xmlnskey] = {uri: node.uri, local: node.local}
stack.push obj
-
+
@saxParser.onclosetag = =>
obj = stack.pop()
nodeName = obj["#name"]
delete obj["#name"]
+ cdata = obj.cdata
+ delete obj.cdata
+
s = stack[stack.length - 1]
# remove the '#' key altogether if it's blank
- if obj[charkey].match(/^\s*$/)
+ if obj[charkey].match(/^\s*$/) and not cdata
+ emptyStr = obj[charkey]
delete obj[charkey]
else
obj[charkey] = obj[charkey].trim() if @options.trim
@@ -92,8 +143,35 @@ class exports.Parser extends events.EventEmitter
if Object.keys(obj).length == 1 and charkey of obj and not @EXPLICIT_CHARKEY
obj = obj[charkey]
- if @options.emptyTag != undefined && isEmpty obj
- obj = @options.emptyTag
+ if (isEmpty obj)
+ obj = if @options.emptyTag != undefined
+ @options.emptyTag
+ else
+ emptyStr
+
+ if @options.validator?
+ xpath = "/" + (node["#name"] for node in stack).concat(nodeName).join("/")
+ try
+ obj = @options.validator(xpath, s and s[nodeName], obj)
+ catch err
+ @emit "error", err
+
+ # put children into <childkey> property and unfold chars if necessary
+ if @options.explicitChildren and not @options.mergeAttrs and typeof obj is 'object'
+ node = {}
+ # separate attributes
+ if @options.attrkey of obj
+ node[@options.attrkey] = obj[@options.attrkey]
+ delete obj[@options.attrkey]
+ # separate char data
+ if not @options.charsAsChildren and @options.charkey of obj
+ node[@options.charkey] = obj[@options.charkey]
+ delete obj[@options.charkey]
+
+ if Object.getOwnPropertyNames(obj).length > 0
+ node[@options.childkey] = obj
+
+ obj = node
# check whether we closed all the open tags
if stack.length > 0
@@ -121,22 +199,55 @@ class exports.Parser extends events.EventEmitter
@resultObject = obj
@emit "end", @resultObject
- @saxParser.ontext = @saxParser.oncdata = (text) =>
+ ontext = (text) =>
s = stack[stack.length - 1]
if s
s[charkey] += text
+ s
+
+ @saxParser.ontext = ontext
+ @saxParser.oncdata = (text) =>
+ s = ontext text
+ if s
+ s.cdata = true
parseString: (str, cb) =>
if cb? and typeof cb is "function"
@on "end", (result) ->
@reset()
- cb null, result
+ if @options.async
+ process.nextTick ->
+ cb null, result
+ else
+ cb null, result
@on "error", (err) ->
@reset()
- cb err
+ if @options.async
+ process.nextTick ->
+ cb err
+ else
+ cb err
if str.toString().trim() is ''
@emit "end", null
return true
@saxParser.write str.toString()
+
+exports.parseString = (str, a, b) ->
+ # let's determine what we got as arguments
+ if b?
+ if typeof b == 'function'
+ cb = b
+ if typeof a == 'object'
+ options = a
+ else
+ # well, b is not set, so a has to be a callback
+ if typeof a == 'function'
+ cb = a
+ # and options should be empty - default
+ options = {}
+
+ # the rest is super-easy
+ parser = new exports.Parser options
+ parser.parseString str, cb
diff --git a/test/fixtures/sample.xml b/test/fixtures/sample.xml
index 6aaf0f8..db2e3e8 100644
--- a/test/fixtures/sample.xml
+++ b/test/fixtures/sample.xml
@@ -1,7 +1,9 @@
<sample>
<chartest desc="Test for CHARs">Character data here!</chartest>
<cdatatest desc="Test for CDATA" misc="true"><![CDATA[CDATA here!]]></cdatatest>
+ <cdatawhitespacetest desc="Test for CDATA with whitespace" misc="true"><![CDATA[ ]]></cdatawhitespacetest>
<nochartest desc="No data" misc="false" />
+ <nochildrentest desc="No data" misc="false" />
<whitespacetest desc="Test for normalizing and trimming">
Line One
Line Two
@@ -23,12 +25,28 @@
<item><subitem>Foo.</subitem><subitem>Bar.</subitem></item>
</arraytest>
<emptytest/>
+ <tagcasetest>
+ <tAg>something</tAg>
+ <TAG>something else</TAG>
+ <tag>something third</tag>
+ </tagcasetest>
<ordertest>
- <one>1</one>
- <two>2</two>
- <three>3</three>
- <one>4</one>
- <two>5</two>
- <three>6</three>
+ <one>1</one>
+ <two>2</two>
+ <three>3</three>
+ <one>4</one>
+ <two>5</two>
+ <three>6</three>
</ordertest>
+ <validatortest>
+ <emptyarray/>
+ <oneitemarray>
+ <item>Bar.</item>
+ </oneitemarray>
+ <numbertest>42</numbertest>
+ <stringtest>43</stringtest>
+ </validatortest>
+ <pfx:top xmlns:pfx="http://foo.com" pfx:attr="baz">
+ <middle xmlns="http://bar.com"/>
+ </pfx:top>
</sample>
diff --git a/test/xml2js.test.coffee b/test/xml2js.test.coffee
index 869a50d..439c27b 100644
--- a/test/xml2js.test.coffee
+++ b/test/xml2js.test.coffee
@@ -4,6 +4,7 @@ fs = require 'fs'
util = require 'util'
assert = require 'assert'
path = require 'path'
+os = require 'os'
fileName = path.join __dirname, '/fixtures/sample.xml'
@@ -16,139 +17,305 @@ skeleton = (options, checks) ->
checks r
test.finish()
if not xmlString
- fs.readFile fileName, (err, data) ->
+ fs.readFile fileName, 'utf8', (err, data) ->
+ data = data.split(os.EOL).join('\n')
x2js.parseString data
else
x2js.parseString xmlString
+###
+The `validator` function validates the value at the XPath. It also transforms the value
+if necessary to conform to the schema or other validation information being used. If there
+is an existing value at this path it is supplied in `currentValue` (e.g. this is the second or
+later item in an array).
+If the validation fails it should throw a `ValidationError`.
+###
+validator = (xpath, currentValue, newValue) ->
+ if xpath == '/sample/validatortest/numbertest'
+ return Number(newValue)
+ else if xpath in ['/sample/arraytest', '/sample/validatortest/emptyarray', '/sample/validatortest/oneitemarray']
+ if not newValue or not ('item' of newValue)
+ return {'item': []}
+ else if xpath in ['/sample/arraytest/item', '/sample/validatortest/emptyarray/item', '/sample/validatortest/oneitemarray/item']
+ if not currentValue
+ return newValue
+ else if xpath == '/validationerror'
+ throw new xml2js.ValidationError("Validation error!")
+ return newValue
+
+# shortcut, because it is quite verbose
+equ = assert.equal
module.exports =
'test parse with defaults': skeleton(undefined, (r) ->
- console.log 'Result object: ' + util.inspect(r, false, 10)
- assert.equal r['chartest']['@']['desc'], 'Test for CHARs'
- assert.equal r['chartest']['#'], 'Character data here!'
- assert.equal r['cdatatest']['@']['desc'], 'Test for CDATA'
- assert.equal r['cdatatest']['@']['misc'], 'true'
- assert.equal r['cdatatest']['#'], 'CDATA here!'
- assert.equal r['nochartest']['@']['desc'], 'No data'
- assert.equal r['nochartest']['@']['misc'], 'false'
- assert.equal r['listtest']['item'][0]['#'], 'This is character data!'
- assert.equal r['listtest']['item'][0]['subitem'][0], 'Foo(1)'
- assert.equal r['listtest']['item'][0]['subitem'][1], 'Foo(2)'
- assert.equal r['listtest']['item'][0]['subitem'][2], 'Foo(3)'
- assert.equal r['listtest']['item'][0]['subitem'][3], 'Foo(4)'
- assert.equal r['listtest']['item'][1], 'Qux.'
- assert.equal r['listtest']['item'][2], 'Quux.')
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.chartest[0].$.desc, 'Test for CHARs'
+ equ r.sample.chartest[0]._, 'Character data here!'
+ equ r.sample.cdatatest[0].$.desc, 'Test for CDATA'
+ equ r.sample.cdatatest[0].$.misc, 'true'
+ equ r.sample.cdatatest[0]._, 'CDATA here!'
+ equ r.sample.nochartest[0].$.desc, 'No data'
+ equ r.sample.nochartest[0].$.misc, 'false'
+ equ r.sample.listtest[0].item[0]._, '\n This is\n \n character\n \n data!\n \n '
+ equ r.sample.listtest[0].item[0].subitem[0], 'Foo(1)'
+ equ r.sample.listtest[0].item[0].subitem[1], 'Foo(2)'
+ equ r.sample.listtest[0].item[0].subitem[2], 'Foo(3)'
+ equ r.sample.listtest[0].item[0].subitem[3], 'Foo(4)'
+ equ r.sample.listtest[0].item[1], 'Qux.'
+ equ r.sample.listtest[0].item[2], 'Quux.'
+ # determine number of items in object
+ equ Object.keys(r.sample.tagcasetest[0]).length, 3)
'test parse with explicitCharkey': skeleton(explicitCharkey: true, (r) ->
- assert.equal r['chartest']['@']['desc'], 'Test for CHARs'
- assert.equal r['chartest']['#'], 'Character data here!'
- assert.equal r['cdatatest']['@']['desc'], 'Test for CDATA'
- assert.equal r['cdatatest']['@']['misc'], 'true'
- assert.equal r['cdatatest']['#'], 'CDATA here!'
- assert.equal r['nochartest']['@']['desc'], 'No data'
- assert.equal r['nochartest']['@']['misc'], 'false'
- assert.equal r['listtest']['item'][0]['#'], 'This is character data!'
- assert.equal r['listtest']['item'][0]['subitem'][0]['#'], 'Foo(1)'
- assert.equal r['listtest']['item'][0]['subitem'][1]['#'], 'Foo(2)'
- assert.equal r['listtest']['item'][0]['subitem'][2]['#'], 'Foo(3)'
- assert.equal r['listtest']['item'][0]['subitem'][3]['#'], 'Foo(4)'
- assert.equal r['listtest']['item'][1]['#'], 'Qux.'
- assert.equal r['listtest']['item'][2]['#'], 'Quux.')
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.chartest[0].$.desc, 'Test for CHARs'
+ equ r.sample.chartest[0]._, 'Character data here!'
+ equ r.sample.cdatatest[0].$.desc, 'Test for CDATA'
+ equ r.sample.cdatatest[0].$.misc, 'true'
+ equ r.sample.cdatatest[0]._, 'CDATA here!'
+ equ r.sample.nochartest[0].$.desc, 'No data'
+ equ r.sample.nochartest[0].$.misc, 'false'
+ equ r.sample.listtest[0].item[0]._, '\n This is\n \n character\n \n data!\n \n '
+ equ r.sample.listtest[0].item[0].subitem[0]._, 'Foo(1)'
+ equ r.sample.listtest[0].item[0].subitem[1]._, 'Foo(2)'
+ equ r.sample.listtest[0].item[0].subitem[2]._, 'Foo(3)'
+ equ r.sample.listtest[0].item[0].subitem[3]._, 'Foo(4)'
+ equ r.sample.listtest[0].item[1]._, 'Qux.'
+ equ r.sample.listtest[0].item[2]._, 'Quux.')
'test parse with mergeAttrs': skeleton(mergeAttrs: true, (r) ->
- console.log 'Result object: ' + util.inspect(r, false, 10)
- assert.equal r['chartest']['desc'], 'Test for CHARs'
- assert.equal r['chartest']['#'], 'Character data here!'
- assert.equal r['cdatatest']['desc'], 'Test for CDATA'
- assert.equal r['cdatatest']['misc'], 'true'
- assert.equal r['cdatatest']['#'], 'CDATA here!'
- assert.equal r['nochartest']['desc'], 'No data'
- assert.equal r['nochartest']['misc'], 'false'
- assert.equal r['listtest']['item'][0]['#'], 'This is character data!'
- assert.equal r['listtest']['item'][0]['subitem'][0], 'Foo(1)'
- assert.equal r['listtest']['item'][0]['subitem'][1], 'Foo(2)'
- assert.equal r['listtest']['item'][0]['subitem'][2], 'Foo(3)'
- assert.equal r['listtest']['item'][0]['subitem'][3], 'Foo(4)'
- assert.equal r['listtest']['item'][1], 'Qux.'
- assert.equal r['listtest']['item'][2], 'Quux.')
-
- 'test default text handling': skeleton(undefined, (r) ->
- assert.equal r['whitespacetest']['#'], 'Line One Line Two')
-
- 'test disable trimming': skeleton(trim: false, (r) ->
- assert.equal r['whitespacetest']['#'], 'Line One Line Two')
-
- 'test disable normalize': skeleton(normalize: false, (r) ->
- assert.equal r['whitespacetest']['#'], 'Line One\n Line Two')
-
- 'test disable normalize and trim': skeleton(normalize: false, trim: false, (r) ->
- assert.equal r['whitespacetest']['#'], '\n Line One\n Line Two\n ')
-
- 'test default root node elimination': skeleton(__xmlString: '<root></root>', (r) ->
- assert.deepEqual r, {})
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.chartest[0].desc, 'Test for CHARs'
+ equ r.sample.chartest[0]._, 'Character data here!'
+ equ r.sample.cdatatest[0].desc, 'Test for CDATA'
+ equ r.sample.cdatatest[0].misc, 'true'
+ equ r.sample.cdatatest[0]._, 'CDATA here!'
+ equ r.sample.nochartest[0].desc, 'No data'
+ equ r.sample.nochartest[0].misc, 'false'
+ equ r.sample.listtest[0].item[0].subitem[0], 'Foo(1)'
+ equ r.sample.listtest[0].item[0].subitem[1], 'Foo(2)'
+ equ r.sample.listtest[0].item[0].subitem[2], 'Foo(3)'
+ equ r.sample.listtest[0].item[0].subitem[3], 'Foo(4)'
+ equ r.sample.listtest[0].item[1], 'Qux.'
+ equ r.sample.listtest[0].item[2], 'Quux.')
+
+ 'test parse with explicitChildren': skeleton(explicitChildren: true, (r) ->
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.$$.chartest[0].$.desc, 'Test for CHARs'
+ equ r.sample.$$.chartest[0]._, 'Character data here!'
+ equ r.sample.$$.cdatatest[0].$.desc, 'Test for CDATA'
+ equ r.sample.$$.cdatatest[0].$.misc, 'true'
+ equ r.sample.$$.cdatatest[0]._, 'CDATA here!'
+ equ r.sample.$$.nochartest[0].$.desc, 'No data'
+ equ r.sample.$$.nochartest[0].$.misc, 'false'
+ equ r.sample.$$.listtest[0].$$.item[0]._, '\n This is\n \n character\n \n data!\n \n '
+ equ r.sample.$$.listtest[0].$$.item[0].$$.subitem[0], 'Foo(1)'
+ equ r.sample.$$.listtest[0].$$.item[0].$$.subitem[1], 'Foo(2)'
+ equ r.sample.$$.listtest[0].$$.item[0].$$.subitem[2], 'Foo(3)'
+ equ r.sample.$$.listtest[0].$$.item[0].$$.subitem[3], 'Foo(4)'
+ equ r.sample.$$.listtest[0].$$.item[1], 'Qux.'
+ equ r.sample.$$.listtest[0].$$.item[2], 'Quux.'
+ equ r.sample.$$.nochildrentest[0].$$, undefined
+ # determine number of items in object
+ equ Object.keys(r.sample.$$.tagcasetest[0].$$).length, 3)
+
+ 'test element without children': skeleton(explicitChildren: true, (r) ->
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.$$.nochildrentest[0].$$, undefined)
+
+ 'test parse with explicitChildren and charsAsChildren': skeleton(explicitChildren: true, charsAsChildren: true, (r) ->
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.$$.chartest[0].$$._, 'Character data here!'
+ equ r.sample.$$.cdatatest[0].$$._, 'CDATA here!'
+ equ r.sample.$$.listtest[0].$$.item[0].$$._, '\n This is\n \n character\n \n data!\n \n '
+ # determine number of items in object
+ equ Object.keys(r.sample.$$.tagcasetest[0].$$).length, 3)
+
+ 'test text trimming, normalize': skeleton(trim: true, normalize: true, (r) ->
+ equ r.sample.whitespacetest[0]._, 'Line One Line Two')
+
+ 'test text trimming, no normalizing': skeleton(trim: true, normalize: false, (r) ->
+ equ r.sample.whitespacetest[0]._, 'Line One\n Line Two')
+
+ 'test text no trimming, normalize': skeleton(trim: false, normalize: true, (r) ->
+ equ r.sample.whitespacetest[0]._, 'Line One Line Two')
+
+ 'test text no trimming, no normalize': skeleton(trim: false, normalize: false, (r) ->
+ equ r.sample.whitespacetest[0]._, '\n Line One\n Line Two\n ')
+
+ 'test enabled root node elimination': skeleton(__xmlString: '<root></root>', explicitRoot: false, (r) ->
+ console.log 'Result object: ' + util.inspect r, false, 10
+ assert.deepEqual r, '')
'test disabled root node elimination': skeleton(__xmlString: '<root></root>', explicitRoot: true, (r) ->
- assert.deepEqual r, {root: {}})
+ assert.deepEqual r, {root: ''})
'test default empty tag result': skeleton(undefined, (r) ->
- assert.deepEqual r['emptytest'], {})
+ assert.deepEqual r.sample.emptytest, [''])
'test empty tag result specified null': skeleton(emptyTag: null, (r) ->
- assert.equal r['emptytest'], null)
+ equ r.sample.emptytest[0], null)
+
+ 'test invalid empty XML file': skeleton(__xmlString: ' ', (r) ->
+ equ r, null)
- 'test empty string result specified null': skeleton(__xmlString: ' ', (r) ->
- assert.equal r, null)
+ 'test enabled normalizeTags': skeleton(normalizeTags: true, (r) ->
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ Object.keys(r.sample.tagcasetest).length, 1)
'test parse with custom char and attribute object keys': skeleton(attrkey: 'attrobj', charkey: 'charobj', (r) ->
- assert.equal r['chartest']['attrobj']['desc'], 'Test for CHARs'
- assert.equal r['chartest']['charobj'], 'Character data here!'
- assert.equal r['cdatatest']['attrobj']['desc'], 'Test for CDATA'
- assert.equal r['cdatatest']['attrobj']['misc'], 'true'
- assert.equal r['cdatatest']['charobj'], 'CDATA here!'
- assert.equal r['nochartest']['attrobj']['desc'], 'No data'
- assert.equal r['nochartest']['attrobj']['misc'], 'false')
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.chartest[0].attrobj.desc, 'Test for CHARs'
+ equ r.sample.chartest[0].charobj, 'Character data here!'
+ equ r.sample.cdatatest[0].attrobj.desc, 'Test for CDATA'
+ equ r.sample.cdatatest[0].attrobj.misc, 'true'
+ equ r.sample.cdatatest[0].charobj, 'CDATA here!'
+ equ r.sample.cdatawhitespacetest[0].charobj, ' '
+ equ r.sample.nochartest[0].attrobj.desc, 'No data'
+ equ r.sample.nochartest[0].attrobj.misc, 'false')
'test child node without explicitArray': skeleton(explicitArray: false, (r) ->
- assert.equal r['arraytest']['item'][0]['subitem'], 'Baz.'
- assert.equal r['arraytest']['item'][1]['subitem'][0], 'Foo.'
- assert.equal r['arraytest']['item'][1]['subitem'][1], 'Bar.')
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.arraytest.item[0].subitem, 'Baz.'
+ equ r.sample.arraytest.item[1].subitem[0], 'Foo.'
+ equ r.sample.arraytest.item[1].subitem[1], 'Bar.')
'test child node with explicitArray': skeleton(explicitArray: true, (r) ->
- assert.equal r['arraytest'][0]['item'][0]['subitem'][0], 'Baz.'
- assert.equal r['arraytest'][0]['item'][1]['subitem'][0], 'Foo.'
- assert.equal r['arraytest'][0]['item'][1]['subitem'][1], 'Bar.')
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.arraytest[0].item[0].subitem[0], 'Baz.'
+ equ r.sample.arraytest[0].item[1].subitem[0], 'Foo.'
+ equ r.sample.arraytest[0].item[1].subitem[1], 'Bar.')
'test ignore attributes': skeleton(ignoreAttrs: true, (r) ->
- assert.equal r['chartest'], 'Character data here!'
- assert.equal r['cdatatest'], 'CDATA here!'
- assert.deepEqual r['nochartest'], {}
- assert.equal r['listtest']['item'][0]['#'], 'This is character data!'
- assert.equal r['listtest']['item'][0]['subitem'][0], 'Foo(1)'
- assert.equal r['listtest']['item'][0]['subitem'][1], 'Foo(2)'
- assert.equal r['listtest']['item'][0]['subitem'][2], 'Foo(3)'
- assert.equal r['listtest']['item'][0]['subitem'][3], 'Foo(4)'
- assert.equal r['listtest']['item'][1], 'Qux.'
- assert.equal r['listtest']['item'][2], 'Quux.')
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample.chartest, 'Character data here!'
+ equ r.sample.cdatatest, 'CDATA here!'
+ assert.equal r.sample.nochartest[0], ''
+ equ r.sample.listtest[0].item[0]._, '\n This is\n \n character\n \n data!\n \n '
+ equ r.sample.listtest[0].item[0].subitem[0], 'Foo(1)'
+ equ r.sample.listtest[0].item[0].subitem[1], 'Foo(2)'
+ equ r.sample.listtest[0].item[0].subitem[2], 'Foo(3)'
+ equ r.sample.listtest[0].item[0].subitem[3], 'Foo(4)'
+ equ r.sample.listtest[0].item[1], 'Qux.'
+ equ r.sample.listtest[0].item[2], 'Quux.')
'test simple callback mode': (test) ->
x2js = new xml2js.Parser()
fs.readFile fileName, (err, data) ->
- assert.equal err, null
+ equ err, null
x2js.parseString data, (err, r) ->
- assert.equal err, null
+ equ err, null
# just a single test to check whether we parsed anything
- assert.equal r['chartest']['#'], 'Character data here!'
+ equ r.sample.chartest[0]._, 'Character data here!'
test.finish()
'test double parse': (test) ->
x2js = new xml2js.Parser()
fs.readFile fileName, (err, data) ->
- assert.equal err, null
+ equ err, null
x2js.parseString data, (err, r) ->
- assert.equal err, null
+ equ err, null
# make sure we parsed anything
- assert.equal r['chartest']['#'], 'Character data here!'
+ equ r.sample.chartest[0]._, 'Character data here!'
x2js.parseString data, (err, r) ->
- assert.equal err, null
- assert.equal r['chartest']['#'], 'Character data here!'
+ equ err, null
+ equ r.sample.chartest[0]._, 'Character data here!'
test.finish()
+
+ 'test simple function without options': (test) ->
+ fs.readFile fileName, (err, data) ->
+ xml2js.parseString data, (err, r) ->
+ equ err, null
+ equ r.sample.chartest[0]._, 'Character data here!'
+ test.finish()
+
+ 'test simple function with options': (test) ->
+ fs.readFile fileName, (err, data) ->
+ # well, {} still counts as option, right?
+ xml2js.parseString data, {}, (err, r) ->
+ equ err, null
+ equ r.sample.chartest[0]._, 'Character data here!'
+ test.finish()
+
+ 'test validator': skeleton(validator: validator, (r) ->
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ typeof r.sample.validatortest[0].stringtest[0], 'string'
+ equ typeof r.sample.validatortest[0].numbertest[0], 'number'
+ assert.ok r.sample.validatortest[0].emptyarray[0].item instanceof Array
+ equ r.sample.validatortest[0].emptyarray[0].item.length, 0
+ assert.ok r.sample.validatortest[0].oneitemarray[0].item instanceof Array
+ equ r.sample.validatortest[0].oneitemarray[0].item.length, 1
+ equ r.sample.validatortest[0].oneitemarray[0].item[0], 'Bar.'
+ assert.ok r.sample.arraytest[0].item instanceof Array
+ equ r.sample.arraytest[0].item.length, 2
+ equ r.sample.arraytest[0].item[0].subitem[0], 'Baz.'
+ equ r.sample.arraytest[0].item[1].subitem[0], 'Foo.'
+ equ r.sample.arraytest[0].item[1].subitem[1], 'Bar.')
+
+ 'test validation error': (test) ->
+ x2js = new xml2js.Parser({validator: validator})
+ x2js.parseString '<validationerror/>', (err, r) ->
+ equ err.message, 'Validation error!'
+ test.finish()
+
+ 'test error throwing': (test) ->
+ xml = '<?xml version="1.0" encoding="utf-8"?><test>content is ok<test>'
+ try
+ xml2js.parseString xml, (err, parsed) ->
+ throw new Error 'error throwing in callback'
+ throw new Error 'error throwing outside'
+ catch e
+ # don't catch the exception that was thrown by callback
+ equ e.message, 'error throwing outside'
+ test.finish()
+
+ 'test xmlns': skeleton(xmlns: true, (r) ->
+ console.log 'Result object: ' + util.inspect r, false, 10
+ equ r.sample["pfx:top"][0].$ns.local, 'top'
+ equ r.sample["pfx:top"][0].$ns.uri, 'http://foo.com'
+ equ r.sample["pfx:top"][0].$["pfx:attr"].value, 'baz'
+ equ r.sample["pfx:top"][0].$["pfx:attr"].local, 'attr'
+ equ r.sample["pfx:top"][0].$["pfx:attr"].uri, 'http://foo.com'
+ equ r.sample["pfx:top"][0].middle[0].$ns.local, 'middle'
+ equ r.sample["pfx:top"][0].middle[0].$ns.uri, 'http://bar.com')
+
+ 'test callback should be called once': (test) ->
+ xml = '<?xml version="1.0" encoding="utf-8"?><test>test</test>'
+ i = 0;
+ try
+ xml2js.parseString xml, (err, parsed) ->
+ i = i + 1
+ # throw something custom
+ throw new Error 'Custom error message'
+ catch e
+ equ i, 1
+ equ e.message, 'Custom error message'
+ test.finish()
+
+ 'test empty CDATA': (test) ->
+ xml = '<xml><Label><![CDATA[]]></Label><MsgId>5850440872586764820</MsgId></xml>'
+ xml2js.parseString xml, (err, parsed) ->
+ equ parsed.xml.Label[0], ''
+ test.finish()
+
+ 'test CDATA whitespaces result': (test) ->
+ xml = '<spacecdatatest><![CDATA[ ]]></spacecdatatest>'
+ xml2js.parseString xml, (err, parsed) ->
+ equ parsed.spacecdatatest, ' '
+ test.finish()
+
+ 'test non-strict parsing': (test) ->
+ html = '<html><head></head><body><br></body></html>'
+ xml2js.parseString html, strict: false, (err, parsed) ->
+ equ err, null
+ test.finish()
+
+ 'test construction with new and without': (test) ->
+ demo = '<xml><foo>Bar</foo></xml>'
+ withNew = new xml2js.Parser
+ withNew.parseString demo, (err, resWithNew) ->
+ equ err, undefined
+ withoutNew = xml2js.Parser()
+ withoutNew.parseString demo, (err, resWithoutNew) ->
+ equ err, undefined
+ assert.deepEqual resWithNew, resWithoutNew
+ test.done()
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/collab-maint/node-xml2js.git
More information about the Pkg-javascript-commits
mailing list