Comparing version 0.0.9 to 1.0.0
21
index.js
@@ -23,2 +23,3 @@ /***@@@ BEGIN LICENSE @@@***/ | ||
var fs = require('fs'), | ||
bl = require('bl'), | ||
ParseStream = require('./lib/parseStream'); | ||
@@ -45,17 +46,13 @@ | ||
parseStream = this.createParseStream(entityHandler); | ||
parseStream.on('error', callback); | ||
parseStream.on('data', function (chunk) { | ||
chunks.push(chunk); | ||
}); | ||
parseStream.on('finish', function () { | ||
callback(null, Buffer.concat(chunks).toString('utf8')); | ||
}); | ||
// Start processing | ||
readStream.pipe(parseStream); | ||
readStream.pipe(parseStream).pipe(bl(function (err, result) { | ||
if (err) { | ||
return callback(err); | ||
} else { | ||
return callback(null, result.toString('utf8')); | ||
} | ||
})); | ||
} | ||
}; | ||
}; |
@@ -24,15 +24,25 @@ /***@@@ BEGIN LICENSE @@@***/ | ||
async = require('async'), | ||
stream = require('readable-stream'), | ||
Parser = require('./parser'); | ||
stream = require('stream'); | ||
function ParseStream(entityHandler) { | ||
function ParseStream(options) { | ||
ParseStream.super_.call(this); | ||
this._handler = entityHandler; | ||
this._queue = []; | ||
options = options || {}; | ||
var parser = this._parser = new Parser({ tags: entityHandler.tags }); | ||
parser.on('tag', this._queueTag.bind(this)); | ||
parser.on('text', this._queueText.bind(this)); | ||
var tags; | ||
this._state = State.BEGIN; | ||
tags = options.tags; | ||
if (Array.isArray(tags)) { | ||
tags = tags.join(','); | ||
} | ||
this._tags = createDict(tags, /\s*,\s*/g); | ||
this._textNode = ''; | ||
this._tagName = ''; | ||
this._attributeName = ''; | ||
this._attributeValue = ''; | ||
this._attributes = {}; | ||
this._options = options; | ||
} | ||
@@ -44,62 +54,294 @@ | ||
ParseStream.prototype.__defineGetter__('entityHandler', function () { | ||
return this._handler; | ||
return this._options; | ||
}); | ||
ParseStream.prototype.__defineSetter__('entityHandler', function (value) { | ||
this._handler = value; | ||
}); | ||
ParseStream.prototype._transform = function (chunk, encoding, cb) { | ||
if (Buffer.isBuffer(chunk)) { | ||
encoding = 'utf8'; | ||
chunk = chunk.toString(encoding); | ||
} | ||
return this.parseChunk(chunk, 0, cb); | ||
}; | ||
ParseStream.prototype._queueTag = function (chunk) { | ||
var that = this; | ||
this._queue.push(function (cb) { | ||
that._handler.onTag(chunk, function (err, result) { | ||
that.push(result); | ||
cb(err); | ||
}); | ||
}); | ||
function createDict(str, del) { | ||
return str && Object.freeze(str.split(del || '').reduce(function (s, c) { | ||
s[c] = true; | ||
return s; | ||
}, {})); | ||
} | ||
var ORD = 0; | ||
var State = { | ||
BEGIN: ORD++, | ||
TEXT: ORD++, | ||
OPEN_CHAR: ORD++, | ||
OPEN_TAG: ORD++, | ||
ATTRIB: ORD++, | ||
ATTRIB_NAME: ORD++, | ||
ATTRIB_VALUE: ORD++, | ||
QUOTED_ATTRIB_VALUE: ORD++, | ||
QUOTED_ATTRIB_VALUE_ESCAPE: ORD++, | ||
CLOSE_TAG: ORD++ | ||
}; | ||
ParseStream.prototype._queueText = function (chunk) { | ||
var CharSet = { | ||
WHITESPACE: createDict('\n\r\t '), | ||
ESCAPEABLE_CONTROL_CHARS: createDict('bfnrtv'), | ||
ALPHANUM: createDict('ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz1234567890._') | ||
}; | ||
ParseStream.prototype._is = function (charSet, character) { | ||
return charSet[character]; | ||
}; | ||
ParseStream.prototype._closeText = function (cb) { | ||
var that = this; | ||
this._queue.push(function (cb) { | ||
if (that._handler.onText) { | ||
that._handler.onText(chunk, function (err, result) { | ||
if (this._textNode && this._options.onText) { | ||
this._options.onText(this._textNode, function (err, result) { | ||
if (err) { | ||
return cb(err); | ||
} else { | ||
that.push(result); | ||
cb(err); | ||
}); | ||
} else { | ||
that.push(chunk); | ||
cb(); | ||
return cb(); | ||
} | ||
}); | ||
this._textNode = ''; | ||
} else { | ||
if (this._textNode) { | ||
this.push(this._textNode); | ||
this._textNode = ''; | ||
} | ||
}); | ||
return cb(); | ||
} | ||
}; | ||
ParseStream.prototype._transform = function (chunk, encoding, cb) { | ||
if (Buffer.isBuffer(chunk)) { | ||
encoding = 'utf8'; | ||
chunk = chunk.toString(encoding); | ||
} | ||
ParseStream.prototype._closeTag = function (cb) { | ||
var that = this; | ||
that._parser.once('end', function () { | ||
async.series(that._queue, function (err) { | ||
if (this._tagName && this._options.onTag) { | ||
this._options.onTag({ | ||
name: this._tagName, | ||
attributes: this._attributes | ||
}, function (err, result) { | ||
if (err) { | ||
cb(err); | ||
return; | ||
return cb(err); | ||
} else { | ||
that.push(result); | ||
return cb(); | ||
} | ||
that._parser.resume(); | ||
that._queue = []; | ||
cb(); | ||
}); | ||
}); | ||
that._parser.write(chunk).close(); | ||
} else { | ||
return cb(); | ||
} | ||
}; | ||
module.exports = ParseStream; | ||
ParseStream.prototype.parseChunk = function (chunk, pos, callback) { | ||
var c; | ||
var escCharMap = {"b":"\x08", "f":"\x0C", "n":"\x0A", "r":"\x0D", "t":"\x09", "v":"\x0B"}; | ||
var that = this; | ||
while (c = chunk.charAt(pos++)) { | ||
switch (this._state) { | ||
case State.BEGIN: | ||
this._textNode = ''; | ||
this._state = State.TEXT; | ||
// XXX: Intentionally fall through | ||
/* falls through */ | ||
case State.TEXT: | ||
this._tagName = ''; | ||
this._attributeName = ''; | ||
this._attributeValue = ''; | ||
this._attributes = {}; | ||
if (c === '{') { | ||
this._state = State.OPEN_CHAR; | ||
} else { | ||
this._textNode += c; | ||
} | ||
break; | ||
case State.OPEN_CHAR: | ||
if (c === '@') { | ||
this._state = State.OPEN_TAG; | ||
} else { | ||
// Revert back to text state, including the previously skipped '{' | ||
this._state = State.TEXT; | ||
this._textNode += '{' + c; | ||
} | ||
break; | ||
case State.OPEN_TAG: | ||
if (this._is(CharSet.ALPHANUM, c)) { | ||
this._tagName += c; | ||
} else if (this._is(CharSet.WHITESPACE, c)) { | ||
if (this._tagName && (!this._tags || this._tags[this._tagName])) { | ||
// Officially a tag so notify end of text node. | ||
this._state = State.ATTRIB; | ||
} else { | ||
// Revert back to text state, including the character and continuing as text node. | ||
this._textNode += '{@' + this._tagName + c; | ||
this._state = State.TEXT; | ||
} | ||
} else if (c === '/') { | ||
if (this._tagName && (!this._tags || this._tags[this._tagName])) { | ||
// It's a tag w/ no attrs so close the prev textNode and initiate close | ||
this._state = State.CLOSE_TAG; | ||
return this._closeText(cont()); | ||
} else { | ||
// Not a tag, but we got here somehow, so just add tag-like chars | ||
this._textNode += '{@' + this._tagName + c; | ||
this._state = State.TEXT; | ||
} | ||
} else if (c === '}') { | ||
// Symmetrical tag, so ignore | ||
this._textNode += '{@' + this._tagName + c; | ||
this._state = State.TEXT; | ||
} else { | ||
if (this._tagName) { | ||
return callback(new Error('Malformed tag. Tag not closed correctly.')); | ||
} else { | ||
// Hit a character that's not valid in a tag, so put together what we've found so far and | ||
// continue as text node. | ||
this._textNode += '{@' + this._tagName + c; | ||
this._state = State.TEXT; | ||
} | ||
} | ||
break; | ||
case State.ATTRIB: | ||
if (this._is(CharSet.ALPHANUM, c)) { | ||
this._attributeName = c; | ||
this._attributeValue = ''; | ||
this._state = State.ATTRIB_NAME; | ||
} else if (this._is(CharSet.WHITESPACE, c)) { | ||
// noop | ||
} else if (c === '/') { | ||
this._state = State.CLOSE_TAG; | ||
return this._closeText(cont()); | ||
} else { | ||
return callback(new Error('Malformed tag. Tag not closed correctly.')); | ||
} | ||
break; | ||
case State.ATTRIB_NAME: | ||
if (this._is(CharSet.ALPHANUM, c)) { | ||
this._attributeName += c; | ||
} else if (this._is(CharSet.WHITESPACE, c)) { | ||
if (this._attributeName) { | ||
this._attributes[this._attributeName] = this._attributeName; | ||
} | ||
this._state = State.ATTRIB; | ||
} else if (c === '=') { | ||
this._state = State.ATTRIB_VALUE; | ||
} else if (c === '/') { | ||
if (this._attributeName) { | ||
this._attributes[this._attributeName] = this._attributeName; | ||
} | ||
this._state = State.CLOSE_TAG; | ||
} else { | ||
this._textNode += c; | ||
this._state = State.TEXT; | ||
} | ||
break; | ||
case State.QUOTED_ATTRIB_VALUE: | ||
if (c === '"') { | ||
this._attributes[this._attributeName] = this._attributeValue; | ||
this._state = State.ATTRIB; | ||
} else if (c === '\\') { | ||
this._state = State.QUOTED_ATTRIB_VALUE_ESCAPE; | ||
} else { | ||
this._attributeValue += c; | ||
} | ||
break; | ||
case State.QUOTED_ATTRIB_VALUE_ESCAPE: | ||
// Only control chars must be mapped to different codes. | ||
if (this._is(CharSet.ESCAPEABLE_CONTROL_CHARS, c)) { | ||
this._attributeValue += escCharMap[c]; | ||
} else { | ||
this._attributeValue += c; | ||
} | ||
this._state = State.QUOTED_ATTRIB_VALUE; | ||
break; | ||
case State.ATTRIB_VALUE: | ||
if (c === '/') { | ||
this._attributes[this._attributeName] = this._attributeValue; | ||
this._state = State.CLOSE_TAG; | ||
} else if (c === '"') { | ||
if (!this._attributeValue) { | ||
this._state = State.QUOTED_ATTRIB_VALUE; | ||
} else { | ||
return callback(new Error('Malformed tag. Invalid quote.')); | ||
} | ||
} else if (c === "'") { | ||
return callback(new Error('Malformed tag. Attribute values must use double quotes.')); | ||
} else if (this._is(CharSet.WHITESPACE, c)) { | ||
if (this._attributeValue) { | ||
this._attributes[this._attributeName] = this._attributeValue; | ||
this._state = State.ATTRIB; | ||
} | ||
} else { | ||
this._attributeValue += c; | ||
} | ||
break; | ||
case State.CLOSE_TAG: | ||
if (c === '}') { | ||
this._state = State.TEXT; | ||
return this._closeTag(cont()); | ||
} | ||
break; | ||
} | ||
} | ||
if (this._state === State.TEXT) { | ||
return this._closeText(cont()); | ||
} else { | ||
return cont()(); | ||
} | ||
function cont() { | ||
if (pos >= chunk.length) { | ||
return callback; | ||
} else { | ||
return function maybeContinueParsing(err, data) { | ||
if (err || pos >= chunk.length) { | ||
return callback(err); | ||
} else { | ||
setImmediate(function() { | ||
return that.parseChunk(chunk, pos, callback); | ||
}); | ||
} | ||
}; | ||
} | ||
} | ||
}; | ||
module.exports = ParseStream; |
{ | ||
"name": "findatag", | ||
"version": "0.0.9", | ||
"version": "1.0.0", | ||
"description": "A specialized tokenizer for finding dust-style tags ({@tagname [attributes]/})", | ||
"main": "index.js", | ||
"scripts": { | ||
"test": "make test" | ||
"test": "grunt test" | ||
}, | ||
"repository": { | ||
"type": "git", | ||
"url": "git://github.com/paypal/findatag.git" | ||
"url": "git://github.com/krakenjs/findatag.git" | ||
}, | ||
"engines": { | ||
"node": ">=0.10.4" | ||
}, | ||
"keywords": [ | ||
@@ -25,12 +22,17 @@ "dust", | ||
"email": "ertoth@paypal.com" | ||
}, | ||
{ | ||
"name": "Aria Stewart", | ||
"email": "ariastewart@paypal.com" | ||
} | ||
], | ||
"licenses": [ | ||
{ | ||
"type": "Apache 2.0", | ||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html" | ||
} | ||
], | ||
{ | ||
"type": "Apache 2.0", | ||
"url": "http://www.apache.org/licenses/LICENSE-2.0.html" | ||
} | ||
], | ||
"dependencies": { | ||
"async": "0.2.x", | ||
"bl": "^0.9.0", | ||
"readable-stream": "1.0.15" | ||
@@ -40,5 +42,8 @@ }, | ||
"chai": "~1.5.0", | ||
"mocha": "~1.9.0" | ||
"mocha": "~1.9.0", | ||
"grunt": "~0.4.1", | ||
"grunt-contrib-jshint": "~0.7.0", | ||
"grunt-mocha-test": "~0.7.0" | ||
}, | ||
"homepage": "https://github.com/paypal/findatag" | ||
} | ||
"homepage": "https://github.com/krakenjs/findatag" | ||
} |
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
New author
Supply chain riskA new npm collaborator published a version of the package for the first time. New collaborators are usually benign additions to a project, but do indicate a change to the security surface area of a package.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
No v1
QualityPackage is not semver >=1. This means it is not stable and does not support ^ ranges.
Found 1 instance in 1 package
28156
11
0
3
5
373
2
+ Addedbl@^0.9.0
+ Addedbl@0.9.5(transitive)
+ Addedcore-util-is@1.0.3(transitive)
+ Addedinherits@2.0.4(transitive)
+ Addedisarray@0.0.1(transitive)
+ Addedreadable-stream@1.0.34(transitive)
+ Addedstring_decoder@0.10.31(transitive)