aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorJames Lal <james@lightsofapollo.com>2012-06-19 14:01:01 -0700
committerJames Lal <james@lightsofapollo.com>2012-06-19 14:01:01 -0700
commit936e76b8945c6f310ad893905e08728715620e38 (patch)
tree39c969cb8739e26e3c4f628b50f1afffa8330dcc
parentf1ae9e6f2a4503b9c5ab55abb13d95cbd9ee753c (diff)
downloadjsCalDAV-936e76b8945c6f310ad893905e08728715620e38.tar.gz
v1 sax parser
-rw-r--r--Makefile1
-rw-r--r--lib/webcals/sax.js229
-rw-r--r--lib/webcals/sax/base.js73
-rw-r--r--lib/webcals/sax/propstat.js30
-rw-r--r--samples/xml/complex-tree.xml24
-rw-r--r--samples/xml/propstat-success.xml10
-rw-r--r--samples/xml/simple.xml6
-rw-r--r--test/helper.js18
-rw-r--r--test/webcals/ics_test.js6
-rw-r--r--test/webcals/sax/base_test.js94
-rw-r--r--test/webcals/sax/propstat_test.js27
-rw-r--r--test/webcals/sax_test.js387
12 files changed, 678 insertions, 227 deletions
diff --git a/Makefile b/Makefile
index f833021..10e47df 100644
--- a/Makefile
+++ b/Makefile
@@ -6,6 +6,7 @@ test:
--ui tdd \
--reporter $(REPORTER) \
--growl test/helper.js \
+ test/webcals/sax/*_test.js \
test/webcals/*_test.js
.PHONY: watch
diff --git a/lib/webcals/sax.js b/lib/webcals/sax.js
index 1e169fd..9cc8425 100644
--- a/lib/webcals/sax.js
+++ b/lib/webcals/sax.js
@@ -1,38 +1,54 @@
(function(module, ns) {
- var sax = require('sax'),
- Responder = ns.require('responder');
+ var Responder = ns.require('responder');
- function Parser() {
- var dispatch = [
- 'onerror',
+ if (typeof(sax) === 'undefined') {
+ Parser.sax = require('sax');
+ } else {
+ Parser.sax = sax;
+ }
+
+ /**
+ * Creates a parser object.
+ *
+ * @param {Object} baseHandler base sax handler.
+ */
+ function Parser(baseHandler) {
+ var handler;
+
+ var events = [
+ 'ontext',
'onopentag',
'onclosetag',
- 'ontext'
+ 'onerror',
+ 'onend'
];
- this.parse = sax.parser(true, {
+ if (typeof(baseHandler) !== 'undefined') {
+ handler = baseHandler;
+ } else {
+ handler = ns.require('sax/base');
+ }
+
+ this.stack = [];
+ this.handles = {};
+ this._handlerStack = [];
+ this.tagStack = [];
+ this.root = this.current = {};
+
+ this.setHandler(handler);
+
+ this._parse = Parser.sax.parser(true, {
xmlns: true,
trim: true,
- normalize: true,
+ normalize: false,
lowercase: true
});
- dispatch.forEach(function(event) {
- this.parse[event] = this._dispatchEvent.bind(this, event);
+ events.forEach(function(event) {
+ this._parse[event] = this[event].bind(this);
}, this);
- this.parse.onend = this.onend.bind(this);
-
- this.handles = {};
-
- this.stack = [];
- this.handlerStack = [];
- this.tagStack = [];
-
- this.current = this.root = {};
- this.setParser(this);
-
Responder.call(this);
}
@@ -40,43 +56,73 @@
__proto__: Responder.prototype,
- setParser: function(parse) {
- this.currentParser = parse;
- },
-
- restoreParser: function() {
- if ('oncomplete' in this.currentParser) {
- this.currentParser.oncomplete.call(this);
+ /**
+ * Sets current handler, optionally adding
+ * previous one to the handlerStack.
+ *
+ * @param {Object} handler new handler.
+ * @param {Boolean} storeOriginal store old handler?
+ */
+ setHandler: function(handler, storeOriginal) {
+ if (storeOriginal) {
+ this._handlerStack.push(this.handler);
}
- var parser = this.handlerStack.pop();
- this.setParser(parser || this);
+ this.handler = handler;
},
- _dispatchEvent: function(type, data) {
-
- if (type === 'onopentag') {
- data.tagSpec = data.uri + '/' + data.local;
+ /**
+ * Sets handler to previous one in the stack.
+ */
+ restoreHandler: function() {
+ if (this._handlerStack.length) {
+ this.handler = this._handlerStack.pop();
}
+ },
- if (type in this.currentParser) {
- this.currentParser[type].call(this, data);
- } else {
- this[type](data);
- }
+ /**
+ * Registers a top level handler
+ *
+ * @param {String} tag xmlns uri/local tag name for example
+ * DAV:/a.
+ *
+ * @param {Object} handler new handler to use when tag is
+ * triggered.
+ */
+ registerHandler: function(tag, handler) {
+ this.handles[tag] = handler;
+ },
+
+ /**
+ * Writes data into the parser.
+ *
+ * @param {String} chunk partial/complete chunk of xml.
+ */
+ write: function(chunk) {
+ return this._parse.write(chunk);
},
- addHandler: function(obj) {
- this.handles[obj.tag] = obj;
+ get closed() {
+ return this._parse.closed;
},
- checkHandler: function(handle) {
- var handler,
- handlers = this.currentParser.handles;
+ /**
+ * Determines if given tagSpec has a specific handler.
+ *
+ * @param {String} tagSpec usual tag spec.
+ */
+ getHandler: function(tagSpec) {
+ var handler;
+ var handlers = this.handler.handles;
+
+ if (!handlers) {
+ handlers = this.handles;
+ }
+
+ if (tagSpec in handlers) {
+ handler = handlers[tagSpec];
- if (handle in handlers) {
- handler = handlers[handle];
- if (handler !== this.currentParser) {
+ if (handler !== this.handler) {
return handler;
}
}
@@ -84,66 +130,75 @@
return false;
},
- handleError: function() {
+ _fireHandler: function(event, data) {
+ if (event in this.handler) {
+ this.handler[event].call(this, data, this.handler);
+ }
},
onopentag: function(data) {
- var current = this.current,
- name = data.local,
- handler = this.checkHandler(data.tagSpec);
-
- if (handler) {
- this.handlerStack.push(this.currentParser);
- this.setParser(handler);
- return this._dispatchEvent('onopentag', data);
- }
+ var handle;
+ var stackData = {};
- this.tagStack.push(data.tagSpec);
- this.stack.push(this.current);
+ //build tagSpec for others to use.
+ data.tagSpec = data.uri + '/' + data.local;
- if (name in current) {
- var next = {};
+ //add to stackData
+ stackData.tag = data.tagSpec;
- if (!(current[name] instanceof Array)) {
- current[name] = [current[name]];
- }
+ // shortcut to the current tag object
+ this.currentTag = data;
+
+ //determine if we need to switch to another
+ //handler object.
+ handle = this.getHandler(data.tagSpec);
- current[name].push(next);
- this.current = next;
- } else {
- this.current = current[name] = {};
+ if (handle) {
+ //switch to new handler object
+ this.setHandler(handle, true);
+ stackData.handler = handle;
}
- },
- onend: function() {
- this.emit('complete', this.root, this);
+ this.tagStack.push(stackData);
+ this._fireHandler('onopentag', data);
},
- checkStackForHandler: function(restore) {
- var stack = this.tagStack,
- last = stack[stack.length - 1],
- result;
+ onclosetag: function(data) {
+ var stack, handler;
- result = last === this.currentParser.tag;
+ stack = this.tagStack[this.tagStack.length - 1];
- if (restore && result) {
- this.restoreParser();
+ if (stack.handler) {
+ //fire oncomplete handler if available
+ this._fireHandler('oncomplete');
}
- return result;
- },
+ //fire the onclosetag event
+ this._fireHandler('onclosetag', data);
- onclosetag: function() {
- this.current = this.stack.pop();
+ if (stack.handler) {
+ //restore previous handler
+ this.restoreHandler();
+ }
+
+ //actually remove the stack tag
this.tagStack.pop();
},
ontext: function(data) {
- this.current.value = data;
+ this._fireHandler('ontext', data);
},
- write: function(data) {
- return this.parse.write(data);
+ onerror: function(data) {
+ //TODO: XXX implement handling of parsing errors.
+ //unlikely but possible if server goes down
+ //or there is some authentication issue that
+ //we miss.
+ this._fireHandler('onerror', data);
+ },
+
+ onend: function() {
+ this._fireHandler('onend', this.root);
}
};
@@ -151,7 +206,7 @@
}.apply(
this,
- (this.CalDav) ?
- [CalDav('xml_parser'), CalDav] :
+ (this.Webcals) ?
+ [Webcals('xml_parser'), Webcals] :
[module, require('./webcals')]
));
diff --git a/lib/webcals/sax/base.js b/lib/webcals/sax/base.js
new file mode 100644
index 0000000..c62981f
--- /dev/null
+++ b/lib/webcals/sax/base.js
@@ -0,0 +1,73 @@
+(function(module, ns) {
+
+ var Base = {
+
+ name: 'base',
+
+ tagField: 'local',
+
+ /**
+ * Creates a new object with base as its prototype.
+ * Adds ._super to object as convenience prop to access
+ * the parents functions.
+ *
+ * @param {Object} obj function overrides.
+ * @return {Object} new object.
+ */
+ create: function(obj) {
+ var key;
+ var child = Object.create(this);
+
+ child._super = this;
+
+ for (key in obj) {
+ if (obj.hasOwnProperty(key)) {
+ child[key] = obj[key];
+ }
+ }
+
+ return child;
+ },
+
+ onopentag: function(data, handler) {
+ var current = this.current;
+ var name = data[handler.tagField];
+
+ this.stack.push(this.current);
+
+ if (name in current) {
+ var next = {};
+
+ if (!(current[name] instanceof Array)) {
+ current[name] = [current[name]];
+ }
+
+ current[name].push(next);
+
+ this.current = next;
+ } else {
+ this.current = current[name] = {};
+ }
+ },
+
+ ontext: function(data) {
+ this.current.value = data;
+ },
+
+ onclosetag: function() {
+ this.current = this.stack.pop();
+ },
+
+ onend: function() {
+ this.emit('complete', this.root);
+ }
+ };
+
+ module.exports = Base;
+
+}.apply(
+ this,
+ (this.Webcals) ?
+ [Webcals('sax/base'), Webcals] :
+ [module, require('../webcals')]
+));
diff --git a/lib/webcals/sax/propstat.js b/lib/webcals/sax/propstat.js
deleted file mode 100644
index 03bedaa..0000000
--- a/lib/webcals/sax/propstat.js
+++ /dev/null
@@ -1,30 +0,0 @@
-(function(module, ns) {
- var Responder = ns.require('responder');
-
- function Propstat(sax, complete) {
-
- function onopen() {
-
- }
-
- function onclose() {
-
- }
-
- function ontext() {
-
- }
-
- sax.on('tagopen', onopen);
- sax.on('tagclose', ontext);
- sax.on('text', ontext);
- }
-
- module.exports = Propstat;
-
-}.apply(
- this,
- (this.CalDav) ?
- [CalDav('sax/propstat'), CalDav] :
- [module, require('../webcals')]
-));
diff --git a/samples/xml/complex-tree.xml b/samples/xml/complex-tree.xml
new file mode 100644
index 0000000..6b599be
--- /dev/null
+++ b/samples/xml/complex-tree.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<D:complex xmlns:D="DAV:">
+ <D:response>
+ </D:response>
+
+ <D:response>
+ <D:href>uri</D:href>
+
+ <D:propstat>
+ <D:status>400</D:status>
+ <D:prop>
+ <A:current xmlns:A="DAV:" />
+ </D:prop>
+ </D:propstat>
+
+ <D:propstat>
+ <D:status>200</D:status>
+ <D:prop>
+ <A:next xmlns:A="DAV:" />
+ </D:prop>
+ </D:propstat>
+ </D:response>
+</D:complex>
+
diff --git a/samples/xml/propstat-success.xml b/samples/xml/propstat-success.xml
deleted file mode 100644
index 93f27ec..0000000
--- a/samples/xml/propstat-success.xml
+++ /dev/null
@@ -1,10 +0,0 @@
-<D:status>HTTP/1.1 200 OK</D:status>
-<D:prop>
- <D:principal-URL>
- <D:href>/calendar/dav/calmozilla1@gmail.com/user/</D:href>
- </D:principal-URL>
- <D:resourcetype>
- <D:principal />
- <D:collection />
- </D:resourcetype>
-</D:prop>
diff --git a/samples/xml/simple.xml b/samples/xml/simple.xml
new file mode 100644
index 0000000..0e539da
--- /dev/null
+++ b/samples/xml/simple.xml
@@ -0,0 +1,6 @@
+<D:simple xmlns:D="DAV:">
+ <D:a>Foo</D:a>
+ <D:a href="some-url">Foo
+ bar</D:a>
+ <D:div><D:span>span</D:span></D:div>
+</D:simple>
diff --git a/test/helper.js b/test/helper.js
index b36aeeb..d5ac7a0 100644
--- a/test/helper.js
+++ b/test/helper.js
@@ -4,10 +4,6 @@ var chai = require('chai'),
chai.Assertion.includeStack = true;
assert = chai.assert;
-globalNS = {
- require: require
-};
-
loadSample = function(file, cb) {
var root = __dirname + '/../samples/';
fs.readFile(root + file, 'utf8', function(err, contents) {
@@ -15,6 +11,20 @@ loadSample = function(file, cb) {
});
};
+defineSample = function(file, cb) {
+ suiteSetup(function(done) {
+ loadSample(file, function(err, data) {
+ if (err) {
+ done(err);
+ }
+ cb(data);
+ done();
+ });
+ });
+};
+
requireLib = function(lib) {
return require(__dirname + '/../lib/webcals/' + lib);
};
+
+Webcals = require('../lib/webcals/webcals.js');
diff --git a/test/webcals/ics_test.js b/test/webcals/ics_test.js
index 904bbf9..af1192e 100644
--- a/test/webcals/ics_test.js
+++ b/test/webcals/ics_test.js
@@ -5,11 +5,7 @@ var fs = require('fs'),
suite('webcals/ics', function() {
test('intiailizer', function() {
- ics(data, function(data) {
- console.log(data.vevent[0]);
- }, function() {
- console.log('y');
- })
+ assert.ok(ics);
});
});
diff --git a/test/webcals/sax/base_test.js b/test/webcals/sax/base_test.js
new file mode 100644
index 0000000..9ad0bdc
--- /dev/null
+++ b/test/webcals/sax/base_test.js
@@ -0,0 +1,94 @@
+requireLib('sax');
+requireLib('sax/base');
+
+suite('webcals/sax/base', function() {
+
+ var data,
+ subject,
+ parser,
+ Parse,
+ Base,
+ handler;
+
+
+ suiteSetup(function() {
+ Parse = Webcals.require('sax');
+ Base = Webcals.require('sax/base');
+ });
+
+ setup(function() {
+ //we omit the option to pass base parser
+ //because we are the base parser
+ subject = new Parse();
+ });
+
+ test('#create', function() {
+ function childText() {}
+
+ var Child = Base.create({
+ ontext: childText
+ });
+
+ assert.equal(Child.ontext, childText);
+ assert.equal(Child.tagField, Base.tagField);
+
+ assert.isTrue(
+ Base.isPrototypeOf(Child),
+ 'should have base in proto chain'
+ );
+
+ assert.equal(Child._super, Base);
+
+ var ChildChild = Child.create();
+
+ assert.isTrue(
+ Child.isPrototypeOf(ChildChild),
+ 'should have child in childchild protochain'
+ );
+
+ assert.isTrue(
+ Base.isPrototypeOf(ChildChild),
+ 'should have base in childchild protochain'
+ );
+
+ assert.equal(ChildChild._super, Child);
+
+ });
+
+ suite('base parser', function() {
+ var xml;
+
+ defineSample('xml/simple.xml', function(data) {
+ xml = data;
+ });
+
+ //base baser does not
+ //care about attrs at this point
+ expected = {
+ simple: {
+ a: [
+ { value: 'Foo' },
+ { value: 'Foo\n bar' }
+ ],
+ div: {
+ span: {
+ value: 'span'
+ }
+ }
+ }
+ };
+
+ test('simple tree', function(done) {
+ subject.once('complete', function(data) {
+ assert.deepEqual(
+ data, expected,
+ "expected \n '" + JSON.stringify(data) + "'\n to equal \n '" +
+ JSON.stringify(expected) + '\n"'
+ );
+ done();
+ });
+ subject.write(xml).close();
+ });
+ });
+
+});
diff --git a/test/webcals/sax/propstat_test.js b/test/webcals/sax/propstat_test.js
deleted file mode 100644
index 56e65fd..0000000
--- a/test/webcals/sax/propstat_test.js
+++ /dev/null
@@ -1,27 +0,0 @@
-suite('webcals/sax/propstat', function() {
- var stat = requireLib('sax/propstat'),
- sax = requireLib('sax'),
- subject;
-
- var expected = {
- status: 200,
- 'principal-URL': '/calendar/dav/calmozilla1@gmail.com/user/',
- 'resource-type': [
- 'principal',
- 'collection'
- ]
- };
-
- test('propstat success', function(done) {
- var parser = sax();
-
- console.log(parser.on);
- stat(parser, function(err, result) {
- console.log(result);
- done();
- });
-
- parser.write(loadSample('xml/propstat-success.xml')).close();
- });
-
-});
diff --git a/test/webcals/sax_test.js b/test/webcals/sax_test.js
index 312990c..71f6571 100644
--- a/test/webcals/sax_test.js
+++ b/test/webcals/sax_test.js
@@ -1,94 +1,353 @@
-var xml = requireLib('sax');
+requireLib('sax');
+requireLib('sax/base');
-suite('sax test', function() {
+suite('webcals/sax', function() {
var data,
- subject;
+ subject,
+ SAX,
+ Base,
+ handler;
- test('existance', function() {
- return;
- var parser = new xml();
+ // you should not use instances
+ // for handlers this is only
+ // to make testing easier.
+ function TestHander() {
+ this.text = [];
+ this.opentag = [];
+ this.closetag = [];
+ this.error = [];
+ this.complete = [];
+ this.end = [];
- var StatusHandler = {
- tag: 'DAV:/status',
+ var events = [
+ 'ontext', 'onclosetag',
+ 'onopentag', 'onerror',
+ 'oncomplete', 'onend'
+ ];
+ }
- onopentag: function(data) {
- },
+ TestHander.prototype = {
- ontext: function(data) {
- this.current.status = data.match(/([0-9]{3,3})/)[1];
- },
+ ontext: function(data, handler) {
+ handler.text.push(data);
+ },
- onclosetag: function(data) {
- this.restoreParser();
- }
- };
+ onclosetag: function(data, handler) {
+ handler.closetag.push(data);
+ },
- var ResourceTypeHandler = {
- tag: 'DAV:/resourcetype',
+ onopentag: function(data, handler) {
+ handler.opentag.push(data);
+ },
- onopentag: function(data) {
- this.tagStack.push(data.tagSpec);
+ onerror: function(data, handler) {
+ handler.error.push(data);
+ },
- if (data.local === 'resourcetype') {
- this.current.resourceTypes = [];
- } else {
- this.current.resourceTypes.push(data.local);
- }
- },
+ oncomplete: function(data, handler) {
+ handler.complete.push(data);
+ },
- onclosetag: function(data) {
- this.checkStackForHandler(true);
- this.tagStack.pop();
- }
- };
+ onend: function(data) {
+ handler.end.push(data);
+ }
+ };
- var TextOnlyHandler = {
- tag: 'DAV:/href',
+ function firesHandler(type, data) {
+ var len = handler[type].length;
+ var event = handler[type][len - 1];
- onopentag: function(data) {
- },
+ assert.deepEqual(event, data);
+ }
- ontext: function(data) {
- this.current.href = data;
- },
+ suiteSetup(function() {
+ SAX = Webcals.require('sax');
+ Base = Webcals.require('sax/base');
+ });
- onclosetag: function(data) {
- this.restoreParser();
- }
- };
+ setup(function() {
+ handler = new TestHander();
+ subject = new SAX(handler);
+ });
+
+ test('initializer', function() {
+ assert.equal(subject.handler, handler);
+ assert.deepEqual(subject.stack, []);
+ assert.deepEqual(subject.handles, {});
+ assert.deepEqual(subject._handlerStack, []);
+ assert.deepEqual(subject.tagStack, []);
+ assert.ok(subject._parse);
+ });
+
+ suite('#setHandler', function() {
+
+ setup(function() {
+ subject.setHandler(handler, false);
+ });
- var ResponseHandler = {
- tag: 'DAV:/response',
- handles: {
- 'DAV:/status': StatusHandler,
- 'DAV:/resourcetype': ResourceTypeHandler,
- 'DAV:/href': TextOnlyHandler,
- 'DAV:/getetag': TextOnlyHandler
- },
+ test('set without store', function() {
+ assert.equal(subject.handler, handler);
+
+ assert.equal(
+ subject._handlerStack.length,
+ 0,
+ 'should not save original'
+ );
+ });
+
+ test('set/store', function() {
+ var uniq = {};
+
+ subject.setHandler(uniq, true);
+
+ assert.equal(subject.handler, uniq);
+ assert.equal(subject._handlerStack[0], handler);
+ });
+
+ });
+
+ test('#restoreHandler', function() {
+ var uniq = {};
+ subject.setHandler(uniq, true);
+ subject.restoreHandler();
+
+ assert.equal(subject.handler, handler);
+ });
- onclosetag: function(data) {
- this.checkStackForHandler(true);
- this.onclosetag(data);
- },
+ test('#registerHandler', function() {
+ var uniq = {};
- oncomplete: function() {
- this.emit('response', this.current, this);
- }
+ subject.registerHandler('a/foo', uniq);
+ assert.equal(subject.handles['a/foo'], uniq);
+ });
+ test('#write', function() {
+ var called, uniq = {};
+ subject._parse.write = function() {
+ return uniq;
};
- parser.addHandler(ResponseHandler);
+ assert.equal(subject.write(), uniq);
+ });
+
+ test('#closed', function() {
+ assert.isFalse(subject.closed, 'should not be closed');
+
+ subject._parse.closed = true;
+ assert.isTrue(
+ subject.closed,
+ 'should be closed now that parser is.'
+ );
+ });
+
+ suite('#getHandler', function() {
+ test('handler not found', function() {
+ assert.isFalse(subject.getHandler('foo'));
+ });
+
+ test('handler found', function() {
+ var uniq;
+
+ subject.registerHandler('foo', uniq);
+
+ var handler = subject.getHandler('foo');
+ assert.equal(uniq, handler);
+ });
+
+ test('handler found but is current', function() {
+ var uniq = {};
+ subject.registerHandler('foo', uniq);
+ subject.setHandler(uniq);
+
+ assert.isFalse(subject.getHandler('foo'));
+ });
+ });
+
+ suite('#onopentag', function() {
+
+ test('basic event', function() {
+ var obj = {
+ local: 'foo',
+ uri: 'bar'
+ };
+
+ subject.onopentag(obj);
+ assert.equal(subject.currentTag, obj);
+ assert.equal(obj.tagSpec, 'bar/foo');
+ assert.deepEqual(
+ subject.tagStack,
+ [{ tag: 'bar/foo' }]
+ );
+
+ firesHandler('opentag', obj);
+ });
+ });
+
+ suite('handler stacks', function() {
+ var newHandler;
- parser.on('response', function(data, context) {
- console.log(JSON.stringify(data), '\n\n');
+ setup(function() {
+ newHandler = new TestHander();
+ subject.registerHandler('a/a', newHandler);
+ subject.onopentag({
+ local: 'a',
+ uri: 'a'
+ });
});
- parser.once('complete', function(data, parser) {
- console.log(JSON.stringify(data));
+ test('switch to new handler', function() {
+ assert.equal(subject.handler, newHandler);
+ assert.deepEqual(
+ subject.tagStack, [
+ { tag: 'a/a', handler: newHandler }
+ ]
+ );
+ });
+
+ test('pop to original handler', function() {
+ subject.onclosetag('a/a');
+ assert.equal(subject.tagStack.length, 0, 'should clear tagStack');
+ assert.equal(subject.handler, handler, 'should reset handler');
+ assert.equal(
+ newHandler.complete.length, 1,
+ 'should fire complete event on new handler'
+ );
+ });
+
+ });
+
+ test('#onclosetag', function() {
+ var obj = { local: 'a', uri: 'b' };
+
+ subject.onopentag(obj);
+ assert.equal(subject.tagStack.length, 1);
+
+ subject.onclosetag('a:b');
+ assert.equal(subject.tagStack.length, 0);
+
+ firesHandler('closetag', 'a:b');
+ });
+
+ test('#ontext', function() {
+ subject.ontext('foo');
+ firesHandler('text', 'foo');
+ });
+
+ test('#onerror', function() {
+ subject.onerror('foo');
+ firesHandler('error', 'foo');
+ });
+
+ test('#onend', function() {
+ subject.onend();
+ assert.ok(handler.end);
+ assert.equal(handler.end[0], subject.root);
+ });
+
+ suite('complex mutli-handler', function() {
+ var xml,
+ expected,
+ events;
+
+ var ResponseHandler;
+ var TextOnlyHandler;
+
+ defineSample('xml/complex-tree.xml', function(data) {
+ xml = data;
+ });
+
+ suiteSetup(function() {
+ TextOnlyHandler = Base.create({
+ name: 'text',
+
+ //don't add text only elements
+ //to the stack as objects
+ onopentag: function() {},
+ onclosetag: function() {},
+
+ //add the value to the parent
+ //value where key is local tag name
+ //and value is the text.
+ ontext: function(data) {
+ var handler = this.handler;
+ this.current[this.currentTag[handler.tagField]] = data;
+ }
+ });
+
+ ResponseHandler = Base.create({
+ name: 'response',
+
+ handles: {
+ 'DAV:/href': TextOnlyHandler,
+ 'DAV:/status': TextOnlyHandler,
+ 'DAV:/getetag': TextOnlyHandler
+ },
+
+ oncomplete: function() {
+ events.push(this.current);
+ }
+ });
+ });
+
+ setup(function() {
+ //use real handlers
+ subject.setHandler(Base);
+ });
+
+ test('complex result', function() {
+ var result,
+ expectedEvent,
+ expectedResult;
+
+ events = [];
+
+ expectedEvent = {
+ href: 'uri',
+ propstat: [
+ {
+ status: '400',
+ prop: {
+ current: {}
+ }
+ },
+ {
+ status: '200',
+ prop: {
+ next: {}
+ }
+ }
+ ]
+ };
+
+ expectedResult = {
+ complex: {
+ response: [{}, expectedEvent]
+ }
+ };
+
+ subject.registerHandler('DAV:/response', ResponseHandler);
+
+ subject.on('response', function(data) {
+ events.push(data);
+ });
+
+ subject.once('complete', function(data) {
+ result = data;
+ });
+
+ subject.write(xml).close();
+
+ assert.ok(events[0]);
+ assert.equal(events.length, 2);
+
+ assert.ok(result);
+
+ assert.deepEqual(events[0], {});
+ assert.deepEqual(events[1], expectedEvent);
+ assert.deepEqual(result, expectedResult);
});
- parser.write(data).close();
});
});