[Pkg-javascript-commits] [ltx] 46/80: Imported Upstream version 0.2.0

Jonas Smedegaard dr at jones.dk
Sun Feb 28 10:50:12 UTC 2016


This is an automated email from the git hooks/post-receive script.

js pushed a commit to branch master
in repository ltx.

commit 8e0f7d8e318af106c9dfc68cfc89c5a07543af49
Author: Jonas Smedegaard <dr at jones.dk>
Date:   Sun Mar 25 12:43:27 2012 +0200

    Imported Upstream version 0.2.0
---
 README.markdown         |  46 +++++++++++++-
 benchmark/.gitignore    |   1 +
 benchmark/benchmark.js  | 144 +++++++++++++++++++++++++++++++++++++++++++
 benchmark/build.js      |  14 +++++
 benchmark/index.html    |   9 +++
 lib/element.js          |   4 +-
 lib/index-browserify.js |   7 +++
 lib/index.js            |  16 ++++-
 lib/parse.js            |  61 +++++++++++--------
 lib/sax_easysax.js      |  45 ++++++++++++++
 lib/sax_expat.js        |  39 ++++++++++++
 lib/sax_ltx.js          | 159 ++++++++++++++++++++++++++++++++++++++++++++++++
 lib/sax_node-xml.js     |  63 +++++++++++++++++++
 lib/sax_saxjs.js        |  39 ++++++++++++
 package.json            |  12 +++-
 test/parse-test.js      | 117 ++++++++++++++++++++++++++++-------
 16 files changed, 723 insertions(+), 53 deletions(-)

diff --git a/README.markdown b/README.markdown
index b4ac945..2886ec1 100644
--- a/README.markdown
+++ b/README.markdown
@@ -2,11 +2,45 @@
 
 * *Element:* any XML Element
 * Text nodes are Strings
+* Runs on node.js and browserify
+
+
+## Parsing
+
+### DOM
+
+Parse a little document at once:
+
+    el = ltx.parse("<document/>")
+
+Push parser:
+
+	p = new ltx.Parser();
+	p.on('tree', function(tree) {
+		proceed(null, tree);
+	});
+	p.on('error', function(error) {
+		proceed(error);
+	});
+
+### SAX
+
+ltx implements multiple SAX backends:
+
+* *node-expat*: libexpat binding
+* *ltx*: fast native-JavaScript parser without error handling
+* *saxjs*: native-JavaScript parser
+
+If present, they are available through
+`ltx.availableSaxParsers`. Mostly, you'll want to do:
+
+    parser = new ltx.bestSaxParser();
+
+Refer to `lib/parse.js` for the interface.
 
 
 ## Element traversal
 
-* `Element(name, attrs?)`: constructor
 * `is(name, xmlns?)`: check
 * `getName()`: name without ns prefix
 * `getNS()`: element's xmlns, respects prefixes and searches upwards
@@ -31,6 +65,7 @@
 
 ## Modifying XML Elements
 
+* `new Element(name, attrs?)`: constructor
 * `remove(child)`: remove child by reference
 * `remove(name, xmlns)`: remove child by tag name and xmlns
 * `attr(attrName, value?)`: modify or get an attribute's value
@@ -39,7 +74,14 @@
 
 ## Building XML Elements
 
-This resembles strophejs a bit.
+    el = new ltx.Element('root').
+		c('children');
+	el.c('child', { age: 5 }).t('Hello').up()
+	  .c('child', { age: 7 }).t('Hello').up()
+	  .c('child', { age: 99 }).t('Hello').up()
+	console.log("Serialized document:", el.root().toString());
+
+This resembles Strophejs a bit.
 
 strophejs' XML Builder is very convenient for producing XMPP
 stanzas. node-xmpp includes it in a much more primitive way: the
