trumpet
Advanced tools
Comparing version 1.6.3 to 1.6.4
@@ -8,8 +8,5 @@ var trumpet = require('../'); | ||
var stream = span.createStream(); | ||
stream.end('PLACEHOLDER'); | ||
/* | ||
stream.pipe(through(function (buf) { | ||
this.queue(buf.toString().toUpperCase()); | ||
})).pipe(stream); | ||
*/ | ||
}); | ||
@@ -16,0 +13,0 @@ |
443
index.js
@@ -1,320 +0,183 @@ | ||
var through = require('through'); | ||
var tokenize = require('./lib/tokenize.js'); | ||
var parseSelector = require('./lib/selector.js'); | ||
var matcher = require('./lib/matcher.js'); | ||
var ent = require('ent'); | ||
var EventEmitter = require('events').EventEmitter; | ||
var Duplex = require('readable-stream').Duplex; | ||
var inherits = require('inherits'); | ||
var duplexer = require('duplexer'); | ||
var through = require('through2'); | ||
var duplexer = require('duplexer2'); | ||
module.exports = function (opts) { | ||
var selectors = []; | ||
var tokens = tokenize(); | ||
var tokenBuffer = null; | ||
var skipping = false; | ||
var skipSpace = false; | ||
var lastToken = null; | ||
var tokenize = require('html-tokenize'); | ||
var select = require('html-select'); | ||
var wrapElem = require('./lib/wrap.js'); | ||
module.exports = Trumpet; | ||
inherits(Trumpet, Duplex); | ||
function Trumpet () { | ||
var self = this; | ||
if (!(this instanceof Trumpet)) return new Trumpet; | ||
Duplex.call(this); | ||
this._tokenize = tokenize(); | ||
this._writing = false; | ||
this._piping = false; | ||
this._select = this._tokenize.pipe(select()); | ||
this._select.once('end', function () { | ||
self.emit('_end'); | ||
self.push(null) | ||
}); | ||
this.once('finish', function () { self._tokenize.end() }); | ||
} | ||
Trumpet.prototype.pipe = function () { | ||
this._piping = true; | ||
return Duplex.prototype.pipe.apply(this, arguments); | ||
}; | ||
Trumpet.prototype._read = function (n) { | ||
var self = this; | ||
var buf, read = 0; | ||
var s = this._select; | ||
while ((row = s.read()) !== null) { | ||
if (row[1].length) { | ||
this.push(row[1]); | ||
read ++; | ||
} | ||
} | ||
if (read === 0) s.once('readable', function () { self._read(n) }); | ||
}; | ||
Trumpet.prototype._write = function (buf, enc, next) { | ||
if (!this._writing && !this._piping) { | ||
this._piping = true; | ||
this.resume(); | ||
} | ||
return this._tokenize._write(buf, enc, next); | ||
}; | ||
Trumpet.prototype.select = function (str, cb) { | ||
var self = this; | ||
var first = true; | ||
tokens.pipe(through(write, end)); | ||
var res = self._selectAll(str, function (elem) { | ||
if (!first) return; | ||
first = false; | ||
res.createReadStream = function () {}; | ||
res.createWriteStream = function () {}; | ||
res.createStream = function () {}; | ||
if (cb) cb(elem); | ||
}); | ||
return res; | ||
}; | ||
Trumpet.prototype.selectAll = function (str, cb) { | ||
return this._selectAll(str, cb); | ||
}; | ||
Trumpet.prototype._selectAll = function (str, cb) { | ||
var self = this; | ||
var readers = [], writers = [], duplex = []; | ||
var gets = [], getss = [], sets = [], removes = []; | ||
var tr = through( | ||
function (buf) { tokens.write(buf) }, | ||
function () { tokens.end() } | ||
); | ||
tr.select = function (sel) { | ||
var r = createResult(sel, { all: false }); | ||
return r; | ||
}; | ||
tr.selectAll = function (sel, cb) { | ||
var r = createResult(sel, { all: true }); | ||
r._matcher.on('pre-open', function (m) { | ||
r.name = m.current.name; | ||
r.attributes = m.current.attributes; | ||
r.isSelfClosing = m.current.isSelfClosing; | ||
cb(r); | ||
this.once('_end', function () { | ||
readers.splice(0).forEach(function (r) { | ||
r.end(); | ||
r.resume(); | ||
}); | ||
r._matcher.on('tag-end', function (node) { | ||
r._getAttr = {}; | ||
r._setAttr = {}; | ||
r._rmAttr = {}; | ||
duplex.splice(0).forEach(function (d) { | ||
d.end(); | ||
d.resume(); | ||
}); | ||
}; | ||
tr.createReadStream = function (sel, opts) { | ||
return tr.select(sel).createReadStream(opts); | ||
}; | ||
tr.createWriteStream = function (sel, opts) { | ||
return tr.select(sel).createWriteStream(opts); | ||
}; | ||
tr.createStream = function (sel, opts) { | ||
return tr.select(sel).createStream(opts); | ||
}; | ||
// End any read streams of unmatched selectors | ||
tr.on("end", function() { | ||
selectors.forEach(function(r) { | ||
r._readStreams.forEach(function(s) { | ||
if (s.readable) s.end(); | ||
}); | ||
}); | ||
}); | ||
return tr; | ||
function createResult (sel, opts) { | ||
var r = new Result(sel); | ||
var element, welem; | ||
this._select.select(str, function (elem) { | ||
element = elem; | ||
welem = wrapElem(elem); | ||
if (cb) cb(welem); | ||
if (opts.all === false) { | ||
r._matcher.once('unmatch', function () { | ||
if (!r._reading && !r._writing) { | ||
var ix = selectors.indexOf(r); | ||
if (ix >= 0) selectors.splice(ix, 1); | ||
} | ||
}); | ||
r.once('read-close', function () { | ||
var ix = selectors.indexOf(r); | ||
if (ix >= 0) selectors.splice(ix, 1); | ||
}); | ||
} | ||
elem.once('close', function () { | ||
element = null; | ||
welem = null; | ||
}); | ||
r.on('_write-begin', function (stream) { | ||
if (lastToken[0] === 'tag-end' | ||
&& lastToken[1].length > 0 | ||
&& '>' === String.fromCharCode(lastToken[1][lastToken[1].length-1]) | ||
) { | ||
if (lastToken[1].length) tr.queue(lastToken[1]); | ||
} | ||
if (stream._skipping !== false) { | ||
tokens.pause(); | ||
} | ||
skipping = true; | ||
stream.pipe(through(write, end)); | ||
stream.resume(); | ||
function write (buf) { | ||
if (Buffer.isBuffer(buf)) { | ||
if (buf.length) tr.queue(buf) | ||
} | ||
else if (typeof buf === 'string') { | ||
if (buf.length) tr.queue(Buffer(buf)); | ||
} | ||
else { | ||
buf = String(buf); | ||
if (buf.length) tr.queue(Buffer(buf)); | ||
} | ||
} | ||
function end () { | ||
if (stream._skipping !== false) { | ||
tokens.resume(); | ||
} | ||
} | ||
readers.splice(0).forEach(function (r) { | ||
welem.createReadStream(r._options).pipe(r); | ||
}); | ||
r.on('_write-end', function () { | ||
skipping = false; | ||
writers.splice(0).forEach(function (w) { | ||
w.pipe(welem.createWriteStream(w._options)); | ||
}); | ||
r.on('queue', function (buf) { tr.queue(buf) }); | ||
duplex.splice(0).forEach(function (d) { | ||
d.input.pipe(welem.createStream(d.options)) | ||
.pipe(d.output) | ||
; | ||
}); | ||
selectors.push(r); | ||
return r; | ||
} | ||
function write (lex) { | ||
lastToken = lex; | ||
var writeSkip = false; | ||
gets.splice(0).forEach(function (g) { | ||
welem.getAttribute(g[0], g[1]); | ||
}); | ||
var sub; | ||
selectors.forEach(function (s) { | ||
s._at(lex); | ||
if (s._substitute !== undefined) { | ||
sub = s._substitute; | ||
s._substitute = undefined; | ||
} | ||
if (s._writing === 'next') { | ||
s._writing = false; | ||
s.emit('_write-end'); | ||
writeSkip = true; | ||
} | ||
getss.splice(0).forEach(function (cb) { | ||
welem.getAttributes(cb); | ||
}); | ||
if (skipSpace) { | ||
skipSpace = false; | ||
if (lex[0] === 'tag-space') return; | ||
} | ||
if (skipping || writeSkip) return; | ||
sets.splice(0).forEach(function (g) { | ||
welem.setAttribute(g[0], g[1]); | ||
}); | ||
if (sub === undefined) { | ||
if (lex[1].length) tr.queue(lex[1]); | ||
} | ||
else if (sub === null) { | ||
skipSpace = true; | ||
} | ||
else if (sub.length) tr.queue(sub); | ||
} | ||
function end () { | ||
tr.queue(null); | ||
} | ||
}; | ||
inherits(Result, EventEmitter); | ||
function Result (sel) { | ||
var self = this; | ||
self._setAttr = {}; | ||
self._rmAttr = {}; | ||
self._getAttr = {}; | ||
self._readStreams = []; | ||
self._writeStream = null; | ||
self._reading = false; | ||
self._writing = false; | ||
self._matcher = matcher(parseSelector(sel)); | ||
var remainingSets = []; | ||
self._matcher.on('open', function (m) { | ||
remainingSets = Object.keys(self._setAttr); | ||
if (self._writeStream && self._writeStream.outer) { | ||
self._writing = true; | ||
self._writeLevel = m.stack.length; | ||
self.emit('_write-begin', self._writeStream); | ||
} | ||
removes.splice(0).forEach(function (key) { | ||
welem.removeAttribute(key); | ||
}); | ||
}); | ||
self._matcher.on('tag-end', function (m) { | ||
for (var i = 0; i < remainingSets.length; i++) { | ||
var key = remainingSets[i]; | ||
self.emit('queue', Buffer(' ' + self._setAttr[key])); | ||
return { | ||
getAttribute: function (key, cb) { | ||
if (welem) return welem.getAttribute(key, cb); | ||
gets.push([ key, cb ]); | ||
}, | ||
getAttributes: function (cb) { | ||
getss.push(cb); | ||
}, | ||
setAttribute: function (key, value) { | ||
if (welem) return welem.setAttribute(key, value); | ||
sets.push([ key, value ]); | ||
}, | ||
removeAttribute: function (key) { | ||
if (welem) return welem.removeAttribute(key); | ||
removes.push(key); | ||
}, | ||
createReadStream: function (opts) { | ||
if (welem) return welem.createReadStream(opts); | ||
var r = through(); | ||
r._options = opts; | ||
readers.push(r); | ||
return r; | ||
}, | ||
createWriteStream: function (opts) { | ||
if (welem) return welem.createWriteStream(opts); | ||
var w = through(); | ||
w._options = opts; | ||
writers.push(w); | ||
return w; | ||
}, | ||
createStream: function (opts) { | ||
if (welem) return welem.createStream(opts); | ||
var d = { input: through(), output: through() }; | ||
d.options = opts; | ||
duplex.push(d); | ||
return duplexer(d.input, d.output); | ||
} | ||
if (self._readStreams.length) { | ||
self._reading = true; | ||
self._readMatcher = m; | ||
self._readLevel = m.stack.length; | ||
for (var i = 0; i < self._readStreams.length; i++) { | ||
if (self._readStreams[i]._level === undefined) { | ||
self._readStreams[i]._level = self._readLevel; | ||
} | ||
} | ||
} | ||
if (self._writeStream && !self._writeStream.outer) { | ||
self._writing = true; | ||
self._writeLevel = m.stack.length; | ||
self.emit('_write-begin', self._writeStream); | ||
} | ||
}); | ||
self._matcher.on('attribute', function (node) { | ||
var f = self._getAttr[node.name]; | ||
if (f) f(node.value); | ||
var v = self._setAttr[node.name]; | ||
if (v !== undefined) { | ||
self._substitute = v; | ||
var ix = remainingSets.indexOf(node.name); | ||
if (ix >= 0) remainingSets.splice(ix, 1); | ||
} | ||
if (self._rmAttr[node.name]) { | ||
self._substitute = null; | ||
} | ||
}); | ||
} | ||
Result.prototype._at = function (lex) { | ||
if (this._reading) { | ||
if (lex[0] === 'closetag') { | ||
var level = this._matcher.matchers[0].stack.length; | ||
var removed = 0; | ||
for (var i = this._readStreams.length - 1; i >= 0; i--) { | ||
var s = this._readStreams[i]; | ||
if (s._level === level) { | ||
if (s.outer && lex[1].length) s.queue(lex[1]); | ||
s.queue(null); | ||
removed ++; | ||
this._readStreams.splice(i, 1); | ||
} | ||
} | ||
if (this._readStreams.length === 0) { | ||
this._reading = false; | ||
} | ||
if (removed > 0) this.emit('read-close'); | ||
} | ||
for (var i = 0; i < this._readStreams.length; i++) { | ||
var s = this._readStreams[i]; | ||
if (s._level !== undefined && lex[1].length) s.queue(lex[1]); | ||
} | ||
} | ||
if (this._writing === 'next') { | ||
this._writing = false; | ||
this.emit('_write-end'); | ||
} | ||
else if (this._writing && lex[0] === 'closetag') { | ||
var level = this._matcher.matchers[0].stack.length; | ||
if (level === this._writeLevel) { | ||
if (this._writeStream.outer) { | ||
this._writing = 'next'; | ||
} | ||
else { | ||
this._writing = false; | ||
this.emit('_write-end'); | ||
} | ||
} | ||
} | ||
var matching = this._matcher.at(lex[0], lex[2]); | ||
if (matching) { | ||
for (var i = 0; i < this._readStreams.length; i++) { | ||
var rs = this._readStreams[i]; | ||
if (rs.outer && lex[1].length) rs.queue(lex[1]); | ||
} | ||
} | ||
}; | ||
}; | ||
Result.prototype.setAttribute = function (key, value) { | ||
var sub = Buffer(ent.encode(key) + '="' + ent.encode(value) + '"'); | ||
this._setAttr[key.toUpperCase()] = sub; | ||
return this; | ||
Trumpet.prototype.createReadStream = function (sel, opts) { | ||
return this.select(sel).createReadStream(opts); | ||
}; | ||
Result.prototype.removeAttribute = function (key) { | ||
this._rmAttr[key.toUpperCase()] = true; | ||
return this; | ||
Trumpet.prototype.createWriteStream = function (sel, opts) { | ||
return this.select(sel).createWriteStream(opts); | ||
}; | ||
Result.prototype.getAttribute = function (key, cb) { | ||
this._getAttr[key.toUpperCase()] = cb; | ||
Trumpet.prototype.createStream = function (sel, opts) { | ||
return this.select(sel).createStream(opts); | ||
}; | ||
Result.prototype.createWriteStream = function (opts) { | ||
if (!opts) opts = {}; | ||
var stream = through().pause(); | ||
if (opts.outer) stream.outer = true; | ||
this._writeStream = stream; | ||
return stream; | ||
}; | ||
Result.prototype.createReadStream = function (opts) { | ||
if (!opts) opts = {}; | ||
var stream = through(); | ||
if (opts.outer) stream.outer = true; | ||
this._readStreams.push(stream); | ||
return stream; | ||
}; | ||
Result.prototype.createStream = function (opts) { | ||
var ws = Result.prototype.createWriteStream.call(this, opts); | ||
ws._skipping = false; | ||
var rs = Result.prototype.createReadStream.call(this, opts); | ||
return duplexer(ws, rs); | ||
}; |
{ | ||
"name" : "trumpet", | ||
"version" : "1.6.3", | ||
"description" : "parse and transform streaming html using css selectors", | ||
"main" : "index.js", | ||
"dependencies" : { | ||
"sax" : "~0.5.4", | ||
"ent" : "~0.0.5", | ||
"through": "~2.3.4", | ||
"duplexer": "~0.1.1", | ||
"buffers": "~0.1.1", | ||
"inherits": "~2.0.0" | ||
}, | ||
"devDependencies" : { | ||
"tap" : "~0.4.0", | ||
"tape" : "~1.0.4", | ||
"concat-stream" : "~1.0.0" | ||
}, | ||
"scripts" : { | ||
"test" : "tap test/*.js" | ||
}, | ||
"repository" : { | ||
"type" : "git", | ||
"url" : "http://github.com/substack/node-trumpet.git" | ||
}, | ||
"keywords" : [ | ||
"html", | ||
"streaming", | ||
"parser", | ||
"transform", | ||
"selectors", | ||
"css" | ||
], | ||
"author" : { | ||
"name" : "James Halliday", | ||
"email" : "mail@substack.net", | ||
"url" : "http://substack.net" | ||
}, | ||
"license" : "MIT", | ||
"engine" : { "node" : ">=0.4" } | ||
"name": "trumpet", | ||
"version": "1.6.4", | ||
"description": "parse and transform streaming html using css selectors", | ||
"main": "index.js", | ||
"dependencies": { | ||
"html-select": "^2.3.5", | ||
"html-tokenize": "^1.1.1", | ||
"through2": "^1.0.0", | ||
"duplexer2": "~0.0.2", | ||
"inherits": "^2.0.0", | ||
"readable-stream": "^1.0.27-1" | ||
}, | ||
"devDependencies": { | ||
"tape": "~2.12.3", | ||
"concat-stream": "~1.4.5", | ||
"through": "~2.3.4" | ||
}, | ||
"scripts": { | ||
"test": "tape test/*.js" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "http://github.com/substack/node-trumpet.git" | ||
}, | ||
"keywords": [ | ||
"html", | ||
"streaming", | ||
"parser", | ||
"transform", | ||
"selectors", | ||
"css" | ||
], | ||
"author": { | ||
"name": "James Halliday", | ||
"email": "mail@substack.net", | ||
"url": "http://substack.net" | ||
}, | ||
"license": "MIT", | ||
"engine": { | ||
"node": ">=0.4" | ||
} | ||
} |
var test = require('tape'); | ||
var trumpet = require('../'); | ||
var through = require('through'); | ||
var through = require('through2'); | ||
var concat = require('concat-stream'); | ||
@@ -13,4 +13,5 @@ var fs = require('fs'); | ||
var loud = tr.select('.loud').createStream(); | ||
loud.pipe(through(function (buf) { | ||
this.queue(buf.toString().toUpperCase()); | ||
loud.pipe(through(function (buf, enc, next) { | ||
this.push(buf.toString().toUpperCase()); | ||
next(); | ||
})).pipe(loud); | ||
@@ -17,0 +18,0 @@ |
@@ -23,3 +23,3 @@ var trumpet = require('../'); | ||
tr.selectAll('.b span', function (span) { | ||
t.equal(span.name, 'SPAN'); | ||
t.equal(span.name, 'span'); | ||
var rs = span.createReadStream(); | ||
@@ -26,0 +26,0 @@ rs.pipe(output, { end: false }); |
@@ -77,3 +77,3 @@ var trumpet = require('../'); | ||
var c_ = classes.shift(); | ||
t.equal(div.attributes.CLASS, c_); | ||
t.equal(div.getAttribute('class'), c_); | ||
@@ -80,0 +80,0 @@ div.getAttribute('class', function (c) { |
@@ -11,3 +11,3 @@ var trumpet = require('../'); | ||
tr.selectAll('input[type=text]', function (elem) { | ||
elem.setAttribute('value', elem.attributes.VALUE.toUpperCase()); | ||
elem.setAttribute('value', elem.getAttribute('value').toUpperCase()); | ||
}); | ||
@@ -14,0 +14,0 @@ |
@@ -10,3 +10,3 @@ var trumpet = require('../'); | ||
var tr = trumpet(); | ||
var elem = tr.select('.a + .d'); | ||
var elem = tr.select('.b + .d'); | ||
elem.getAttribute('class', function (value) { | ||
@@ -13,0 +13,0 @@ t.equal(value, 'd'); |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
65
190
65975
1178
32
+ Addedduplexer2@~0.0.2
+ Addedhtml-select@^2.3.5
+ Addedhtml-tokenize@^1.1.1
+ Addedreadable-stream@^1.0.27-1
+ Addedthrough2@^1.0.0
+ Addedcore-util-is@1.0.3(transitive)
+ Addedcssauron@1.4.0(transitive)
+ Addedduplexer2@0.0.2(transitive)
+ Addedhtml-select@2.3.24(transitive)
+ Addedhtml-tokenize@1.2.5(transitive)
+ Addedindexof@0.0.1(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedminimist@0.0.10(transitive)
+ Addedobject-keys@0.4.0(transitive)
+ Addedreadable-stream@1.0.341.1.14(transitive)
+ Addedreadable-wrap@1.0.0(transitive)
+ Addedsplit@0.3.3(transitive)
+ Addedstream-splicer@1.3.2(transitive)
+ Addedstring_decoder@0.10.31(transitive)
+ Addedthrough2@0.4.21.1.1(transitive)
+ Addedxtend@2.1.24.0.2(transitive)
- Removedbuffers@~0.1.1
- Removedduplexer@~0.1.1
- Removedent@~0.0.5
- Removedsax@~0.5.4
- Removedthrough@~2.3.4
- Removedbuffers@0.1.1(transitive)
- Removedduplexer@0.1.2(transitive)
- Removedent@0.0.7(transitive)
- Removedsax@0.5.8(transitive)
Updatedinherits@^2.0.0