[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