diff --git a/benchmark/.gitignore b/benchmark/.gitignore
new file mode 100644
index 0000000..012a3cd
--- /dev/null
+++ b/benchmark/.gitignore
@@ -0,0 +1 @@
+index.js
diff --git a/benchmark/benchmark.js b/benchmark/benchmark.js
new file mode 100644
index 0000000..b725272
--- /dev/null
+++ b/benchmark/benchmark.js
@@ -0,0 +1,144 @@
+if (process.title === 'browser') {
+    var ltx = require("ltx");
+    var strophe = require('Strophe.js');
+    var requestAnimationFrame = require('request-animation-frame').requestAnimationFrame;
+} else {
+    var path = "../lib/index";
+    var ltx = require(path);
+}
+var util = require('util');
+
+function now() {
+    return new Date().getTime();
+}
+
+function Test() {
+    this.timings = {};
+}
+Test.prototype = {
+    record: function(name, fun) {
+	var t1 = now();
+	var res = fun();
+	var t2 = now();
+
+	if (!this.timings.hasOwnProperty(name))
+	    this.timings[name] = { i: 0, t: 0 };
+	this.timings[name].i++;
+	this.timings[name].t += t2 - t1;
+    },
+
+    report: function() {
+	if (process.title === 'browser') {
+	    var s = [];
+	    var html = "<div style='float: left; min-width: 25em'><h2>" + this.name + "</h2><dl>";
+	    for(var k in this.timings) {
+		var t = this.timings[k].t / this.timings[k].i;
+		html += "<dt>" + k + "</dt><dd class='" + k + "'>" + t + " ms </dd>";
+	    }
+	    html += "</dl></div>\n";
+	    return html;
+	} else {
+	    var s = this.name + "\t";
+	    for(k in this.timings) {
+		var t = this.timings[k].t / this.timings[k].i;
+		s += k + ": " + t + " ms\t";
+	    }
+	    return s;
+	}
+    }
+};
+
+function LtxTest(saxParser) {
+    Test.call(this);
+    this.saxParser = saxParser;
+    this.name = "LTX/" + saxParser.name;
+}
+util.inherits(LtxTest, Test);
+
+LtxTest.prototype.parse = function(s) {
+    return ltx.parse(s, this.saxParser);
+};
+
+LtxTest.prototype.serialize = function(el) {
+    return el.toString();
+};
+
+LtxTest.prototype.traverse = function(node) {
+    while(node.children && node.children[0])
+	node = node.children[0];
+};
+
+function StropheTest() {
+    Test.call(this);
+
+    this.serialize = Strophe.serialize;
+}
+util.inherits(StropheTest, Test);
+
+StropheTest.prototype.name = "Strophe.js";
+
+StropheTest.prototype.parse = function(s) {
+    return Strophe.xmlHtmlNode(s).firstChild;
+};
+
+StropheTest.prototype.traverse = function(node) {
+    while(node.firstChild)
+	node = node.firstChild;
+};
+
+var tests = ltx.availableSaxParsers.map(function(saxParser) {
+    return new LtxTest(saxParser);
+});
+if (process.title === 'browser')
+    tests.push(new StropheTest());
+var messages = [
+    "<message/>",
+    "<message foo='bar'/>",
+    "<message foo='bar'><foo/><bar>fnord<baz/>fnord</bar></message>"
+];
+messages[3] = "<message>";
+for(var i = 0; i < 10; i++) {
+    messages[3] += "fnord fnord fnord<foo bar='baz'>";
+}
+for(var i = 0; i < 10; i++) {
+    messages[3] += "</foo>fnordfnordfnordfnord<quux foo='bar'/>";
+}
+messages[3] += "</message>";
+
+var iteration = 0;
+function runTests() {
+    iteration++;
+    tests.forEach(function(test) {
+	for(var j = 0; j < messages.length; j++) {
+	    var parsed, serialized;
+	    test.record('parse' + j, function() {
+		parsed = test.parse(messages[j]);
+	    });
+	    test.record('serialize' + j, function() {
+		serialized = test.serialize(parsed);
+	    });
+	    test.record('traverse' + j, function() {
+		test.traverse(parsed);
+	    });
+	}
+    });
+
+    if (process.title === 'browser') {
+	document.body.innerHTML = "<style>.parse0, .parse1, .parse2, .parse3 { color: red; } .serialize1, .serialize2, .serialize3, .serialize4 { color: blue; }</style>\n" +
+	    "<h1>Iteration " + iteration + "<h1>\n";
+	tests.forEach(function(test) {
+	    document.body.innerHTML += test.report() + "<br>";
+	});
+	requestAnimationFrame(runTests);
+    } else {
+	console.log("Iteration " + iteration);
+	tests.forEach(function(test) {
+	    console.log(test.report());
+	});
+	process.nextTick(runTests);
+    }
+}
+
+setTimeout(function() {
+    runTests();
+}, 1000);
diff --git a/benchmark/build.js b/benchmark/build.js
new file mode 100644
index 0000000..8fd00cd
--- /dev/null
+++ b/benchmark/build.js
@@ -0,0 +1,14 @@
+var browserify = require('browserify');
+var path = require('path');
+var b = browserify({
+    debug: true
+});
+var root = path.join(__dirname, "..");
+b.require("./lib/index-browserify.js",
+    { root: root, basedir: root });
+b.alias("ltx", "./lib/index-browserify.js");
+b.addEntry('benchmark.js');
+
+var fs = require('fs');
+
+fs.writeFileSync('index.js', b.bundle());
diff --git a/benchmark/index.html b/benchmark/index.html
new file mode 100644
index 0000000..44a50c8
--- /dev/null
+++ b/benchmark/index.html
@@ -0,0 +1,9 @@
+<!DOCTYPE html>
+<html>
+  <head>
+    <script src="index.js"></script>
+  </head>
+  <body>
+  </body>
+</html>
+
diff --git a/lib/element.js b/lib/element.js
index 010e358..12b2367 100644
--- a/lib/element.js
+++ b/lib/element.js
@@ -280,14 +280,14 @@ function escapeXml(s) {
         replace(/>/g, '>').
         replace(/"/g, '"').
         replace(/'/g, ''');
-};
+}
 
 function escapeXmlText(s) {
     return s.
         replace(/\&/g, '&').
         replace(/</g, '<').
         replace(/>/g, '>');
-};
+}
 
 exports.Element = Element;
 exports.escapeXml = escapeXml;
