Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP

Loading…

xml stream #2

Merged
merged 8 commits into from

2 participants

@dodo

i hope the code is not to ugly .. but it should work

the only api changes i made are, that when a xml.element is contained, it will return a readable stream, otherwise its a string like before.

the other change are the options. i like to use objects for options because its more readable code when its XML({foo:null}, {indent:true}) instead of just XML({foo:null}, true). if you don't like it, revert it.

the other option i added is that you can force the result to be a stream .. so even if XML could return a string it doesn't.

ps: i would like to see this feature in your other project node-atom (:

@dylang dylang merged commit bd3981b into dylang:master
@dylang
Owner

I haven't had time to figure out everything you've done but i'm happy with the tests and and features you added so i did a couple cleanups published the update. I hope you don't mind me adding you as a contributor in the package.json.

@dodo

no .. that's nice :]

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Commits on Apr 30, 2011
  1. @dodo

    extract xml formatting

    dodo authored
  2. @dodo

    handle null as single tag

    dodo authored
  3. @dodo

    streaming api

    dodo authored
  4. @dodo

    test stream

    dodo authored
  5. @dodo

    stream example

    dodo authored
  6. @dodo

    use object for options

    dodo authored
  7. @dodo

    update readme

    dodo authored
  8. @dodo

    add example server

    dodo authored
This page is out of date. Refresh to see the latest.
View
20 examples/examples.js
@@ -55,4 +55,22 @@ console.log(XML(example5, true));
<description><![CDATA[<strong>Master of the Universe!</strong>]]></description>
</toy>
</toys>
-*/
+*/
+
+var elem = XML.Element({ _attr: { decade: '80s', locale: 'US'} });
+var xml = XML({ toys: elem }, true);
+xml.on('data', function (chunk) {console.log("data:", chunk)});
+elem.push({ toy: 'Transformers' });
+elem.push({ toy: 'GI Joe' });
+elem.push({ toy: [{name:'He-man'}] });
+elem.close();
+
+/*
+data: <toys decade="80s" locale="US">
+data: <toy>Transformers</toy>
+data: <toy>GI Joe</toy>
+data: <toy>
+ <name>He-man</name>
+ </toy>
+data: </toys>
+*/
View
24 examples/server.js
@@ -0,0 +1,24 @@
+var http = require('http'),
+ XML = require('../lib/xml');
+
+var server = http.createServer(function(req, res) {
+ res.writeHead(200, {"Content-Type": "text/xml"});
+
+ var elem = XML.Element({ _attr: { decade: '80s', locale: 'US'} });
+ var xml = XML({ toys: elem }, {indent:true, stream:true});
+
+ res.write('<?xml version="1.0" encoding="utf-8"?>\n');
+
+ xml.pipe(res);
+
+ process.nextTick(function () {
+ elem.push({ toy: 'Transformers' });
+ elem.push({ toy: 'GI Joe' });
+ elem.push({ toy: [{name:'He-man'}] });
+ elem.close();
+ });
+
+});
+
+server.listen(parseInt(process.env.PORT) || 3000);
+console.log("server listening on port %d …", server.address().port);
View
181 lib/xml.js
@@ -1,20 +1,107 @@
var util = require('./util');
+var Stream = require('stream').Stream;
var DEFAULT_INDENT = ' ';
-function xml (input, indent) {
- var output = [];
+function xml (input, opts) {
+ var stream;
+ var output = "";
+ var interupted = false;
- indent = !indent ? '' : indent === true ? DEFAULT_INDENT : indent;
+ if (typeof opts != 'object') {
+ opts = { indent:opts };
+ }
+
+ var indent = !opts.indent ? '' : opts.indent === true ?
+ DEFAULT_INDENT : opts.indent;
+
+ if (opts.stream)
+ stream = new Stream();
+
+ var instant = true,
+ delay = function (func) {
+ if (!instant) {
+ func();
+ } else {
+ process.nextTick(func);
+ }
+ };
+ // disable delay delayed
+ delay(function () { instant = false });
+
+ var append = function (interupt, out) {
+ if (out !== undefined) {
+ output += out;
+ }
+ if (interupt && !interupted) {
+ stream = stream || new Stream();
+ interupted = true;
+ }
+ if (interupt && interupted) {
+ var data = output;
+ delay(function () { stream.emit('data', data) });
+ output = "";
+ }
+ };
+
+ var add = function (value, last) {
+ format(append, resolve(value, indent, indent ? 1 : 0), last);
+ };
+
+ var end = function () {
+ if (stream) {
+ var data = output;
+ delay(function () { stream.emit('data', data) });
+
+ stream.emit('end');
+ stream.readable = false;
+ stream.emit('close');
+ }
+ };
if (input && input.forEach) {
- input.forEach(function(value){
- output.push(resolve(value, indent, indent ? 1 : 0));
+ input.forEach(function (value, i) {
+ var last;
+ if (i + 1 == input.length)
+ last = end;
+ add(value, last);
});
} else {
- output.push(resolve(input, indent, indent ? 1 : 0));
+ add(input, end);
+ }
+
+ if (stream) {
+ stream.readable = true;
+ return stream;
}
- return output.join(indent ? '\n' : '');
+ return output;
+}
+
+function element (/*input, …*/) {
+ var input = Array.prototype.slice.call(arguments);
+ var self = { _elem:resolve(input) };
+
+ self.push = function (input) {
+ if (!this.append) {
+ throw new Error("not assigned to a parent!");
+ }
+ var that = this;
+ var indent = this._elem.indent;
+ format(this.append, resolve(
+ input, indent, this._elem.icount + (indent ? 1 : 0)),
+ function () { that.append(true) });
+ };
+
+ self.close = function (input) {
+ if (input !== undefined) {
+ this.push(input);
+ }
+ if (this.end) {
+ this.end();
+ }
+ };
+
+ return self;
}
function create_indent(character, count) {
@@ -26,11 +113,21 @@ function resolve(data, indent, indent_count) {
var indent_spaces = create_indent(indent, indent_count);
var name;
var values = data;
+ var interupt = false;
if (typeof data == 'object') {
var keys = Object.keys(data);
name = keys[0];
values = data[name];
+
+ if (values._elem) {
+ values._elem.name = name;
+ values._elem.icount = indent_count;
+ values._elem.indent = indent;
+ values._elem.indents = indent_spaces;
+ values._elem.interupt = values;
+ return values._elem;
+ }
}
var attributes = [],
@@ -41,11 +138,12 @@ function resolve(data, indent, indent_count) {
keys.forEach(function(key){
attributes.push(attribute(key, obj[key]));
});
-
}
switch(typeof values) {
case 'object':
+ if (values === null) break;
+
if (values._attr) {
get_attributes(values._attr);
}
@@ -63,11 +161,13 @@ function resolve(data, indent, indent_count) {
if (_name == '_attr') {
get_attributes(value._attr);
} else {
- content.push(resolve(value, indent, indent_count + 1));
+ content.push(resolve(
+ value, indent, indent_count + 1));
}
} else {
//string
- content.push(create_indent(indent, indent_count + 1) + util.xml_safe(value));
+ content.push(create_indent(
+ indent, indent_count + 1) + util.xml_safe(value));
}
});
@@ -81,20 +181,61 @@ function resolve(data, indent, indent_count) {
}
- return indent_spaces
- + (name ? '<' + name : '')
- + (attributes.length ? ' '
- + attributes.join(' ')
- : '')
- + (content.length ? (name ? '>' : '')
- + content.join(indent ? '\n' : '')
- + (content.length > 1 ? indent_spaces : '')
- + (name ? '</' + name + '>' : '')
- : (name ? '/>' : ''));
+ return { name: name,
+ interupt: interupt,
+ attributes: attributes,
+ content: content,
+ icount: indent_count,
+ indents: indent_spaces,
+ indent: indent };
+}
+
+function format(append, elem, end) {
+ if (typeof elem != 'object') {
+ return append(false, elem);
+ }
+
+ var len = elem.interupt ? 1 : elem.content.length;
+ append(false, elem.indents
+ + (elem.name ? '<' + elem.name : '')
+ + (elem.attributes.length ? ' ' + elem.attributes.join(' ') : '')
+ + (len ? (elem.name ? '>' : '') : (elem.name ? '/>' : ''))
+ + (elem.indent && len > 1 ? '\n' : ''));
+
+ if (!len) return append(false, elem.indent ? '\n' : '');
+
+ var interupt, proceed = function () {
+ while (elem.content.length) {
+ var value = elem.content.shift();
+
+ if (value === undefined) continue;
+ if (interupt(value)) return;
+
+ format(append, value);
+ }
+ append(false, (len > 1 ? elem.indents : '')
+ + (elem.name ? '</' + elem.name + '>' : '')
+ + (elem.indent && !end ? '\n' : ''));
+ if (end) end();
+ };
+
+ interupt = function (value) {
+ if (value.interupt) {
+ value.interupt.append = append;
+ value.interupt.end = proceed;
+ value.interupt = false;
+ append(true);
+ return true;
+ }
+ return false;
+ };
+
+ if (!interupt(elem)) proceed();
}
function attribute(key, value) {
return key + '=' + '"' + util.xml_safe(value) + '"';
}
+xml.Element = element;
module.exports = xml;
View
38 readme.md
@@ -20,21 +20,23 @@
Everything should pass:
test
- ? empty
- ? simple
- ? deep
- ? indent
- ? attributes
- ? cdata
- ? encoding
+ ✔ empty
+ ✔ simple
+ ✔ deep
+ ✔ indent
+ ✔ attributes
+ ✔ cdata
+ ✔ encoding
+ ✔ stream
- OK: 30 assertions (4ms)
+ OK: 33 assertions (7ms)
## API
- XML(object, [indent])
+ XML(object, [indent || options])
* __object__ See Usage for how this can work.
* __indent__ Falsy value: No indent or line breaks (default). True: 4 spaces. '\t': Single tab. ' ': Two spaces. Etc.
+ * __options__ 'indent': same as __indent__, 'stream': force result to be a stream
## Usage
@@ -96,6 +98,24 @@ Everything should pass:
</toys>
*/
+ var elem = XML.Element({ _attr: { decade: '80s', locale: 'US'} });
+ var xml = XML({ toys: elem }, true);
+ xml.on('data', function (chunk) {console.log("data:", chunk)});
+ elem.push({ toy: 'Transformers' });
+ elem.push({ toy: 'GI Joe' });
+ elem.push({ toy: [{name:'He-man'}] });
+ elem.close();
+
+ /*
+ data: <toys decade="80s" locale="US">
+ data: <toy>Transformers</toy>
+ data: <toy>GI Joe</toy>
+ data: <toy>
+ <name>He-man</name>
+ </toy>
+ data: </toys>
+ */
+
## Keywords
View
21 test/test.js
@@ -62,6 +62,27 @@ module.exports = {
encoding: function(test) {
test.equal(XML([ { a: [ { _attr: { anglebrackets: 'this is <strong>strong</strong>', url: 'http://google.com?s=opower&y=fun' } }, 'text'] } ]), '<a anglebrackets="this is &lt;strong&gt;strong&lt;/strong&gt;" url="http://google.com?s=opower&amp;y=fun">text</a>');
test.done();
+ },
+
+ stream: function (test) {
+ var elem = XML.Element({ _attr: { decade: '80s', locale: 'US'} });
+ var xmlstream = XML({ toys: elem });
+ var results = ['<toys decade="80s" locale="US">','<toy>Transformers</toy>','<toy><name>He-man</name></toy>','<toy>GI Joe</toy>','</toys>'];
+
+ xmlstream.on('data', function (stanza) {
+ test.equal(stanza, results.shift());
+ });
+ xmlstream.on('close', function () {
+ test.deepEqual(results, []);
+ test.done();
+ });
+
+ elem.push({ toy: 'Transformers' });
+ elem.push({ toy: [ { name: 'He-man' } ] });
+ setTimeout(function () {
+ elem.push({ toy: 'GI Joe' });
+ elem.close();
+ }, 1);
}
};
Something went wrong with that request. Please try again.