Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

OS-721 add node-expat to parse XML

  • Loading branch information...
commit b1a52e5335854135e69e8bf4fc474ce53b5803a4 1 parent f0b3529
@joshwilsdon joshwilsdon authored
View
14 src/Makefile
@@ -1,6 +1,8 @@
CC=gcc
CFLAGS=-Wall
-TARGETS=bootparams disklist removable_disk disk_size node-kstat/build/default/kstat.node zoneevent
+TARGETS=bootparams disklist removable_disk disk_size \
+ node-kstat/build/default/kstat.node zoneevent \
+ node-expat/build/default/node-expat.node
SMARTDC_TARGETS=has_hvx
DESTDIR=../proto
NODE_WAF=$(PWD)/../proto/usr/bin/node-waf
@@ -23,9 +25,14 @@ install: $(TARGETS) sysinfo
mkdir -m 0755 -p $(DESTDIR)/usr/node_modules
find node_modules -type f -exec cp {} $(DESTDIR)/usr/node_modules/ \;
cp node-kstat/build/default/kstat.node $(DESTDIR)/usr/node_modules/
- mkdir -m 0755 -p $(DESTDIR)/usr/vm/
rm -rf $(DESTDIR)/usr/vm
cp -PR vm $(DESTDIR)/usr/
+ mkdir -m 0755 -p $(DESTDIR)/usr/vm/node_modules
+ cp node-expat/build/default/node-expat.node \
+ $(DESTDIR)/usr/vm/node_modules/expat_binding.node
+ cat node-expat/lib/node-expat.js | \
+ sed -e "s|var expat.*;|var expat = require('expat_binding');|" > \
+ $(DESTDIR)/usr/vm/node_modules/node-expat.js
cp zoneevent $(DESTDIR)/usr/vm/sbin/zoneevent
mkdir -p $(DESTDIR)/lib/svc/manifest/system
cp vm/smf/system-vmadmd.xml $(DESTDIR)/lib/svc/manifest/system/system-vmadmd.xml
@@ -51,6 +58,9 @@ has_hvx: has_hvx.c
node-kstat/build/default/kstat.node: node-kstat/kstat.cc $(NODE_WAF)
(cd node-kstat && $(NODE_WAF) configure && $(NODE_WAF) build)
+node-expat/build/default/node-expat.node: node-expat/node-expat.cc $(NODE_WAF)
+ (cd node-expat && $(NODE_WAF) configure && $(NODE_WAF) build)
+
disklist:
cp disklist.sh disklist
chmod 0755 disklist
View
2  src/manifest
@@ -13,7 +13,9 @@ f usr/vm/sbin/vmadm 0555 root bin
f usr/vm/sbin/vmadmd 0555 root bin
f usr/vm/sbin/zoneevent 0555 root bin
f usr/vm/etc/vmadm.completion 0444 root bin
+f usr/vm/node_modules/expat_binding.node 0444 root bin
f usr/vm/node_modules/httpu.js 0444 root bin
+f usr/vm/node_modules/node-expat.js 0444 root bin
f usr/vm/node_modules/nopt.js 0444 root bin
f usr/vm/node_modules/qmp.js 0444 root bin
f usr/vm/node_modules/VM.js 0444 root bin
View
4 src/node-expat/.gitignore
@@ -0,0 +1,4 @@
+.lock-wscript
+node_modules
+build
+*.swp
View
20 src/node-expat/LICENSE
@@ -0,0 +1,20 @@
+Copyright (c) 2010 Stephan Maka
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in
+all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
+THE SOFTWARE.
+
View
49 src/node-expat/README.markdown
@@ -0,0 +1,49 @@
+# node-expat #
+
+## Motivation ##
+
+You use [node.js](http://github.com/ry/node) for speed? You process
+XML streams? Then you want the fastest XML parser: [libexpat](http://expat.sourceforge.net/)!
+
+## Speed ##
+
+A stupid speed test is supplied in `bench.js`. We measure how many
+25-byte elements a SAX parser can process:
+
+- [node-xml](http://github.com/robrighter/node-xml) (pure JavaScript): 23,000 el/s
+- [libxmljs](http://github.com/polotek/libxmljs) (libxml2 binding): 77,000 el/s
+- [node-expat](http://github.com/astro/node-expat) (libexpat binding, this): 113,000 el/s
+
+These numbers were recorded on a Core 2 2400 MHz and may turn out to
+be bullshit, given my few node.js experience.
+
+## Instructions ##
+
+ node-waf configure
+ node-waf build
+
+For using the library, make sure `build/default/expat.node` is in
+either `$NODE_PATH` or `require.paths`.
+
+Important events emitted by a parser:
+
+- *startElement* with `name, attrs`
+- *endElement* with `name`
+- *text* with `string`
+
+There are more. Use `test.js` for reference.
+
+It's possible to stop and resume the parser from within element handlers using the parsers
+stop() and resume() methods.
+
+## Error handling ##
+
+We don't emit an error event because libexpat doesn't use a callback
+either. Instead, check that `parse()` returns `true`. A descriptive
+string can be obtained via `getError()` to provide user feedback.
+
+## Namespace handling ##
+
+A word about special parsing of *xmlns:* this is not neccessary in a
+bare SAX parser like this, given that the DOM replacement you are
+using (if any) is not relevant to the parser.
View
40 src/node-expat/bench.js
@@ -0,0 +1,40 @@
+var sys = require('sys');
+var node_xml = require("node-xml");
+var libxml = require("libxmljs");
+var expat = require('./lib/node-expat');
+
+function NodeXmlParser() {
+ var parser = new node_xml.SaxParser(function(cb) { });
+ this.parse = function(s) {
+ parser.parseString(s);
+ };
+}
+function LibXmlJsParser() {
+ var parser = new libxml.SaxPushParser(function(cb) { });
+ this.parse = function(s) {
+ parser.push(s, false);
+ };
+}
+function ExpatParser() {
+ var parser = new expat.Parser();
+ this.parse = function(s) {
+ parser.parse(s, false);
+ };
+}
+
+//var p = new NodeXmlParser();
+//var p = new LibXmlJsParser();
+var p = new ExpatParser();
+p.parse("<r>");
+var nEl = 0;
+function d() {
+ p.parse("<foo bar='baz'>quux</foo>");
+ nEl++;
+ setTimeout(d, 0);
+}
+d();
+
+setInterval(function() {
+ sys.puts(nEl + " el/s");
+ nEl = 0;
+}, 1000);
View
38 src/node-expat/lib/node-expat.js
@@ -0,0 +1,38 @@
+var EventEmitter = require('events').EventEmitter;
+var util = require('util');
+var expat = require('../build/default/node-expat');
+
+/**
+ * Simple wrapper because EventEmitter has turned pure-JS as of node
+ * 0.5.x.
+ */
+exports.Parser = function(encoding) {
+ this.parser = new expat.Parser(encoding);
+
+ var that = this;
+ this.parser.emit = function() {
+ that.emit.apply(that, arguments);
+ };
+};
+util.inherits(exports.Parser, EventEmitter);
+
+exports.Parser.prototype.parse = function(buf, isFinal) {
+ return this.parser.parse(buf, isFinal);
+};
+
+exports.Parser.prototype.setEncoding = function(encoding) {
+ return this.parser.setEncoding(encoding);
+};
+
+exports.Parser.prototype.getError = function() {
+ return this.parser.getError();
+};
+exports.Parser.prototype.stop = function() {
+ return this.parser.stop();
+};
+exports.Parser.prototype.pause = function() {
+ return this.stop();
+};
+exports.Parser.prototype.resume = function() {
+ return this.parser.resume();
+};
View
378 src/node-expat/node-expat.cc
@@ -0,0 +1,378 @@
+#include <node.h>
+#include <node_version.h>
+#include <node_object_wrap.h>
+#include <node_buffer.h>
+extern "C" {
+#include <expat.h>
+}
+
+using namespace v8;
+using namespace node;
+
+static Persistent<String> sym_startElement, sym_endElement,
+ sym_startCdata, sym_endCdata,
+ sym_text, sym_processingInstruction,
+ sym_comment, sym_xmlDecl, sym_entityDecl,
+ sym_emit;
+
+class Parser : public ObjectWrap {
+public:
+ static void Initialize(Handle<Object> target)
+ {
+ HandleScope scope;
+ Local<FunctionTemplate> t = FunctionTemplate::New(New);
+
+ t->InstanceTemplate()->SetInternalFieldCount(1);
+
+ NODE_SET_PROTOTYPE_METHOD(t, "parse", Parse);
+ NODE_SET_PROTOTYPE_METHOD(t, "setEncoding", SetEncoding);
+ NODE_SET_PROTOTYPE_METHOD(t, "getError", GetError);
+ NODE_SET_PROTOTYPE_METHOD(t, "stop", Stop);
+ NODE_SET_PROTOTYPE_METHOD(t, "resume", Resume);
+
+ target->Set(String::NewSymbol("Parser"), t->GetFunction());
+
+ sym_startElement = NODE_PSYMBOL("startElement");
+ sym_endElement = NODE_PSYMBOL("endElement");
+ sym_startCdata = NODE_PSYMBOL("startCdata");
+ sym_endCdata = NODE_PSYMBOL("endCdata");
+ sym_text = NODE_PSYMBOL("text");
+ sym_processingInstruction = NODE_PSYMBOL("processingInstruction");
+ sym_comment = NODE_PSYMBOL("comment");
+ sym_xmlDecl = NODE_PSYMBOL("xmlDecl");
+ sym_entityDecl = NODE_PSYMBOL("entityDecl");
+ sym_emit = NODE_PSYMBOL("emit");
+ }
+
+protected:
+ /*** Constructor ***/
+
+ static Handle<Value> New(const Arguments& args)
+ {
+ HandleScope scope;
+ XML_Char *encoding = NULL;
+ if (args.Length() == 1 && args[0]->IsString())
+ {
+ encoding = new XML_Char[32];
+ args[0]->ToString()->WriteAscii(encoding, 0, 32);
+ }
+
+ Parser *parser = new Parser(encoding);
+ if (encoding)
+ delete[] encoding;
+ parser->Wrap(args.This());
+ return args.This();
+ }
+
+ Parser(const XML_Char *encoding)
+ : ObjectWrap()
+ {
+ parser = XML_ParserCreate(encoding);
+ assert(parser != NULL);
+
+ XML_SetUserData(parser, this);
+ XML_SetElementHandler(parser, StartElement, EndElement);
+ XML_SetCharacterDataHandler(parser, Text);
+ XML_SetCdataSectionHandler(parser, StartCdata, EndCdata);
+ XML_SetProcessingInstructionHandler(parser, ProcessingInstruction);
+ XML_SetCommentHandler(parser, Comment);
+ XML_SetXmlDeclHandler(parser, XmlDecl);
+ XML_SetEntityDeclHandler(parser, EntityDecl);
+ }
+
+ ~Parser()
+ {
+ XML_ParserFree(parser);
+ }
+
+ /*** parse() ***/
+
+ static Handle<Value> Parse(const Arguments& args)
+ {
+ Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
+ HandleScope scope;
+ Local<String> str;
+ int isFinal = 0;
+
+ /* Argument 2: isFinal :: Bool */
+ if (args.Length() >= 2)
+ {
+ isFinal = args[1]->IsTrue();
+ }
+
+ /* Argument 1: buf :: String or Buffer */
+ if (args.Length() >= 1 && args[0]->IsString())
+ {
+ str = args[0]->ToString();
+ return scope.Close(parser->parseString(**str, isFinal) ? True() : False());
+ }
+ else if (args.Length() >= 1 && args[0]->IsObject())
+ {
+ Local<Object> obj = args[0]->ToObject();
+ if (Buffer::HasInstance(obj))
+ {
+#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3
+ Buffer *buffer = ObjectWrap::Unwrap<Buffer>(obj);
+ return scope.Close(parser->parseBuffer(*buffer, isFinal) ? True() : False());
+#else
+ return scope.Close(parser->parseBuffer(obj, isFinal) ? True() : False());
+#endif
+ }
+ else
+ return ThrowException(
+ Exception::TypeError(
+ String::New("Parse buffer must be String or Buffer")));
+ }
+ else
+ return ThrowException(
+ Exception::TypeError(
+ String::New("Parse buffer must be String or Buffer")));
+ }
+
+ /** Parse a v8 String by first writing it to the expat parser's
+ buffer */
+ bool parseString(String &str, int isFinal)
+ {
+ int len = str.Utf8Length();
+ if (len == 0)
+ return true;
+
+ void *buf = XML_GetBuffer(parser, len);
+ assert(buf != NULL);
+ assert(str.WriteUtf8(static_cast<char *>(buf), len) == len);
+
+ return XML_ParseBuffer(parser, len, isFinal) != XML_STATUS_ERROR;
+ }
+
+ /** Parse a node.js Buffer directly */
+#if NODE_MAJOR_VERSION == 0 && NODE_MINOR_VERSION < 3
+ bool parseBuffer(Buffer &buffer, int isFinal)
+ {
+ return XML_Parse(parser, buffer.data(), buffer.length(), isFinal) != XML_STATUS_ERROR;
+ }
+#else
+ bool parseBuffer(Local<Object> buffer, int isFinal)
+ {
+ return XML_Parse(parser, Buffer::Data(buffer), Buffer::Length(buffer), isFinal) != XML_STATUS_ERROR;
+ }
+#endif
+
+ /*** setEncoding() ***/
+
+ static Handle<Value> SetEncoding(const Arguments& args)
+ {
+ Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
+ HandleScope scope;
+
+ if (args.Length() == 1 && args[0]->IsString())
+ {
+ XML_Char *encoding = new XML_Char[32];
+ args[0]->ToString()->WriteAscii(encoding, 0, 32);
+
+ int status = parser->setEncoding(encoding);
+
+ delete[] encoding;
+
+ return scope.Close(status ? True() : False());
+ }
+ else
+ return False();
+ }
+
+ int setEncoding(XML_Char *encoding)
+ {
+ return XML_SetEncoding(parser, encoding) != 0;
+ }
+
+ /*** getError() ***/
+
+ static Handle<Value> GetError(const Arguments& args)
+ {
+ HandleScope scope;
+ Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
+
+ const XML_LChar *error = parser->getError();
+ if (error)
+ return scope.Close(String::New(error));
+ else
+ return scope.Close(Null());
+ }
+
+ /*** stop() ***/
+
+ static Handle<Value> Stop(const Arguments& args)
+ {
+ Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
+ HandleScope scope;
+
+ int status = parser->stop();
+
+ return scope.Close(status ? True() : False());
+ }
+
+ int stop()
+ {
+ return XML_StopParser(parser, XML_TRUE) != 0;
+ }
+
+ /*** resume() ***/
+
+ static Handle<Value> Resume(const Arguments& args)
+ {
+ Parser *parser = ObjectWrap::Unwrap<Parser>(args.This());
+ HandleScope scope;
+
+ int status = parser->resume();
+
+ return scope.Close(status ? True() : False());
+ }
+
+ int resume()
+ {
+ return XML_ResumeParser(parser) != 0;
+ }
+
+ const XML_LChar *getError()
+ {
+ enum XML_Error code;
+ code = XML_GetErrorCode(parser);
+ return XML_ErrorString(code);
+ }
+
+private:
+ /* expat instance */
+ XML_Parser parser;
+
+ /* no default ctor */
+ Parser();
+
+ /*** SAX callbacks ***/
+ /* Should a local HandleScope be used in those callbacks? */
+
+ static void StartElement(void *userData,
+ const XML_Char *name, const XML_Char **atts)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Collect atts into JS object */
+ Local<Object> attr = Object::New();
+ for(const XML_Char **atts1 = atts; *atts1; atts1 += 2)
+ attr->Set(String::New(atts1[0]), String::New(atts1[1]));
+
+ /* Trigger event */
+ Handle<Value> argv[3] = { sym_startElement,
+ String::New(name),
+ attr };
+ parser->Emit(3, argv);
+ }
+
+ static void EndElement(void *userData,
+ const XML_Char *name)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[2] = { sym_endElement, String::New(name) };
+ parser->Emit(2, argv);
+ }
+
+ static void StartCdata(void *userData)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[1] = { sym_startCdata };
+ parser->Emit(1, argv);
+ }
+
+ static void EndCdata(void *userData)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[1] = { sym_endCdata };
+ parser->Emit(1, argv);
+ }
+
+ static void Text(void *userData,
+ const XML_Char *s, int len)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[2] = { sym_text,
+ String::New(s, len) };
+ parser->Emit(2, argv);
+ }
+
+ static void ProcessingInstruction(void *userData,
+ const XML_Char *target, const XML_Char *data)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[3] = { sym_processingInstruction,
+ String::New(target),
+ String::New(data) };
+ parser->Emit(3, argv);
+ }
+
+ static void Comment(void *userData,
+ const XML_Char *data)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[2] = { sym_comment, String::New(data) };
+ parser->Emit(2, argv);
+ }
+
+ static void XmlDecl(void *userData,
+ const XML_Char *version, const XML_Char *encoding,
+ int standalone)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[4] = { sym_xmlDecl,
+ version ? String::New(version) : Null(),
+ encoding ? String::New(encoding) : Null(),
+ Boolean::New(standalone) };
+ parser->Emit(4, argv);
+ }
+
+ static void EntityDecl(void *userData, const XML_Char *entityName, int is_parameter_entity,
+ const XML_Char *value, int value_length, const XML_Char *base,
+ const XML_Char *systemId, const XML_Char *publicId, const XML_Char *notationName)
+ {
+ Parser *parser = reinterpret_cast<Parser *>(userData);
+
+ /* Trigger event */
+ Handle<Value> argv[8] = { sym_entityDecl,
+ entityName ? String::New(entityName) : Null(),
+ Boolean::New(is_parameter_entity),
+ value ? String::New(value) : Null(),
+ base ? String::New(base) : Null(),
+ systemId ? String::New(systemId) : Null(),
+ publicId ? String::New(publicId) : Null(),
+ notationName ? String::New(notationName) : Null(),
+ };
+ parser->Emit(8, argv);
+ }
+
+ void Emit(int argc, Handle<Value> argv[])
+ {
+ HandleScope scope;
+
+ Local<Function> emit = Local<Function>::Cast(handle_->Get(sym_emit));
+ emit->Call(handle_, argc, argv);
+ }
+};
+
+
+
+extern "C" void init(Handle<Object> target)
+{
+ HandleScope scope;
+ Parser::Initialize(target);
+}
View
23 src/node-expat/package.json
@@ -0,0 +1,23 @@
+{ "name": "node-expat"
+,"version": "1.4.0"
+,"main": "./lib/node-expat"
+,"description": "NodeJS binding for fast XML parsing."
+,"scripts" : { "install": "node-waf configure build"
+ ,"update": "node-waf build"
+ ,"test": "node test.js"
+ }
+,"dependencies": []
+,"devDependencies": ["vows"]
+,"repositories": [{ "type": "git"
+ ,"path": "git://github.com/astro/node-expat.git"
+ }]
+,"homepage": "http://github.com/astro/node-expat"
+,"bugs": "http://github.com/astro/node-expat/issues"
+,"maintainers": [{ "name": "Astro"
+ ,"email": "astro@spaceboyz.net"
+ ,"web": "http://spaceboyz.net/~astro/"
+ }]
+,"contributors": ["Stephan Maka", "Derek Hammer", "Iein Valdez", "Peter Körner", "Camilo Aguilar"]
+,"licenses": [{ "type": "MIT" }]
+,"engine": "node"
+}
View
254 src/node-expat/test.js
@@ -0,0 +1,254 @@
+var sys = require('sys');
+var expat = require('./lib/node-expat');
+var Buffer = require('buffer').Buffer;
+var vows = require('vows');
+var assert = require('assert');
+
+function collapseTexts(evs) {
+ var r = [];
+ var t = "";
+ evs.forEach(function(ev) {
+ if (ev[0] == 'text')
+ t += ev[1];
+ else {
+ if (t != "")
+ r.push(['text', t]);
+ t = "";
+ r.push(ev);
+ }
+ });
+ if (t != "")
+ r.push(['text', t]);
+ return r;
+}
+
+function expect(s, evs_expected) {
+ for(var step = s.length; step > 0; step--) {
+ var evs_received = [];
+ var p = new expat.Parser("UTF-8");
+ //p.setEncoding("UTF-8");
+ p.addListener('startElement', function(name, attrs) {
+ evs_received.push(['startElement', name, attrs]);
+ });
+ p.addListener('endElement', function(name) {
+ evs_received.push(['endElement', name]);
+ });
+ p.addListener('text', function(s) {
+ evs_received.push(['text', s]);
+ });
+ p.addListener('processingInstruction', function(target, data) {
+ evs_received.push(['processingInstruction', target, data]);
+ });
+ p.addListener('comment', function(s) {
+ evs_received.push(['comment', s]);
+ });
+ p.addListener('xmlDecl', function(version, encoding, standalone) {
+ evs_received.push(['xmlDecl', version, encoding, standalone]);
+ });
+ p.addListener('startCdata', function() {
+ evs_received.push(['startCdata']);
+ });
+ p.addListener('endCdata', function() {
+ evs_received.push(['endCdata']);
+ });
+ p.addListener('entityDecl', function(entityName, isParameterEntity, value, base, systemId, publicId, notationName) {
+ evs_received.push(['entityDecl', entityName, isParameterEntity, value, base, systemId, publicId, notationName]);
+ });
+ for(var l = 0; l < s.length; l += step)
+ {
+ var end = l + step;
+ if (end > s.length)
+ end = s.length;
+
+ if (!p.parse(s.slice(l, end), false))
+ evs_received.push(['error']);
+ }
+
+ var expected = JSON.stringify(evs_expected);
+ var received = JSON.stringify(collapseTexts(evs_received));
+ assert.equal(received, expected);
+ }
+}
+
+vows.describe('node-expat').addBatch({
+ 'single element': {
+ 'simple': function() {
+ expect("<r/>",
+ [['startElement', 'r', {}],
+ ['endElement', 'r']]);
+ },
+ 'single element with attribute': function() {
+ expect("<r foo='bar'/>",
+ [['startElement', 'r', {foo: 'bar'}],
+ ['endElement', 'r']]);
+ },
+ 'single elemeht with differently quoted attributes': function() {
+ expect("<r foo='bar' baz=\"quux\" test=\"tset\"/>",
+ [['startElement', 'r', {foo: 'bar', baz: 'quux', test: 'tset'}],
+ ['endElement', 'r']]);
+ },
+ 'single element with namespaces': function() {
+ expect("<r xmlns='http://localhost/' xmlns:x=\"http://example.com/\"></r>",
+ [['startElement', 'r', {xmlns: 'http://localhost/', 'xmlns:x': 'http://example.com/'}],
+ ['endElement', 'r']]);
+ },
+ 'single element with text content': function() {
+ expect("<r>foo</r>",
+ [['startElement', 'r', {}],
+ ['text', "foo"],
+ ['endElement', 'r']]);
+ },
+ 'single element with text content and line break': function() {
+ expect("<r>foo\nbar</r>",
+ [['startElement', 'r', {}],
+ ['text', "foo\nbar"],
+ ['endElement', 'r']]);
+ },
+ 'single element with CDATA content': function() {
+ expect("<r><![CDATA[<greeting>Hello, world!</greeting>]]></r>",
+ [['startElement', 'r', {}],
+ ['startCdata'],
+ ['text', "<greeting>Hello, world!</greeting>"],
+ ['endCdata'],
+ ['endElement', 'r']]);
+ },
+ 'single element with entity text': function() {
+ expect("<r>foo&amp;bar</r>",
+ [['startElement', 'r', {}],
+ ['text', "foo&bar"],
+ ['endElement', 'r']]);
+ },
+ 'single element with umlaut text': function() {
+ expect("<r>ß</r>",
+ [['startElement', 'r', {}],
+ ['text', "ß"],
+ ['endElement', 'r']]);
+ },
+ 'from buffer': function() {
+ expect(new Buffer('<foo>bar</foo>'),
+ [['startElement', 'foo', {}],
+ ['text', 'bar'],
+ ['endElement', 'foo']]);
+ }
+ },
+ 'entity declaration': {
+ 'a billion laughs': function() {
+ expect('<!DOCTYPE b [<!ELEMENT b (#PCDATA)>' +
+ '<!ENTITY l0 "ha"><!ENTITY l1 "&l0;&l0;"><!ENTITY l2 "&l1;&l1;">' +
+ ']><b>&l2;</b>',
+ [["entityDecl","l0",false,"ha",null,null,null,null],
+ ["entityDecl","l1",false,"&l0;&l0;",null,null,null,null],
+ ["entityDecl","l2",false,"&l1;&l1;",null,null,null,null],
+ ["startElement","b",{}],["text","hahahaha"],["endElement","b"]]);
+ }
+ },
+ 'processing instruction': {
+ 'with parameters': function() {
+ expect("<?i like xml?>",
+ [['processingInstruction', 'i', 'like xml']]);
+ },
+ 'simple': function() {
+ expect("<?dragons?>",
+ [['processingInstruction', 'dragons', '']]);
+ },
+ 'XML declaration with encoding': function() {
+ expect("<?xml version='1.0' encoding='UTF-8'?>",
+ [['xmlDecl', '1.0', 'UTF-8', true]]);
+ },
+ 'XML declaration': function() {
+ expect("<?xml version='1.0'?>",
+ [['xmlDecl', '1.0', null, true]]);
+ }
+ },
+ 'comment': {
+ 'simple': function() {
+ expect("<!-- no comment -->",
+ [['comment', ' no comment ']]);
+ }
+ },
+ 'error': {
+ 'tag name starting with ampersand': function() {
+ expect("<&", [['error']]);
+ }
+ },
+ 'stop and resume': {
+ topic: function() {
+ var cb = this.callback;
+ var p = new expat.Parser("UTF-8");
+
+ var input = '\
+ <wrap> \
+ <short /> \
+ <short></short> \
+ <long /> \
+ <short /> \
+ <long>foo</long> \
+ </wrap>';
+
+ var expected = ['wrap', 'short', 'short', 'long', 'short', 'long'];
+ var received = [];
+
+ var tolerance = 10/100;
+ var expectedRuntime = 1000;
+ var start = new Date();
+
+ p.addListener('startElement', function(name, attrs) {
+ received.push(name);
+
+ // suspend parser for 1/2 second
+ if(name == 'long') {
+ p.stop();
+
+ setTimeout(function() {
+ p.resume();
+ }, 500);
+ }
+ });
+
+ p.addListener('endElement', function(name) {
+ // finished parsing
+ if(name == 'wrap') {
+ // test elements received (count. naming, order)
+ assert.equal(JSON.stringify(received), JSON.stringify(expected));
+
+ // test timing (+-5%)
+ var now = new Date();
+ var diff = now.getTime() - start.getTime();
+ var max = expectedRuntime + expectedRuntime * tolerance,
+ min = expectedRuntime - expectedRuntime * tolerance;
+
+ assert.ok(diff < max, 'Runtime within maximum expected time');
+ assert.ok(diff > min, 'Runtime at least minimum expected time');
+
+ return cb(true);
+ }
+ });
+
+ assert.ok(p.parse(input));
+ },
+ 'should have worked': function() {
+ assert.ok(true, 'start & stop works');
+ }
+ },
+ 'corner cases': {
+ 'parse empty string': function() {
+ var p = new expat.Parser("UTF-8");
+ p.parse('');
+ assert.ok(true, "Did not segfault");
+ },
+
+ 'parsing twice the same document with the same parser instance should be fine': 'reset() not yet implemented'
+ /*function() {
+ var p = new expat.Parser("UTF-8");
+ var xml = "<foo>bar</foo>";
+ var result = p.parse(xml);
+ assert.ok(result);
+ assert.isNull(p.getError());
+
+ var result2 = p.parse(xml);
+ assert.isNull(p.getError());
+ assert.ok(result2);
+
+ }*/
+ }
+}).run();
View
23 src/node-expat/wscript
@@ -0,0 +1,23 @@
+srcdir = '.'
+blddir = 'build'
+VERSION = '1.1.0'
+
+def set_options(opt):
+ opt.tool_options('compiler_cxx')
+
+def configure(conf):
+ conf.check_tool('compiler_cxx')
+ conf.check_tool('node_addon')
+ conf.check( header_name='expat.h',
+ mandatory = True,
+ errmsg = "not installed")
+
+ conf.env['LIB_EXPAT'] = ['expat']
+
+
+def build(bld):
+ obj = bld.new_task_gen('cxx', 'shlib', 'node_addon')
+ obj.target = 'node-expat'
+ obj.source = 'node-expat.cc'
+ obj.lib = 'expat'
+ obj.uselib = 'EXPAT'
Please sign in to comment.
Something went wrong with that request. Please try again.