diff --git a/lib/index-browserify.js b/lib/index-browserify.js
new file mode 100644
index 0000000..5c44ae7
--- /dev/null
+++ b/lib/index-browserify.js
@@ -0,0 +1,7 @@
+/* Cause browserify to bundle SAX parsers: */
+//require('./sax_easysax');
+//require('./sax_saxjs');
+require('./sax_ltx');
+
+/* SHIM */
+module.exports = require('./index');
\ No newline at end of file
diff --git a/lib/index.js b/lib/index.js
index a9614b5..68de945 100644
--- a/lib/index.js
+++ b/lib/index.js
@@ -1,8 +1,22 @@
 var element = require('./element');
 var parse = require('./parse');
 
+/**
+ * The only (relevant) data structure
+ */
 exports.Element = element.Element;
+/**
+ * Helper
+ */
 exports.escapeXml = element.escapeXml;
 
-exports.Parser = parse.Parser;
+/**
+ * DOM parser interface
+ */
 exports.parse = parse.parse;
+exports.Parser = parse.Parser;
+/**
+ * SAX parser interface
+ */
+exports.availableSaxParsers = parse.availableSaxParsers;
+exports.bestSaxParser = parse.bestSaxParser;
diff --git a/lib/parse.js b/lib/parse.js
index 0265c8f..29cdfb1 100644
--- a/lib/parse.js
+++ b/lib/parse.js
@@ -1,17 +1,33 @@
 var events = require('events');
-var util;
-try {
-    util = require('util');
-} catch(e) {
-    util = require('sys');
-}
-var expat = require('node-expat');
+var util = require('util');
+
+exports.availableSaxParsers = [];
+exports.bestSaxParser = null;
+['./sax_expat.js', './sax_ltx.js', /*'./sax_easysax.js', './sax_node-xml.js',*/ './sax_saxjs.js'].forEach(function(modName) {
+    var mod;
+    try {
+	mod = require(modName);
+    } catch (e) {
+	/* Silently missing libraries drop; for debug:
+	console.error(e.stack || e);
+	 */
+    }
+    if (mod) {
+	exports.availableSaxParsers.push(mod);
+	if (!exports.bestSaxParser)
+	    exports.bestSaxParser = mod;
+    }
+});
 var element = require('./element');
 
-exports.Parser = function() {
+exports.Parser = function(saxParser) {
+    events.EventEmitter.call(this);
     var that = this;
 
-    this.parser = new expat.Parser('UTF-8');
+    var parserMod = saxParser || exports.bestSaxParser;
+    if (!parserMod)
+	throw new Error("No SAX parser available");
+    this.parser = new parserMod();
 
     var el;
     this.parser.addListener('startElement', function(name, attrs) {
@@ -22,7 +38,7 @@ exports.Parser = function() {
             el = el.cnode(child);
         }
     });
-    this.parser.addListener('endElement', function(name, attrs) {
+    this.parser.addListener('endElement', function(name) {
         if (!el) {
             /* Err */
         } else if (el && name == el.name) {
@@ -38,24 +54,21 @@ exports.Parser = function() {
         if (el)
             el.t(str);
     });
+    this.parser.addListener('error', function(e) {
+	that.error = e;
+	that.emit('error', e);
+    });
 };
 util.inherits(exports.Parser, events.EventEmitter);
 
 exports.Parser.prototype.write = function(data) {
-    if (!this.parser.parse(data, false)) {
-        this.emit('error', new Error(this.parser.getError()));
-
-        // Premature error thrown,
-        // disable all functionality:
-        this.write = function() { };
-        this.end = function() { };
-    }
+    this.parser.write(data);
 };
 
-exports.Parser.prototype.end = function() {
-    if (!this.parser.parse('', true))
-        this.emit('error', new Error(this.parser.getError()));
-    else {
+exports.Parser.prototype.end = function(data) {
+    this.parser.end(data);
+
+    if (!this.error) {
 	if (this.tree)
 	    this.emit('tree', this.tree);
 	else
@@ -63,8 +76,8 @@ exports.Parser.prototype.end = function() {
     }
 };
 
-exports.parse = function(data) {
-    var p = new exports.Parser();
+exports.parse = function(data, saxParser) {
+    var p = new exports.Parser(saxParser);
     var result = null, error = null;
 
     p.on('tree', function(tree) {
diff --git a/lib/sax_easysax.js b/lib/sax_easysax.js
new file mode 100644
index 0000000..a8b7f60
--- /dev/null
+++ b/lib/sax_easysax.js
@@ -0,0 +1,45 @@
+var util = require('util');
+var events = require('events');
+var Easysax = require('easysax');
+
+/**
+ * FIXME: This SAX parser cannot be pushed to!
+ */
+var SaxEasysax = module.exports = function SaxEasysax() {
+    events.EventEmitter.call(this);
+    this.parser = new Easysax();
+    var that = this;
+    this.parser.on('startNode', function(name, attr, uq, str, tagend) {
+	attr = attr();
+	for(var k in attr)
+	    if (attr.hasOwnProperty(k)) {
+		attr[k] = uq(attr[k]);
+	    }
+        that.emit('startElement', name, attr);
+    });
+    this.parser.on('endNode', function(name, uq, str, tagstart) {
+        that.emit('endElement', name);
+    });
+    this.parser.on('textNode', function(str, uq) {
+        that.emit('text', uq(str));
+    });
+    this.parser.on('cdata', function(str) {
+        that.emit('text', str);
+    });
+    this.parser.on('error', function(e) {
+	that.emit('error', e);
+    });
+    // TODO: other events, esp. entityDecl (billion laughs!)
+};
+util.inherits(SaxEasysax, events.EventEmitter);
+
+SaxEasysax.prototype.write = function(data) {
+    if (typeof data !== 'string')
+	data = data.toString();
+    this.parser.parse(data);
+};
+
+SaxEasysax.prototype.end = function(data) {
+    if (data)
+	this.write(data);
+};
diff --git a/lib/sax_expat.js b/lib/sax_expat.js
new file mode 100644
index 0000000..9d05cb2
--- /dev/null
+++ b/lib/sax_expat.js
@@ -0,0 +1,39 @@
+var util = require('util');
+var events = require('events');
+var expat = require('node-expat');
+
+var SaxExpat = module.exports = function SaxExpat() {
+    events.EventEmitter.call(this);
+    this.parser = new expat.Parser('UTF-8');
+
+    var that = this;
+    this.parser.on('startElement', function(name, attrs) {
+        that.emit('startElement', name, attrs);
+    });
+    this.parser.on('endElement', function(name) {
+        that.emit('endElement', name);
+    });
+    this.parser.on('text', function(str) {
+        that.emit('text', str);
+    });
+    // TODO: other events, esp. entityDecl (billion laughs!)
+};
+util.inherits(SaxExpat, events.EventEmitter);
+
+SaxExpat.prototype.write = function(data) {
+    if (!this.parser.parse(data, false)) {
+        this.emit('error', new Error(this.parser.getError()));
+
+        // Premature error thrown,
+        // disable all functionality:
+        this.write = function() { };
+        this.end = function() { };
+    }
+};
+
+SaxExpat.prototype.end = function(data) {
+    if (!this.parser.parse('', true))
+        this.emit('error', new Error(this.parser.getError()));
+    else
+        this.emit('end');
+};
diff --git a/lib/sax_ltx.js b/lib/sax_ltx.js
new file mode 100644
index 0000000..cb3b709
--- /dev/null
+++ b/lib/sax_ltx.js
@@ -0,0 +1,159 @@
+var util = require('util');
+var events = require('events');
+
+const STATE_TEXT = 0,
+    STATE_IGNORE_TAG = 1,
+    STATE_TAG_NAME = 2,
+    STATE_TAG = 3,
+    STATE_ATTR_NAME = 4,
+    STATE_ATTR_EQ = 5,
+    STATE_ATTR_QUOT = 6,
+    STATE_ATTR_VALUE = 7;
+
+var RE_TAG_NAME = /^[^\s\/>]+$/,
+    RE_ATTR_NAME = /^[^\s=]+$/;
+
+var SaxLtx = module.exports = function SaxLtx() {
+    events.EventEmitter.call(this);
+
+    var state = STATE_TEXT, remainder;
+    var tagName, attrs, endTag, selfClosing, attrQuote;
+    var recordStart = 0;
+
+    this.write = function(data) {
+	if (typeof data !== 'string')
+	    data = data.toString();
+	var pos = 0;
+
+	/* Anything from previous write()? */
+	if (remainder) {
+	    data = remainder + data;
+	    pos += remainder.length;
+	    delete remainder;
+	}
+
+	function endRecording() {
+	    if (typeof recordStart === 'number') {
+		var recorded = data.slice(recordStart, pos);
+		recordStart = undefined;
+		return recorded;
+	    }
+	}
+
+	for(; pos < data.length; pos++) {
+	    var c = data.charCodeAt(pos);
+	    //console.log("state", state, "c", c, data[pos]);
+	    switch(state) {
+	    case STATE_TEXT:
+		if (c === 60 /* < */) {
+		    var text = endRecording();
+		    if (text)
+			this.emit('text', unescapeXml(text));
+		    state = STATE_TAG_NAME;
+		    recordStart = pos + 1;
+		    attrs = {};
+		}
+		break;
+	    case STATE_TAG_NAME:
+		if (c === 47 /* / */ && recordStart === pos) {
+		    recordStart = pos + 1;
+		    endTag = true;
+		} else if (c === 33 /* ! */ || c === 63 /* ? */) {
+		    recordStart = undefined;
+		    state = STATE_IGNORE_TAG;
+		} else if (c <= 32 || c === 47 /* / */ || c === 62 /* > */) {
+		    tagName = endRecording();
+		    pos--;
+		    state = STATE_TAG;
+		}
+		break;
+	    case STATE_IGNORE_TAG:
+		if (c === 62 /* > */) {
+		    state = STATE_TEXT;
+		}
+		break;
+	    case STATE_TAG:
+		if (c === 62 /* > */) {
+		    if (!endTag) {
+			this.emit('startElement', tagName, attrs);
+			if (selfClosing)
+			    this.emit('endElement', tagName);
+		    } else
+			this.emit('endElement', tagName);
+		    tagName = undefined;
+		    attrs = undefined;
+		    endTag = undefined;
+		    selfClosing = undefined;
+		    state = STATE_TEXT;
+		    recordStart = pos + 1;
+		} else if (c === 47 /* / */) {
+		    selfClosing = true;
+		} else if (c > 32) {
+		    recordStart = pos;
+		    state = STATE_ATTR_NAME;
+		}
+		break;
+	    case STATE_ATTR_NAME:
+		if (c <= 32 || c === 61 /* = */) {
+		    attrName = endRecording();
+		    pos--;
+		    state = STATE_ATTR_EQ;
+		}
+		break;
+	    case STATE_ATTR_EQ:
+		if (c === 61 /* = */) {
+		    state = STATE_ATTR_QUOT;
+		}
+		break;
+	    case STATE_ATTR_QUOT:
+		if (c === 34 /* " */ || c === 39 /* ' */) {
+		    attrQuote = c;
+		    state = STATE_ATTR_VALUE;
+		    recordStart = pos + 1;
+		}
+		break;
+	    case STATE_ATTR_VALUE:
+		if (c === attrQuote) {
+		    var value = unescapeXml(endRecording());
+		    attrs[attrName] = value;
+		    attrName = undefined;
+		    state = STATE_TAG;
+		}
+		break;
+	    }
+	}
+
+	if (typeof recordStart === 'number' &&
+	    recordStart <= data.length) {
+
+	    remainder = data.slice(recordStart);
+	    recordStart = 0;
+	}
+    };
+
+    /*var origEmit = this.emit;
+    this.emit = function() {
+	console.log('ltx', arguments);
+	origEmit.apply(this, arguments);
+    };*/
+};
+util.inherits(SaxLtx, events.EventEmitter);
+
+
+SaxLtx.prototype.end = function(data) {
+    if (data)
+	this.write(data);
+
+    /* Uh, yeah */
+    this.write = function() {
+    };
+};
+
+function unescapeXml(s) {
+    return s.
+        replace(/\&/g, '&').
+        replace(/\</g, '<').
+        replace(/\>/g, '>').
+        replace(/\"/g, '"').
+        replace(/\'/g, '\'');
+}
diff --git a/lib/sax_node-xml.js b/lib/sax_node-xml.js
new file mode 100644
index 0000000..16c64cc
--- /dev/null
+++ b/lib/sax_node-xml.js
@@ -0,0 +1,63 @@
+var util = require('util');
+var events = require('events');
+var xml = require('node-xml');
+
+/**
+ * This cannot be used as long as node-xml starts parsing only after
+ * setTimeout(f, 0);
+ */
+var SaxNodeXML = module.exports = function SaxNodeXML() {
+    events.EventEmitter.call(this);
+    var that = this;
+    this.parser = new xml.SaxParser(function(handler) {
+	handler.onStartElementNS(function(elem, attrs, prefix, uri, namespaces) {
+	    var i, attrsHash = {};
+	    if (prefix)
+		elem = prefix + ":" + elem;
+	    for(i = 0; i < attrs.length; i++) {
+		var attr = attrs[i];
+		attrsHash[attr[0]] = unescapeXml(attr[1]);
+	    }
+	    for(i = 0; i < namespaces.length; i++) {
+		var namespace = namespaces[i];
+		var k = !namespace[0] ? "xmlns" : "xmlns:" + namespace[0];
+		attrsHash[k] = unescapeXml(namespace[1]);
+	    }
+	    that.emit('startElement', elem, attrsHash);
+	});
+	handler.onEndElementNS(function(elem, prefix, uri) {
+	    if (prefix)
+		elem = prefix + ":" + elem;
+	    that.emit('endElement', elem);
+	});
+	handler.onCharacters(function(str) {
+	    that.emit('text', str);
+	});
+	handler.onCdata(function(str) {
+	    that.emit('text', str);
+	});
+	handler.onError(function(e) {
+	    that.emit('error', e);
+	});
+	// TODO: other events, esp. entityDecl (billion laughs!)
+    });
+};
+util.inherits(SaxNodeXML, events.EventEmitter);
+
+SaxNodeXML.prototype.write = function(data) {
+    this.parser.parseString(data);
+};
+
+SaxNodeXML.prototype.end = function(data) {
+    if (data)
+	this.write(data);
+};
+
+function unescapeXml(s) {
+    return s.
+        replace(/\&/g, '&').
+        replace(/\</g, '<').
+        replace(/\>/g, '>').
+        replace(/\"/g, '"').
+        replace(/\'/g, '\'');
+}
diff --git a/lib/sax_saxjs.js b/lib/sax_saxjs.js
new file mode 100644
index 0000000..1a27b6e
--- /dev/null
+++ b/lib/sax_saxjs.js
@@ -0,0 +1,39 @@
+var util = require('util');
+var events = require('events');
+var sax = require('sax');
+
+var SaxSaxjs = module.exports = function SaxSaxjs() {
+    events.EventEmitter.call(this);
+    this.parser = sax.parser(true);
+
+    var that = this;
+    this.parser.onopentag = function(a) {
+        that.emit('startElement', a.name, a.attributes);
+    };
+    this.parser.onclosetag = function(name) {
+        that.emit('endElement', name);
+    };
+    this.parser.ontext = function(str) {
+        that.emit('text', str);
+    };
+    this.parser.onend = function() {
+        that.emit('end');
+    };
+    this.parser.onerror = function(e) {
+        that.emit('error', e);
+    };
+    // TODO: other events, esp. entityDecl (billion laughs!)
+};
+util.inherits(SaxSaxjs, events.EventEmitter);
+
+SaxSaxjs.prototype.write = function(data) {
+    if (typeof data !== 'string')
+	data = data.toString();
+    this.parser.write(data);
+};
+
+SaxSaxjs.prototype.end = function(data) {
+    if (data)
+        this.parser.write(data);
+    this.parser.close();
+};
diff --git a/package.json b/package.json
index bc5e9b0..59b7838 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,11 @@
 { "name": "ltx"
-,"version": "0.1.3"
+,"version": "0.2.0"
 ,"main": "./lib/index"
+,"browserify": "./lib/index-browserify.js"
 ,"description": "<xml for=\"node.js\">"
 ,"author": "Stephan Maka"
 ,"dependencies": {"node-expat": ">=1.2.0"
+		 ,"sax": ">=0.3.5"
 		 }
 ,"repositories": [{"type": "git"
 		  ,"path": "http://github.com/astro/ltx.git"
@@ -17,6 +19,12 @@
 ,"contributors": ["Stephan Maka", "Will Fife", "Markus Kohlhase"]
 ,"licenses": [{"type": "MIT"}]
 ,"engine": "node"
-,"devDependencies": {"vows": ">=0.5.12"}
+,"devDependencies": {"vows": ">=0.5.12"
+		    ,"easysax": ">=0.1.7"
+		    ,"node-xml": ">=1.0.1"
+		    ,"Strophe.js": "https://github.com/metajack/strophejs/tarball/master"
+		    ,"request-animation-frame": ">=0.1.0"
+		    ,"browserify": ">=1.10.4"
+		    }
 ,"scripts": {"test":"vows --spec"}
 }
diff --git a/test/parse-test.js b/test/parse-test.js
index 0f05708..a9c0b44 100644
--- a/test/parse-test.js
+++ b/test/parse-test.js
@@ -2,26 +2,99 @@ var vows = require('vows'),
 assert = require('assert'),
 ltx = require('./../lib/index');
 
-vows.describe('ltx').addBatch({
-    'parsing': {
-        'simple document': function() {
-            var el = ltx.parse('<root/>');
-            assert.equal(el.name, 'root');
-            assert.equal(0, el.children.length);
+ltx.availableSaxParsers.forEach(function(saxParser) {
+    var parse = function(s) {
+        return ltx.parse(s, saxParser);
+    };
+    vows.describe('ltx with ' + saxParser.name).addBatch({
+        'DOM parsing': {
+            'simple document': function() {
+                var el = parse('<root/>');
+                assert.equal(el.name, 'root');
+                assert.equal(0, el.children.length);
+            },
+            'text with commas': function() {
+                var el = parse("<body>sa'sa'1'sasa</body>");
+                assert.equal("sa'sa'1'sasa", el.getText());
+            },
+            'text with entities': function() {
+                var el = parse("<body><>&"'</body>");
+                assert.equal("<>&\"'", el.getText());
+            },
+            'attribute with entities': function() {
+		var el = parse("<body title='<>&"''/>");
+		assert.equal("<>&\"'", el.attrs.title);
+            },
+            'erroneous document raises error': function() {
+                assert.throws(function() {
+                    parse('<root></toor>');
+                });
+            },
+            'incomplete document raises error': function() {
+                assert.throws(function() {
+                    parse('<root>');
+                });
+            },
+            'namespace declaration': function() {
+                var el = parse("<root xmlns='https://github.com/astro/ltx'/>");
+                assert.equal(el.name, 'root');
+                assert.equal(el.attrs.xmlns, 'https://github.com/astro/ltx');
+                assert.ok(el.is('root', 'https://github.com/astro/ltx'));
+            },
+            'namespace declaration with prefix': function() {
+                var el = parse("<x:root xmlns:x='https://github.com/astro/ltx'/>");
+                assert.equal(el.name, 'x:root');
+                assert.equal(el.getName(), 'root');
+                assert.ok(el.is('root', 'https://github.com/astro/ltx'));
+            },
+	    'buffer': function() {
+		var buf = new Buffer('<root/>');
+		var el = parse(buf);
+                assert.equal(el.name, 'root');
+                assert.equal(0, el.children.length);
+	    },
+	    'utf-8 text': function() {
+		var el = parse('<?xml version="1.0" encoding="utf-8"?><text>Möwe</text>');
+                assert.equal(el.name, 'text');
+                assert.equal(el.getText(), "Möwe");
+	    },
+	    'iso8859-1 text': function() {
+		var el = parse('<?xml version="1.0" encoding="iso-8859-1"?><text>M\xF6we</text>');
+                assert.equal(el.name, 'text');
+                assert.equal(el.getText(), "Möwe");
+	    }
         },
-	'text with commas': function() {
-	    var el = ltx.parse("<body>sa'sa'1'sasa</body>");
-	    assert.equal("sa'sa'1'sasa", el.getText());
-	},
-        'erroneous document raises error': function() {
-            assert.throws(function() {
-                ltx.parse('<root></toor>');
-            });
-        },
-        'incomplete document raises error': function() {
-            assert.throws(function() {
-                ltx.parse('<root>');
-            });
-        }
-    }
-}).export(module);
+	'SAX parsing': {
+	    'XMPP stream': function() {
+		var parser = new saxParser();
+		var events = [];
+		parser.on('startElement', function(name) {
+		    events.push({ start: name });
+		});
+		parser.on('endElement', function(name) {
+		    events.push({ end: name });
+		});
+		parser.on('text', function(s) {
+		    events.push({ text: s });
+		});
+		parser.write("<?xml version='1.0'?><stream:stream xmlns='jabber:client'");
+		parser.write(" xmlns:stream='http://etherx.jabber.org/streams' id='5568");
+		assert.equal(events.length, 0);
+		parser.write("90365' from='jabber.ccc.de' version='1.0' xml:lang='en'><");
+		assert.equal(events.length, 1);
+		parser.write("stream:features><starttls xmlns='urn:ietf:params:xml:ns:x");
+		assert.equal(events.length, 2);
+		parser.write("mpp-tls'/><mechanisms xmlns='urn:ietf:params:xml:ns:xmpp-");
+		assert.equal(events.length, 4);
+		parser.write("sasl'><mechanism>PLAIN</mechanism><mechanism>DIGEST-MD5</");
+		assert.ok(events.length >= 9);
+		parser.write("mechanism><mechanism>SCRAM-SHA-1</mechanism></mechanisms>");
+		assert.equal(events.length, 15);
+		parser.write("<register xmlns='http://jabber.org/features/iq-register'/");
+		assert.equal(events.length, 15);
+		parser.write("></stream:features>");
+		assert.equal(events.length, 18);
+	    }
+	}
+    }).export(module);
+});

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-javascript/ltx.git



More information about the Pkg-javascript-commits mailing list