diff options
37 files changed, 504 insertions, 2240 deletions
diff --git a/.eslintrc b/.eslintrc new file mode 100644 index 0000000..24d1a9e --- /dev/null +++ b/.eslintrc @@ -0,0 +1,94 @@ +{ + "extends": "eslint:recommended", + "env": { + "node": true + }, + "globals": { + "describe": true, + "it": true + }, + "rules": { + "eqeqeq": 2, + "no-extend-native": 2, + "no-use-before-define": [ + 2, + { + "functions": false, + "classes": false + } + ], + "no-caller": 2, + "no-irregular-whitespace": 2, + "quotes": [ + 2, + "double" + ], + "no-undef": 2, + "no-unused-vars": 2, + "no-eq-null": 2, + "no-proto": 2, + "curly": [ + 2, + "multi-line" + ], + "no-mixed-spaces-and-tabs": [ + 2, + "smart-tabs" + ], + "space-infix-ops": 2, + "keyword-spacing": [ + 2, + { + "overrides": { + "if": { + "after": false + }, + "catch": { + "after": false + }, + "for": { + "after": false + }, + "while": { + "after": false + } + } + } + ], + "new-cap": 2, + "comma-style": [ + 2, + "last" + ], + "dot-notation": 2, + "wrap-iife": 2, + "no-empty": 2, + "space-unary-ops": [ + 2, + { + "words": false, + "nonwords": false + } + ], + "no-with": 2, + "no-multi-str": 2, + "no-trailing-spaces": 2, + "indent": [ + 2, + "tab", + { + "SwitchCase": 1, + "VariableDeclarator": 0 + } + ], + "linebreak-style": [ + 2, + "unix" + ], + "consistent-this": [ + 2, + "_this" + ], + "no-extra-semi": 0 // https://github.com/eslint/eslint/issues/6386 + } +} diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/.gitignore diff --git a/.travis.yml b/.travis.yml index 89d4e03..15628ee 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,16 @@ language: node_js node_js: - - 0.10 - - 0.11 + - stable + - unstable + - 5.1 + - 4.2 + - 0.12 + +sudo: false + +matrix: + fast_finish: true + allow_failures: + - node_js: unstable script: npm run coveralls @@ -1,18 +1,18 @@ -#htmlparser2 +# htmlparser2 [](https://npmjs.org/package/htmlparser2) [](https://npmjs.org/package/htmlparser2) [](http://travis-ci.org/fb55/htmlparser2) [](https://coveralls.io/r/fb55/htmlparser2) -A forgiving HTML/XML/RSS parser written in JS for NodeJS. The parser can handle streams (chunked data) and supports custom handlers for writing custom DOMs/output. +A forgiving HTML/XML/RSS parser. The parser can handle streams and provides a callback interface. -##Installing +## Installation npm install htmlparser2 - -A live demo of htmlparser2 is available at http://demos.forbeslindesay.co.uk/htmlparser2/ -##Usage +A live demo of htmlparser2 is available [here](http://demos.forbeslindesay.co.uk/htmlparser2/). + +## Usage ```javascript var htmlparser = require("htmlparser2"); @@ -30,28 +30,30 @@ var parser = new htmlparser.Parser({ console.log("That's it?!"); } } -}); +}, {decodeEntities: true}); parser.write("Xyz <script type='text/javascript'>var foo = '<<bar>>';</ script>"); parser.end(); ``` Output (simplified): -```javascript ---> Xyz +``` +--> Xyz JS! Hooray! --> var foo = '<<bar>>'; That's it?! ``` -Read more about the parser in the [wiki](https://github.com/fb55/htmlparser2/wiki/Parser-options). +## Documentation -##Get a DOM +Read more about the parser and its options in the [wiki](https://github.com/fb55/htmlparser2/wiki/Parser-options). + +## Get a DOM The `DomHandler` (known as `DefaultHandler` in the original `htmlparser` module) produces a DOM (document object model) that can be manipulated using the [`DomUtils`](https://github.com/fb55/DomUtils) helper. The `DomHandler`, while still bundled with this module, was moved to its [own module](https://github.com/fb55/domhandler). Have a look at it for further information. -##Parsing RSS/RDF/Atom Feeds +## Parsing RSS/RDF/Atom Feeds ```javascript new htmlparser.FeedHandler(function(<error> error, <object> feed){ @@ -59,7 +61,9 @@ new htmlparser.FeedHandler(function(<error> error, <object> feed){ }); ``` -##Performance +Note: While the provided feed handler works for most feeds, you might want to use [danmactough/node-feedparser](https://github.com/danmactough/node-feedparser), which is much better tested and actively maintained. + +## Performance After having some artificial benchmarks for some time, __@AndreasMadsen__ published his [`htmlparser-benchmark`](https://github.com/AndreasMadsen/htmlparser-benchmark), which benchmarks HTML parses based on real-world websites. @@ -78,9 +82,10 @@ parse5 : 22.0439 ms/file ± 15.3743 sax : 49.6513 ms/file ± 26.6032 ``` -##How is this different from [node-htmlparser](https://github.com/tautologistics/node-htmlparser)? +## How does this module differ from [node-htmlparser](https://github.com/tautologistics/node-htmlparser)? + This is a fork of the `htmlparser` module. The main difference is that this is intended to be used only with node (it runs on other platforms using [browserify](https://github.com/substack/node-browserify)). `htmlparser2` was rewritten multiple times and, while it maintains an API that's compatible with `htmlparser` in most cases, the projects don't share any code anymore. The parser now provides a callback interface close to [sax.js](https://github.com/isaacs/sax-js) (originally targeted at [readabilitySAX](https://github.com/fb55/readabilitysax)). As a result, old handlers won't work anymore. -The `DefaultHandler` and the `RssHandler` were renamed to clarify their purpose (to `DomHandler` and `FeedHandler`). The old names are still available when requiring `htmlparser2`, so your code should work as expected. +The `DefaultHandler` and the `RssHandler` were renamed to clarify their purpose (to `DomHandler` and `FeedHandler`). The old names are still available when requiring `htmlparser2`, your code should work as expected. diff --git a/debian/changelog b/debian/changelog index 06e7321..7e58c13 100644 --- a/debian/changelog +++ b/debian/changelog @@ -1,3 +1,10 @@ +node-htmlparser2 (3.9.2-1) unstable; urgency=low + + * New upstream release + * Unbundle node-dom{elementtype,handler,utils} (Closes: #794988) + + -- Paolo Greppi <paolo.greppi@libpf.com> Wed, 11 Jan 2017 17:50:19 +0100 + node-htmlparser2 (3.7.3-1) unstable; urgency=low * Initial release (Closes: #761433) diff --git a/debian/compat b/debian/compat index 45a4fb7..ec63514 100644 --- a/debian/compat +++ b/debian/compat @@ -1 +1 @@ -8 +9 diff --git a/debian/control b/debian/control index 5d2402e..49ab239 100644 --- a/debian/control +++ b/debian/control @@ -1,21 +1,38 @@ Source: node-htmlparser2 Section: web -Priority: extra +Priority: optional Maintainer: Debian Javascript Maintainers <pkg-javascript-devel@lists.alioth.debian.org> -Uploaders: W. Martin Borgert <debacle@debian.org> -Build-Depends: debhelper (>= 8) -Standards-Version: 3.9.5 -Homepage: https://github.com/fb55/htmlparser2 -Vcs-Git: git://anonscm.debian.org/pkg-javascript/node-htmlparser2.git -Vcs-Browser: http://anonscm.debian.org/gitweb/?p=pkg-javascript/node-htmlparser2.git -XS-Testsuite: autopkgtest +Uploaders: + W. Martin Borgert <debacle@debian.org>, + Paolo Greppi <paolo.greppi@libpf.com> +Build-Depends: + debhelper (>= 9) + , dh-buildinfo + , node-entities (>= 1.1.1) + , node-domhandler (>= 2.3.0) + , node-domelementtype (>= 1.3.0) + , node-inherits (>= 2.0.1) + , node-domutils (>= 1.5.1) + , mocha + , nodejs +Standards-Version: 3.9.8 +Homepage: https://github.com/fb55/htmlparser2#readme +Vcs-Git: https://anonscm.debian.org/git/pkg-javascript/node-htmlparser2.git +Vcs-Browser: https://anonscm.debian.org/cgit/pkg-javascript/node-htmlparser2.git Package: node-htmlparser2 Architecture: all -Depends: ${misc:Depends} +Depends: + ${misc:Depends} , nodejs - , node-entities -Description: fast & forgiving HTML/XML/RSS parser + , node-entities (>= 1.1.1) + , node-domhandler (>= 2.3.0) + , node-domelementtype (>= 1.3.0) + , node-inherits (>= 2.0.1) + , node-domutils (>= 1.5.1) +Description: Fast & forgiving HTML/XML/RSS parser A forgiving HTML/XML/RSS parser written in JS for NodeJS. The parser can handle streams (chunked data) and supports custom handlers for writing custom DOMs/output. + . + Node.js is an event-based server-side JavaScript engine. diff --git a/debian/copyright b/debian/copyright index b005bdf..d36c7de 100644 --- a/debian/copyright +++ b/debian/copyright @@ -1,27 +1,22 @@ -Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0 +Format: http://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ Upstream-Name: htmlparser2 +Upstream-Contact: http://github.com/fb55/htmlparser2/issues +Source: https://github.com/fb55/htmlparser2#readme Files: * -Copyright: 2010-2011 Chris Winberry <chris@winberry.net>, - 2011-2014 Felix Böhm <me@feedic.com> -License: MIT +Copyright: + 2010-2011 Chris Winberry <chris@winberry.net> + 2011-2016 Felix Böhm <me@feedic.com> +License: Expat -Files: debian/* -Copyright: 2014 W. Martin Borgert <debacle@debian.org> -License: MIT - -Files: debian/patches/* -Copyright: 2012-2014 Felix Böhm <me@feedic.com> -License: BSD-2-clause - -License: MIT - 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: +License: Expat + 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. @@ -29,10 +24,17 @@ License: MIT 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. + 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. + +Files: debian/* +Copyright: + 2014 W. Martin Borgert <debacle@debian.org> + 2016 Paolo Greppi <paolo.greppi@libpf.com> +License: BSD-2-clause License: BSD-2-clause All rights reserved. diff --git a/debian/install b/debian/install index 2cabf3e..fcd9ccb 100644 --- a/debian/install +++ b/debian/install @@ -1,5 +1,2 @@ -lib usr/lib/nodejs/htmlparser2 -package.json usr/lib/nodejs/htmlparser2 -domelementtype usr/lib/nodejs/htmlparser2 -domhandler usr/lib/nodejs/htmlparser2 -domutils usr/lib/nodejs/htmlparser2 +lib usr/lib/nodejs/htmlparser2/ +package.json usr/lib/nodejs/htmlparser2/ diff --git a/debian/patches/1000_node-domelementtype.patch b/debian/patches/1000_node-domelementtype.patch deleted file mode 100644 index fbd8822..0000000 --- a/debian/patches/1000_node-domelementtype.patch +++ /dev/null @@ -1,60 +0,0 @@ -Description: add node-domelementtype package as patch -Author: Felix Boehm <me@feedic.com> -Origin: https://github.com/fb55/domelementtype/ -Bug-Debian: https://bugs.debian.org/761437 -Last-Update: 2014-09-21 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- /dev/null -+++ b/domelementtype/LICENSE -@@ -0,0 +1,11 @@ -+Copyright (c) Felix Böhm -+All rights reserved. -+ -+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -+ -+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -+ -+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -+ -+THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, -+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- /dev/null -+++ b/domelementtype/index.js -@@ -0,0 +1,14 @@ -+//Types of elements found in the DOM -+module.exports = { -+ Text: "text", //Text -+ Directive: "directive", //<? ... ?> -+ Comment: "comment", //<!-- ... --> -+ Script: "script", //<script> tags -+ Style: "style", //<style> tags -+ Tag: "tag", //Any tag -+ CDATA: "cdata", //<![CDATA[ ... ]]> -+ -+ isTag: function(elem){ -+ return elem.type === "tag" || elem.type === "script" || elem.type === "style"; -+ } -+}; ---- /dev/null -+++ b/domelementtype/package.json -@@ -0,0 +1,15 @@ -+{ -+ "name": "domelementtype", -+ "version": "1.1.1", -+ "description": "all the types of nodes in htmlparser2's dom", -+ "main": "index.js", -+ "repository": { -+ "type": "git", -+ "url": "git://github.com/FB55/domelementtype.git" -+ }, -+ "keywords": [ -+ "dom", -+ "htmlparser2" -+ ], -+ "author": "Felix Boehm <me@feedic.com>" -+} ---- /dev/null -+++ b/domelementtype/readme.md -@@ -0,0 +1 @@ -+all the types of nodes in htmlparser2's dom diff --git a/debian/patches/2000_node-domhandler.patch b/debian/patches/2000_node-domhandler.patch deleted file mode 100644 index 7f2e583..0000000 --- a/debian/patches/2000_node-domhandler.patch +++ /dev/null @@ -1,1188 +0,0 @@ -Description: add node-domhandler package as patch -Author: Felix Boehm <me@feedic.com> -Origin: https://github.com/fb55/domhandler/ -Bug-Debian: https://bugs.debian.org/761438 -Last-Update: 2014-09-21 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- /dev/null -+++ b/domhandler/.travis.yml -@@ -0,0 +1,4 @@ -+language: node_js -+node_js: -+ - 0.8 -+ - 0.10 ---- /dev/null -+++ b/domhandler/LICENSE -@@ -0,0 +1,11 @@ -+Copyright (c) Felix Böhm -+All rights reserved. -+ -+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -+ -+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -+ -+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -+ -+THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, -+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- /dev/null -+++ b/domhandler/index.js -@@ -0,0 +1,211 @@ -+var ElementType = require("domelementtype"); -+ -+var re_whitespace = /\s+/g; -+ -+function DomHandler(callback, options, elementCB){ -+ if(typeof callback === "object"){ -+ elementCB = options; -+ options = callback; -+ callback = null; -+ } else if(typeof options === "function"){ -+ elementCB = options; -+ options = defaultOpts; -+ } -+ this._callback = callback; -+ this._options = options || defaultOpts; -+ this._elementCB = elementCB; -+ this.dom = []; -+ this._done = false; -+ this._tagStack = []; -+} -+ -+//default options -+var defaultOpts = { -+ normalizeWhitespace: false //Replace all whitespace with single spaces -+}; -+ -+//Resets the handler back to starting state -+DomHandler.prototype.onreset = function(){ -+ DomHandler.call(this, this._callback, this._options, this._elementCB); -+}; -+ -+//Signals the handler that parsing is done -+DomHandler.prototype.onend = function(){ -+ if(this._done) return; -+ this._done = true; -+ this._handleCallback(null); -+}; -+ -+DomHandler.prototype._handleCallback = -+DomHandler.prototype.onerror = function(error){ -+ if(typeof this._callback === "function"){ -+ this._callback(error, this.dom); -+ } else { -+ if(error) throw error; -+ } -+}; -+ -+DomHandler.prototype.onclosetag = function(){ -+ //if(this._tagStack.pop().name !== name) this._handleCallback(Error("Tagname didn't match!")); -+ var elem = this._tagStack.pop(); -+ if(this._elementCB) this._elementCB(elem); -+}; -+ -+DomHandler.prototype._addDomElement = function(element){ -+ var parent = this._tagStack[this._tagStack.length - 1]; -+ var siblings = parent ? parent.children : this.dom; -+ var previousSibling = siblings[siblings.length - 1]; -+ -+ element.next = null; -+ -+ if (this._options.withDomLvl1) { -+ element.__proto__ = NodePrototype; -+ } -+ -+ if(previousSibling){ -+ element.prev = previousSibling; -+ previousSibling.next = element; -+ } else { -+ element.prev = null; -+ } -+ -+ siblings.push(element); -+ element.parent = parent || null; -+}; -+ -+// This object will be used as the prototype for Nodes when creating a -+// DOM-Level-1-compliant structure. -+var NodePrototype = { -+ get firstChild() { -+ var children = this.children; -+ return children && children[0] || null; -+ }, -+ get lastChild() { -+ var children = this.children; -+ return children && children[children.length - 1] || null; -+ }, -+ get nodeType() { -+ return nodeTypes[this.type] || nodeTypes.element; -+ } -+}; -+var domLvl1 = { -+ tagName: "name", -+ childNodes: "children", -+ parentNode: "parent", -+ previousSibling: "prev", -+ nextSibling: "next", -+ nodeValue: "data" -+}; -+var nodeTypes = { -+ element: 1, -+ text: 3, -+ cdata: 4, -+ comment: 8 -+}; -+Object.keys(domLvl1).forEach(function(key) { -+ var shorthand = domLvl1[key]; -+ Object.defineProperty(NodePrototype, key, { -+ get: function() { -+ return this[shorthand] || null; -+ }, -+ set: function(val) { -+ this[shorthand] = val; -+ return val; -+ } -+ }); -+}); -+ -+DomHandler.prototype.onopentag = function(name, attribs){ -+ var element = { -+ type: name === "script" ? ElementType.Script : name === "style" ? ElementType.Style : ElementType.Tag, -+ name: name, -+ attribs: attribs, -+ children: [] -+ }; -+ -+ this._addDomElement(element); -+ -+ this._tagStack.push(element); -+}; -+ -+DomHandler.prototype.ontext = function(data){ -+ //the ignoreWhitespace is officially dropped, but for now, -+ //it's an alias for normalizeWhitespace -+ var normalize = this._options.normalizeWhitespace || this._options.ignoreWhitespace; -+ -+ var lastTag; -+ -+ if(!this._tagStack.length && this.dom.length && (lastTag = this.dom[this.dom.length-1]).type === ElementType.Text){ -+ if(normalize){ -+ lastTag.data = (lastTag.data + data).replace(re_whitespace, " "); -+ } else { -+ lastTag.data += data; -+ } -+ } else { -+ if( -+ this._tagStack.length && -+ (lastTag = this._tagStack[this._tagStack.length - 1]) && -+ (lastTag = lastTag.children[lastTag.children.length - 1]) && -+ lastTag.type === ElementType.Text -+ ){ -+ if(normalize){ -+ lastTag.data = (lastTag.data + data).replace(re_whitespace, " "); -+ } else { -+ lastTag.data += data; -+ } -+ } else { -+ if(normalize){ -+ data = data.replace(re_whitespace, " "); -+ } -+ -+ this._addDomElement({ -+ data: data, -+ type: ElementType.Text -+ }); -+ } -+ } -+}; -+ -+DomHandler.prototype.oncomment = function(data){ -+ var lastTag = this._tagStack[this._tagStack.length - 1]; -+ -+ if(lastTag && lastTag.type === ElementType.Comment){ -+ lastTag.data += data; -+ return; -+ } -+ -+ var element = { -+ data: data, -+ type: ElementType.Comment -+ }; -+ -+ this._addDomElement(element); -+ this._tagStack.push(element); -+}; -+ -+DomHandler.prototype.oncdatastart = function(){ -+ var element = { -+ children: [{ -+ data: "", -+ type: ElementType.Text -+ }], -+ type: ElementType.CDATA -+ }; -+ -+ this._addDomElement(element); -+ this._tagStack.push(element); -+}; -+ -+DomHandler.prototype.oncommentend = DomHandler.prototype.oncdataend = function(){ -+ this._tagStack.pop(); -+}; -+ -+DomHandler.prototype.onprocessinginstruction = function(name, data){ -+ this._addDomElement({ -+ name: name, -+ data: data, -+ type: ElementType.Directive -+ }); -+}; -+ -+module.exports = DomHandler; ---- /dev/null -+++ b/domhandler/package.json -@@ -0,0 +1,40 @@ -+{ -+ "name": "domhandler", -+ "version": "2.2.0", -+ "description": "handler for htmlparser2 that turns pages into a dom", -+ "main": "index.js", -+ "directories": { -+ "test": "tests" -+ }, -+ "scripts": { -+ "test": "mocha -R list && jshint index.js test/" -+ }, -+ "repository": { -+ "type": "git", -+ "url": "git://github.com/fb55/DomHandler.git" -+ }, -+ "keywords": [ -+ "dom", -+ "htmlparser2" -+ ], -+ "dependencies": { -+ "domelementtype": "1" -+ }, -+ "devDependencies": { -+ "htmlparser2": "3.2", -+ "mocha": "1", -+ "jshint": "~2.3.0" -+ }, -+ "author": "Felix Boehm <me@feedic.com>", -+ "jshintConfig": { -+ "quotmark": "double", -+ "trailing": true, -+ "unused": true, -+ "undef": true, -+ "node": true, -+ "proto": true, -+ "globals": { -+ "it": true -+ } -+ } -+} ---- /dev/null -+++ b/domhandler/readme.md -@@ -0,0 +1,102 @@ -+#DOMHandler [](http://travis-ci.org/fb55/DomHandler) -+ -+The DOM handler (formally known as DefaultHandler) creates a tree containing all nodes of a page. The tree may be manipulated using the DOMUtils library. -+ -+##Usage -+```javascript -+var handler = new DomHandler([ <func> callback(err, dom), ] [ <obj> options ]); -+// var parser = new Parser(handler[, options]); -+``` -+ -+##Example -+```javascript -+var htmlparser = require("htmlparser2"); -+var rawHtml = "Xyz <script language= javascript>var foo = '<<bar>>';< / script><!--<!-- Waah! -- -->"; -+var handler = new htmlparser.DomHandler(function (error, dom) { -+ if (error) -+ [...do something for errors...] -+ else -+ [...parsing done, do something...] -+ console.log(dom); -+}); -+var parser = new htmlparser.Parser(handler); -+parser.write(rawHtml); -+parser.done(); -+``` -+ -+Output: -+ -+```javascript -+[{ -+ data: 'Xyz ', -+ type: 'text' -+}, { -+ type: 'script', -+ name: 'script', -+ attribs: { -+ language: 'javascript' -+ }, -+ children: [{ -+ data: 'var foo = \'<bar>\';<', -+ type: 'text' -+ }] -+}, { -+ data: '<!-- Waah! -- ', -+ type: 'comment' -+}] -+``` -+ -+##Option: normalizeWhitespace -+Indicates whether the whitespace in text nodes should be normalized (= all whitespace should be replaced with single spaces). The default value is "false". -+ -+The following HTML will be used: -+ -+```html -+<font> -+ <br>this is the text -+<font> -+``` -+ -+###Example: true -+ -+```javascript -+[{ -+ type: 'tag', -+ name: 'font', -+ children: [{ -+ data: ' ', -+ type: 'text' -+ }, { -+ type: 'tag', -+ name: 'br' -+ }, { -+ data: 'this is the text ', -+ type: 'text' -+ }, { -+ type: 'tag', -+ name: 'font' -+ }] -+}] -+``` -+ -+###Example: false -+ -+```javascript -+[{ -+ type: 'tag', -+ name: 'font', -+ children: [{ -+ data: '\n\t', -+ type: 'text' -+ }, { -+ type: 'tag', -+ name: 'br' -+ }, { -+ data: 'this is the text\n', -+ type: 'text' -+ }, { -+ type: 'tag', -+ name: 'font' -+ }] -+}] -+``` ---- /dev/null -+++ b/domhandler/test/cases/01-basic.json -@@ -0,0 +1,57 @@ -+{ -+ "name": "Basic test", -+ "options": {}, -+ "html": "<!DOCTYPE html><html><title>The Title</title><body>Hello world</body></html>", -+ "expected": [ -+ { -+ "name": "!doctype", -+ "data": "!DOCTYPE html", -+ "type": "directive" -+ }, -+ { -+ "type": "tag", -+ "name": "html", -+ "attribs": {}, -+ "parent": null, -+ "children": [ -+ { -+ "type": "tag", -+ "name": "title", -+ "attribs": {}, -+ "parent": { -+ "type": "tag", -+ "name": "html", -+ "attribs": {} -+ }, -+ "children": [ -+ { -+ "data": "The Title", -+ "type": "text", -+ "parent": { -+ "type": "tag", -+ "name": "title", -+ "attribs": {} -+ } -+ } -+ ] -+ }, -+ { -+ "type": "tag", -+ "name": "body", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": "Hello world", -+ "type": "text" -+ } -+ ], -+ "prev": { -+ "type": "tag", -+ "name": "title", -+ "attribs": {} -+ } -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/02-single_tag_1.json -@@ -0,0 +1,21 @@ -+{ -+ "name": "Single Tag 1", -+ "options": {}, -+ "html": "<br>text</br>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ }, -+ { -+ "data": "text", -+ "type": "text" -+ }, -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/03-single_tag_2.json -@@ -0,0 +1,21 @@ -+{ -+ "name": "Single Tag 2", -+ "options": {}, -+ "html": "<br>text<br>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ }, -+ { -+ "data": "text", -+ "type": "text" -+ }, -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/04-unescaped_in_script.json -@@ -0,0 +1,27 @@ -+{ -+ "name": "Unescaped chars in script", -+ "options": {}, -+ "html": "<head><script language=\"Javascript\">var foo = \"<bar>\"; alert(2 > foo); var baz = 10 << 2; var zip = 10 >> 1; var yap = \"<<>>>><<\";</script></head>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "head", -+ "attribs": {}, -+ "children": [ -+ { -+ "type": "script", -+ "name": "script", -+ "attribs": { -+ "language": "Javascript" -+ }, -+ "children": [ -+ { -+ "data": "var foo = \"<bar>\"; alert(2 > foo); var baz = 10 << 2; var zip = 10 >> 1; var yap = \"<<>>>><<\";", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/05-tags_in_comment.json -@@ -0,0 +1,18 @@ -+{ -+ "name": "Special char in comment", -+ "options": {}, -+ "html": "<head><!-- commented out tags <title>Test</title>--></head>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "head", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": " commented out tags <title>Test</title>", -+ "type": "comment" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/06-comment_in_script.json -@@ -0,0 +1,18 @@ -+{ -+ "name": "Script source in comment", -+ "options": {}, -+ "html": "<script><!--var foo = 1;--></script>", -+ "expected": [ -+ { -+ "type": "script", -+ "name": "script", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": "<!--var foo = 1;-->", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/07-unescaped_in_style.json -@@ -0,0 +1,20 @@ -+{ -+ "name": "Unescaped chars in style", -+ "options": {}, -+ "html": "<style type=\"text/css\">\n body > p\n\t{ font-weight: bold; }</style>", -+ "expected": [ -+ { -+ "type": "style", -+ "name": "style", -+ "attribs": { -+ "type": "text/css" -+ }, -+ "children": [ -+ { -+ "data": "\n body > p\n\t{ font-weight: bold; }", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/08-extra_spaces_in_tag.json -@@ -0,0 +1,20 @@ -+{ -+ "name": "Extra spaces in tag", -+ "options": {}, -+ "html": "<font\t\n size='14' \n>the text</\t\nfont\t \n>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "font", -+ "attribs": { -+ "size": "14" -+ }, -+ "children": [ -+ { -+ "data": "the text", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/09-unquoted_attrib.json -@@ -0,0 +1,20 @@ -+{ -+ "name": "Unquoted attributes", -+ "options": {}, -+ "html": "<font size= 14>the text</font>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "font", -+ "attribs": { -+ "size": "14" -+ }, -+ "children": [ -+ { -+ "data": "the text", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/10-singular_attribute.json -@@ -0,0 +1,15 @@ -+{ -+ "name": "Singular attribute", -+ "options": {}, -+ "html": "<option value='foo' selected>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "option", -+ "attribs": { -+ "value": "foo", -+ "selected": "" -+ } -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/11-text_outside_tags.json -@@ -0,0 +1,40 @@ -+{ -+ "name": "Text outside tags", -+ "options": {}, -+ "html": "Line one\n<br>\nline two", -+ "expected": [ -+ { -+ "data": "Line one\n", -+ "type": "text", -+ "prev": null, -+ "next": { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ } -+ }, -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {}, -+ "prev": { -+ "data": "Line one\n", -+ "type": "text" -+ }, -+ "next": { -+ "data": "\nline two", -+ "type": "text" -+ } -+ }, -+ { -+ "data": "\nline two", -+ "type": "text", -+ "prev": { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ }, -+ "next": null -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/12-text_only.json -@@ -0,0 +1,11 @@ -+{ -+ "name": "Only text", -+ "options": {}, -+ "html": "this is the text", -+ "expected": [ -+ { -+ "data": "this is the text", -+ "type": "text" -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/13-comment_in_text.json -@@ -0,0 +1,19 @@ -+{ -+ "name": "Comment within text", -+ "options": {}, -+ "html": "this is <!-- the comment --> the text", -+ "expected": [ -+ { -+ "data": "this is ", -+ "type": "text" -+ }, -+ { -+ "data": " the comment ", -+ "type": "comment" -+ }, -+ { -+ "data": " the text", -+ "type": "text" -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/14-comment_in_text_in_script.json -@@ -0,0 +1,18 @@ -+{ -+ "name": "Comment within text within script", -+ "options": {}, -+ "html": "<script>this is <!-- the comment --> the text</script>", -+ "expected": [ -+ { -+ "type": "script", -+ "name": "script", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": "this is <!-- the comment --> the text", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/15-non-verbose.json -@@ -0,0 +1,22 @@ -+{ -+ "name": "Option 'verbose' set to 'false'", -+ "options": { -+ "verbose": false -+ }, -+ "html": "<font\t\n size='14' \n>the text</\t\nfont\t \n>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "font", -+ "attribs": { -+ "size": "14" -+ }, -+ "children": [ -+ { -+ "data": "the text", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/16-normalize_whitespace.json -@@ -0,0 +1,47 @@ -+{ -+ "name": "Normalize whitespace", -+ "options": { -+ "normalizeWhitespace": true -+ }, -+ "html": "Line one\n<br>\t \r\n\f <br>\nline two<font><br> x </font>", -+ "expected": [ -+ { -+ "data": "Line one ", -+ "type": "text" -+ }, -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ }, -+ { -+ "data": " ", -+ "type": "text" -+ }, -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ }, -+ { -+ "data": " line two", -+ "type": "text" -+ }, -+ { -+ "type": "tag", -+ "name": "font", -+ "attribs": {}, -+ "children": [ -+ { -+ "type": "tag", -+ "name": "br", -+ "attribs": {} -+ }, -+ { -+ "data": " x ", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/17-xml_namespace.json -@@ -0,0 +1,18 @@ -+{ -+ "name": "XML Namespace", -+ "options": {}, -+ "html": "<ns:tag>text</ns:tag>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "ns:tag", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": "text", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/18-enforce_empty_tags.json -@@ -0,0 +1,16 @@ -+{ -+ "name": "Enforce empty tags", -+ "options": {}, -+ "html": "<link>text</link>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "link", -+ "attribs": {} -+ }, -+ { -+ "data": "text", -+ "type": "text" -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/19-ignore_empty_tags.json -@@ -0,0 +1,20 @@ -+{ -+ "name": "Ignore empty tags (xml mode)", -+ "options": { -+ "xmlMode": true -+ }, -+ "html": "<link>text</link>", -+ "expected": [ -+ { -+ "type": "tag", -+ "name": "link", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": "text", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/20-template_script_tags.json -@@ -0,0 +1,20 @@ -+{ -+ "name": "Template script tags", -+ "options": {}, -+ "html": "<script type=\"text/template\"><h1>Heading1</h1></script>", -+ "expected": [ -+ { -+ "type": "script", -+ "name": "script", -+ "attribs": { -+ "type": "text/template" -+ }, -+ "children": [ -+ { -+ "data": "<h1>Heading1</h1>", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/21-conditional_comments.json -@@ -0,0 +1,15 @@ -+{ -+ "name": "Conditional comments", -+ "options": {}, -+ "html": "<!--[if lt IE 7]> <html class='no-js ie6 oldie' lang='en'> <![endif]--><!--[if lt IE 7]> <html class='no-js ie6 oldie' lang='en'> <![endif]-->", -+ "expected": [ -+ { -+ "data": "[if lt IE 7]> <html class='no-js ie6 oldie' lang='en'> <![endif]", -+ "type": "comment" -+ }, -+ { -+ "data": "[if lt IE 7]> <html class='no-js ie6 oldie' lang='en'> <![endif]", -+ "type": "comment" -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/22-lowercase_tags.json -@@ -0,0 +1,41 @@ -+{ -+ "name": "lowercase tags", -+ "options": {}, -+ "html": "<!DOCTYPE html><HTML><TITLE>The Title</title><BODY>Hello world</body></html>", -+ "expected": [ -+ { -+ "name": "!doctype", -+ "data": "!DOCTYPE html", -+ "type": "directive" -+ }, -+ { -+ "type": "tag", -+ "name": "html", -+ "attribs": {}, -+ "children": [ -+ { -+ "type": "tag", -+ "name": "title", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": "The Title", -+ "type": "text" -+ } -+ ] -+ }, -+ { -+ "type": "tag", -+ "name": "body", -+ "attribs": {}, -+ "children": [ -+ { -+ "data": "Hello world", -+ "type": "text" -+ } -+ ] -+ } -+ ] -+ } -+ ] -+} -\ No newline at end of file ---- /dev/null -+++ b/domhandler/test/cases/23-dom-lvl1.json -@@ -0,0 +1,121 @@ -+{ -+ "name": "DOM level 1", -+ "options": { "withDomLvl1": true }, -+ "html": "<div>some stray text<h1>Hello, world.</h1><!-- comment node -->more stray text</div>", -+ "expected": [ -+ { -+ "type": "tag", -+ "nodeType": 1, -+ "name": "div", -+ "tagName": "div", -+ "attribs": {}, -+ "nodeValue": null, -+ "children": [ -+ { -+ "type": "text", -+ "nodeType": 3, -+ "data": "some stray text", -+ "nodeValue": "some stray text", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null -+ }, -+ { -+ "type": "tag", -+ "nodeType": 1, -+ "name": "h1", -+ "tagName": "h1", -+ "nodeValue": null, -+ "attribs": {}, -+ "children": [ -+ { -+ "type": "text", -+ "nodeType": 3, -+ "data": "Hello, world.", -+ "nodeValue": "Hello, world.", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null -+ } -+ ], -+ "firstChild": { -+ "type": "text", -+ "nodeType": 3, -+ "data": "Hello, world.", -+ "nodeValue": "Hello, world.", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null -+ }, -+ "lastChild": { -+ "type": "text", -+ "nodeType": 3, -+ "data": "Hello, world.", -+ "nodeValue": "Hello, world.", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null -+ } -+ }, -+ { -+ "type": "comment", -+ "nodeType": 8, -+ "data": " comment node ", -+ "nodeValue": " comment node ", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null, -+ "prev": { -+ "type": "tag", -+ "name": "h1", -+ "nodeValue": null, -+ "attribs": {} -+ }, -+ "previousSibling": { -+ "type": "tag", -+ "name": "h1", -+ "nodeValue": null, -+ "attribs": {} -+ }, -+ "next": { -+ "type": "text", -+ "data": "more stray text" -+ }, -+ "nextSibling": { -+ "type": "text", -+ "data": "more stray text" -+ } -+ }, -+ { -+ "type": "text", -+ "nodeType": 3, -+ "data": "more stray text", -+ "nodeValue": "more stray text", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null, -+ "next": null, -+ "nextSibling": null -+ } -+ ], -+ "firstChild": { -+ "type": "text", -+ "nodeType": 3, -+ "data": "some stray text", -+ "nodeValue": "some stray text", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null -+ }, -+ "lastChild": { -+ "type": "text", -+ "nodeType": 3, -+ "data": "more stray text", -+ "nodeValue": "more stray text", -+ "childNodes": null, -+ "firstChild": null, -+ "lastChild": null -+ } -+ } -+ ] -+} ---- /dev/null -+++ b/domhandler/test/tests.js -@@ -0,0 +1,58 @@ -+var fs = require("fs"), -+ path = require("path"), -+ assert = require("assert"), -+ util = require("util"), -+ Parser = require("htmlparser2").Parser, -+ Handler = require("../"); -+ -+var basePath = path.resolve(__dirname, "cases"), -+ inspectOpts = { showHidden: true, depth: null }; -+ -+fs -+.readdirSync(basePath) -+.filter(RegExp.prototype.test, /\.json$/) //only allow .json files -+.map(function(name){ -+ return path.resolve(basePath, name); -+}) -+.map(require) -+.forEach(function(test){ -+ it(test.name, function(){ -+ var expected = test.expected; -+ -+ var handler = new Handler(function(err, actual){ -+ assert.ifError(err); -+ try { -+ compare(expected, actual); -+ } catch(e){ -+ e.expected = util.inspect(expected, inspectOpts); -+ e.actual = util.inspect(actual, inspectOpts); -+ throw e; -+ } -+ }, test.options); -+ -+ var data = test.html; -+ -+ var parser = new Parser(handler, test.options); -+ -+ //first, try to run the test via chunks -+ for(var i = 0; i < data.length; i++){ -+ parser.write(data.charAt(i)); -+ } -+ parser.done(); -+ -+ //then parse everything -+ parser.parseComplete(data); -+ }); -+}); -+ -+function compare(expected, result){ -+ assert.equal(typeof expected, typeof result, "types didn't match"); -+ if(typeof expected !== "object" || expected === null){ -+ assert.strictEqual(expected, result, "result doesn't equal expected"); -+ } else { -+ for(var prop in expected){ -+ assert.ok(prop in result, "result didn't contain property " + prop); -+ compare(expected[prop], result[prop]); -+ } -+ } -+} -\ No newline at end of file diff --git a/debian/patches/3000_node-domutils.patch b/debian/patches/3000_node-domutils.patch deleted file mode 100644 index 6716779..0000000 --- a/debian/patches/3000_node-domutils.patch +++ /dev/null @@ -1,789 +0,0 @@ -Description: add node-domutils package as patch -Author: Felix Boehm <me@feedic.com> -Origin: https://github.com/fb55/domutils/ -Bug-Debian: https://bugs.debian.org/761439 -Last-Update: 2014-09-21 ---- -This patch header follows DEP-3: http://dep.debian.net/deps/dep3/ ---- /dev/null -+++ b/domutils/LICENSE -@@ -0,0 +1,11 @@ -+Copyright (c) Felix Böhm -+All rights reserved. -+ -+Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: -+ -+Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. -+ -+Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. -+ -+THIS IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS, -+EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ---- /dev/null -+++ b/domutils/index.js -@@ -0,0 +1,14 @@ -+var DomUtils = module.exports; -+ -+[ -+ require("./lib/stringify"), -+ require("./lib/traversal"), -+ require("./lib/manipulation"), -+ require("./lib/querying"), -+ require("./lib/legacy"), -+ require("./lib/helpers") -+].forEach(function(ext){ -+ Object.keys(ext).forEach(function(key){ -+ DomUtils[key] = ext[key].bind(DomUtils); -+ }); -+}); ---- /dev/null -+++ b/domutils/lib/helpers.js -@@ -0,0 +1,141 @@ -+// removeSubsets -+// Given an array of nodes, remove any member that is contained by another. -+exports.removeSubsets = function(nodes) { -+ var idx = nodes.length, node, ancestor, replace; -+ -+ // Check if each node (or one of its ancestors) is already contained in the -+ // array. -+ while (--idx > -1) { -+ node = ancestor = nodes[idx]; -+ -+ // Temporarily remove the node under consideration -+ nodes[idx] = null; -+ replace = true; -+ -+ while (ancestor) { -+ if (nodes.indexOf(ancestor) > -1) { -+ replace = false; -+ nodes.splice(idx, 1); -+ break; -+ } -+ ancestor = ancestor.parent; -+ } -+ -+ // If the node has been found to be unique, re-insert it. -+ if (replace) { -+ nodes[idx] = node; -+ } -+ } -+ -+ return nodes; -+}; -+ -+// Source: http://dom.spec.whatwg.org/#dom-node-comparedocumentposition -+var POSITION = { -+ DISCONNECTED: 1, -+ PRECEDING: 2, -+ FOLLOWING: 4, -+ CONTAINS: 8, -+ CONTAINED_BY: 16 -+}; -+ -+// Compare the position of one node against another node in any other document. -+// The return value is a bitmask with the following values: -+// -+// document order: -+// > There is an ordering, document order, defined on all the nodes in the -+// > document corresponding to the order in which the first character of the -+// > XML representation of each node occurs in the XML representation of the -+// > document after expansion of general entities. Thus, the document element -+// > node will be the first node. Element nodes occur before their children. -+// > Thus, document order orders element nodes in order of the occurrence of -+// > their start-tag in the XML (after expansion of entities). The attribute -+// > nodes of an element occur after the element and before its children. The -+// > relative order of attribute nodes is implementation-dependent./ -+// Source: -+// http://www.w3.org/TR/DOM-Level-3-Core/glossary.html#dt-document-order -+// -+// @argument {Node} nodaA The first node to use in the comparison -+// @argument {Node} nodeB The second node to use in the comparison -+// -+// @return {Number} A bitmask describing the input nodes' relative position. -+// See http://dom.spec.whatwg.org/#dom-node-comparedocumentposition for -+// a description of these values. -+var comparePos = exports.compareDocumentPosition = function(nodeA, nodeB) { -+ var aParents = []; -+ var bParents = []; -+ var current, sharedParent, siblings, aSibling, bSibling, idx; -+ -+ if (nodeA === nodeB) { -+ return 0; -+ } -+ -+ current = nodeA; -+ while (current) { -+ aParents.unshift(current); -+ current = current.parent; -+ } -+ current = nodeB; -+ while (current) { -+ bParents.unshift(current); -+ current = current.parent; -+ } -+ -+ idx = 0; -+ while (aParents[idx] === bParents[idx]) { -+ idx++; -+ } -+ -+ if (idx === 0) { -+ return POSITION.DISCONNECTED; -+ } -+ -+ sharedParent = aParents[idx - 1]; -+ siblings = sharedParent.children; -+ aSibling = aParents[idx]; -+ bSibling = bParents[idx]; -+ -+ if (siblings.indexOf(aSibling) > siblings.indexOf(bSibling)) { -+ if (sharedParent === nodeB) { -+ return POSITION.FOLLOWING | POSITION.CONTAINED_BY; -+ } -+ return POSITION.FOLLOWING; -+ } else { -+ if (sharedParent === nodeA) { -+ return POSITION.PRECEDING | POSITION.CONTAINS; -+ } -+ return POSITION.PRECEDING; -+ } -+}; -+ -+// Sort an array of nodes based on their relative position in the document and -+// remove any duplicate nodes. If the array contains nodes that do not belong -+// to the same document, sort order is unspecified. -+// -+// @argument {Array} nodes Array of DOM nodes -+// -+// @returns {Array} collection of unique nodes, sorted in document order -+exports.uniqueSort = function(nodes) { -+ var idx = nodes.length, node, position; -+ -+ nodes = nodes.slice(); -+ -+ while (--idx > -1) { -+ node = nodes[idx]; -+ position = nodes.indexOf(node); -+ if (position > -1 && position < idx) { -+ nodes.splice(idx, 1); -+ } -+ } -+ nodes.sort(function(a, b) { -+ var relative = comparePos(a, b); -+ if (relative & POSITION.PRECEDING) { -+ return -1; -+ } else if (relative & POSITION.FOLLOWING) { -+ return 1; -+ } -+ return 0; -+ }); -+ -+ return nodes; -+}; ---- /dev/null -+++ b/domutils/lib/legacy.js -@@ -0,0 +1,87 @@ -+var ElementType = require("domelementtype"); -+var isTag = exports.isTag = ElementType.isTag; -+ -+exports.testElement = function(options, element){ -+ for(var key in options){ -+ if(!options.hasOwnProperty(key)); -+ else if(key === "tag_name"){ -+ if(!isTag(element) || !options.tag_name(element.name)){ -+ return false; -+ } -+ } else if(key === "tag_type"){ -+ if(!options.tag_type(element.type)) return false; -+ } else if(key === "tag_contains"){ -+ if(isTag(element) || !options.tag_contains(element.data)){ -+ return false; -+ } -+ } else if(!element.attribs || !options[key](element.attribs[key])){ -+ return false; -+ } -+ } -+ return true; -+}; -+ -+var Checks = { -+ tag_name: function(name){ -+ if(typeof name === "function"){ -+ return function(elem){ return isTag(elem) && name(elem.name); }; -+ } else if(name === "*"){ -+ return isTag; -+ } else { -+ return function(elem){ return isTag(elem) && elem.name === name; }; -+ } -+ }, -+ tag_type: function(type){ -+ if(typeof type === "function"){ -+ return function(elem){ return type(elem.type); }; -+ } else { -+ return function(elem){ return elem.type === type; }; -+ } -+ }, -+ tag_contains: function(data){ -+ if(typeof data === "function"){ -+ return function(elem){ return !isTag(elem) && data(elem.data); }; -+ } else { -+ return function(elem){ return !isTag(elem) && elem.data === data; }; -+ } -+ } -+}; -+ -+function getAttribCheck(attrib, value){ -+ if(typeof value === "function"){ -+ return function(elem){ return elem.attribs && value(elem.attribs[attrib]); }; -+ } else { -+ return function(elem){ return elem.attribs && elem.attribs[attrib] === value; }; -+ } -+} -+ -+function combineFuncs(a, b){ -+ return function(elem){ -+ return a(elem) || b(elem); -+ }; -+} -+ -+exports.getElements = function(options, element, recurse, limit){ -+ var funcs = Object.keys(options).map(function(key){ -+ var value = options[key]; -+ return key in Checks ? Checks[key](value) : getAttribCheck(key, value); -+ }); -+ -+ return funcs.length === 0 ? [] : this.filter( -+ funcs.reduce(combineFuncs), -+ element, recurse, limit -+ ); -+}; -+ -+exports.getElementById = function(id, element, recurse){ -+ if(!Array.isArray(element)) element = [element]; -+ return this.findOne(getAttribCheck("id", id), element, recurse !== false); -+}; -+ -+exports.getElementsByTagName = function(name, element, recurse, limit){ -+ return this.filter(Checks.tag_name(name), element, recurse, limit); -+}; -+ -+exports.getElementsByTagType = function(type, element, recurse, limit){ -+ return this.filter(Checks.tag_type(type), element, recurse, limit); -+}; ---- /dev/null -+++ b/domutils/lib/manipulation.js -@@ -0,0 +1,77 @@ -+exports.removeElement = function(elem){ -+ if(elem.prev) elem.prev.next = elem.next; -+ if(elem.next) elem.next.prev = elem.prev; -+ -+ if(elem.parent){ -+ var childs = elem.parent.children; -+ childs.splice(childs.lastIndexOf(elem), 1); -+ } -+}; -+ -+exports.replaceElement = function(elem, replacement){ -+ var prev = replacement.prev = elem.prev; -+ if(prev){ -+ prev.next = replacement; -+ } -+ -+ var next = replacement.next = elem.next; -+ if(next){ -+ next.prev = replacement; -+ } -+ -+ var parent = replacement.parent = elem.parent; -+ if(parent){ -+ var childs = parent.children; -+ childs[childs.lastIndexOf(elem)] = replacement; -+ } -+}; -+ -+exports.appendChild = function(elem, child){ -+ child.parent = elem; -+ -+ if(elem.children.push(child) !== 1){ -+ var sibling = elem.children[elem.children.length - 2]; -+ sibling.next = child; -+ child.prev = sibling; -+ child.next = null; -+ } -+}; -+ -+exports.append = function(elem, next){ -+ var parent = elem.parent, -+ currNext = elem.next; -+ -+ next.next = currNext; -+ next.prev = elem; -+ elem.next = next; -+ next.parent = parent; -+ -+ if(currNext){ -+ currNext.prev = next; -+ if(parent){ -+ var childs = parent.children; -+ childs.splice(childs.lastIndexOf(currNext), 0, next); -+ } -+ } else if(parent){ -+ parent.children.push(next); -+ } -+}; -+ -+exports.prepend = function(elem, prev){ -+ var parent = elem.parent; -+ if(parent){ -+ var childs = parent.children; -+ childs.splice(childs.lastIndexOf(elem), 0, prev); -+ } -+ -+ if(elem.prev){ -+ elem.prev.next = prev; -+ } -+ -+ prev.parent = parent; -+ prev.prev = elem.prev; -+ prev.next = elem; -+ elem.prev = prev; -+}; -+ -+ ---- /dev/null -+++ b/domutils/lib/querying.js -@@ -0,0 +1,94 @@ -+var isTag = require("domelementtype").isTag; -+ -+module.exports = { -+ filter: filter, -+ find: find, -+ findOneChild: findOneChild, -+ findOne: findOne, -+ existsOne: existsOne, -+ findAll: findAll -+}; -+ -+function filter(test, element, recurse, limit){ -+ if(!Array.isArray(element)) element = [element]; -+ -+ if(typeof limit !== "number" || !isFinite(limit)){ -+ limit = Infinity; -+ } -+ return find(test, element, recurse !== false, limit); -+} -+ -+function find(test, elems, recurse, limit){ -+ var result = [], childs; -+ -+ for(var i = 0, j = elems.length; i < j; i++){ -+ if(test(elems[i])){ -+ result.push(elems[i]); -+ if(--limit <= 0) break; -+ } -+ -+ childs = elems[i].children; -+ if(recurse && childs && childs.length > 0){ -+ childs = find(test, childs, recurse, limit); -+ result = result.concat(childs); -+ limit -= childs.length; -+ if(limit <= 0) break; -+ } -+ } -+ -+ return result; -+} -+ -+function findOneChild(test, elems){ -+ for(var i = 0, l = elems.length; i < l; i++){ -+ if(test(elems[i])) return elems[i]; -+ } -+ -+ return null; -+} -+ -+function findOne(test, elems){ -+ var elem = null; -+ -+ for(var i = 0, l = elems.length; i < l && !elem; i++){ -+ if(!isTag(elems[i])){ -+ continue; -+ } else if(test(elems[i])){ -+ elem = elems[i]; -+ } else if(elems[i].children.length > 0){ -+ elem = findOne(test, elems[i].children); -+ } -+ } -+ -+ return elem; -+} -+ -+function existsOne(test, elems){ -+ for(var i = 0, l = elems.length; i < l; i++){ -+ if( -+ isTag(elems[i]) && ( -+ test(elems[i]) || ( -+ elems[i].children.length > 0 && -+ existsOne(test, elems[i].children) -+ ) -+ ) -+ ){ -+ return true; -+ } -+ } -+ -+ return false; -+} -+ -+function findAll(test, elems){ -+ var result = []; -+ for(var i = 0, j = elems.length; i < j; i++){ -+ if(!isTag(elems[i])) continue; -+ if(test(elems[i])) result.push(elems[i]); -+ -+ if(elems[i].children.length > 0){ -+ result = result.concat(findAll(test, elems[i].children)); -+ } -+ } -+ return result; -+} ---- /dev/null -+++ b/domutils/lib/stringify.js -@@ -0,0 +1,22 @@ -+var ElementType = require("domelementtype"), -+ getOuterHTML = require("dom-serializer"), -+ isTag = ElementType.isTag; -+ -+module.exports = { -+ getInnerHTML: getInnerHTML, -+ getOuterHTML: getOuterHTML, -+ getText: getText -+}; -+ -+function getInnerHTML(elem, opts){ -+ return elem.children ? elem.children.map(function(elem){ -+ return getOuterHTML(elem, opts); -+ }).join("") : ""; -+} -+ -+function getText(elem){ -+ if(Array.isArray(elem)) return elem.map(getText).join(""); -+ if(isTag(elem) || elem.type === ElementType.CDATA) return getText(elem.children); -+ if(elem.type === ElementType.Text) return elem.data; -+ return ""; -+} ---- /dev/null -+++ b/domutils/lib/traversal.js -@@ -0,0 +1,24 @@ -+var getChildren = exports.getChildren = function(elem){ -+ return elem.children; -+}; -+ -+var getParent = exports.getParent = function(elem){ -+ return elem.parent; -+}; -+ -+exports.getSiblings = function(elem){ -+ var parent = getParent(elem); -+ return parent ? getChildren(parent) : [elem]; -+}; -+ -+exports.getAttributeValue = function(elem, name){ -+ return elem.attribs && elem.attribs[name]; -+}; -+ -+exports.hasAttrib = function(elem, name){ -+ return hasOwnProperty.call(elem.attribs, name); -+}; -+ -+exports.getName = function(elem){ -+ return elem.name; -+}; ---- /dev/null -+++ b/domutils/package.json -@@ -0,0 +1,46 @@ -+{ -+ "name": "domutils", -+ "version": "1.5.0", -+ "description": "utilities for working with htmlparser2's dom", -+ "main": "index.js", -+ "directories": { -+ "test": "tests" -+ }, -+ "scripts": { -+ "test": "mocha test/tests/**.js && jshint index.js test/**/*.js lib/*.js" -+ }, -+ "repository": { -+ "type": "git", -+ "url": "git://github.com/FB55/domutils.git" -+ }, -+ "keywords": [ -+ "dom", -+ "htmlparser2" -+ ], -+ "dependencies": { -+ "dom-serializer": "0", -+ "domelementtype": "1" -+ }, -+ "devDependencies": { -+ "htmlparser2": "~3.3.0", -+ "domhandler": "2", -+ "jshint": "~2.3.0", -+ "mocha": "~1.15.1" -+ }, -+ "author": "Felix Boehm <me@feedic.com>", -+ "jshintConfig": { -+ "proto": true, -+ "unused": true, -+ "eqnull": true, -+ "undef": true, -+ "quotmark": "double", -+ "eqeqeq": true, -+ "trailing": true, -+ "node": true, -+ "globals": { -+ "describe": true, -+ "it": true, -+ "beforeEach": true -+ } -+ } -+} ---- /dev/null -+++ b/domutils/readme.md -@@ -0,0 +1 @@ -+utilities for working with htmlparser2's dom ---- /dev/null -+++ b/domutils/test/fixture.js -@@ -0,0 +1,6 @@ -+var makeDom = require("./utils").makeDom; -+var markup = Array(21).join( -+ "<?xml><tag1 id='asdf'> <script>text</script> <!-- comment --> <tag2> text </tag1>" -+); -+ -+module.exports = makeDom(markup); ---- /dev/null -+++ b/domutils/test/tests/helpers.js -@@ -0,0 +1,89 @@ -+var makeDom = require("../utils").makeDom; -+var helpers = require("../.."); -+var assert = require("assert"); -+ -+describe("helpers", function() { -+ describe("removeSubsets", function() { -+ var removeSubsets = helpers.removeSubsets; -+ var dom = makeDom("<div><p><span></span></p><p></p></div>")[0]; -+ -+ it("removes identical trees", function() { -+ var matches = removeSubsets([dom, dom]); -+ assert.equal(matches.length, 1); -+ }); -+ -+ it("Removes subsets found first", function() { -+ var matches = removeSubsets([dom, dom.children[0].children[0]]); -+ assert.equal(matches.length, 1); -+ }); -+ -+ it("Removes subsets found last", function() { -+ var matches = removeSubsets([dom.children[0], dom]); -+ assert.equal(matches.length, 1); -+ }); -+ -+ it("Does not remove unique trees", function() { -+ var matches = removeSubsets([dom.children[0], dom.children[1]]); -+ assert.equal(matches.length, 2); -+ }); -+ }); -+ -+ describe("compareDocumentPosition", function() { -+ var compareDocumentPosition = helpers.compareDocumentPosition; -+ var markup = "<div><p><span></span></p><a></a></div>"; -+ var dom = makeDom(markup)[0]; -+ var p = dom.children[0]; -+ var span = p.children[0]; -+ var a = dom.children[1]; -+ -+ it("reports when the first node occurs before the second indirectly", function() { -+ assert.equal(compareDocumentPosition(span, a), 2); -+ }); -+ -+ it("reports when the first node contains the second", function() { -+ assert.equal(compareDocumentPosition(p, span), 10); -+ }); -+ -+ it("reports when the first node occurs after the second indirectly", function() { -+ assert.equal(compareDocumentPosition(a, span), 4); -+ }); -+ -+ it("reports when the first node is contained by the second", function() { -+ assert.equal(compareDocumentPosition(span, p), 20); -+ }); -+ -+ it("reports when the nodes belong to separate documents", function() { -+ var other = makeDom(markup)[0].children[0].children[0]; -+ -+ assert.equal(compareDocumentPosition(span, other), 1); -+ }); -+ -+ it("reports when the nodes are identical", function() { -+ assert.equal(compareDocumentPosition(span, span), 0); -+ }); -+ }); -+ -+ describe("uniqueSort", function() { -+ var uniqueSort = helpers.uniqueSort; -+ var dom, p, span, a; -+ -+ beforeEach(function() { -+ dom = makeDom("<div><p><span></span></p><a></a></div>")[0]; -+ p = dom.children[0]; -+ span = p.children[0]; -+ a = dom.children[1]; -+ }); -+ -+ it("leaves unique elements untouched", function() { -+ assert.deepEqual(uniqueSort([p, a]), [p, a]); -+ }); -+ -+ it("removes duplicate elements", function() { -+ assert.deepEqual(uniqueSort([p, a, p]), [p, a]); -+ }); -+ -+ it("sorts nodes in document order", function() { -+ assert.deepEqual(uniqueSort([a, dom, span, p]), [dom, p, span, a]); -+ }); -+ }); -+}); ---- /dev/null -+++ b/domutils/test/tests/legacy.js -@@ -0,0 +1,119 @@ -+var DomUtils = require("../.."); -+var fixture = require("../fixture"); -+var assert = require("assert"); -+ -+// Set up expected structures -+var expected = { -+ idAsdf: fixture[1], -+ tag2: [], -+ typeScript: [] -+}; -+for (var idx = 0; idx < 20; ++idx) { -+ expected.tag2.push(fixture[idx*2 + 1].children[5]); -+ expected.typeScript.push(fixture[idx*2 + 1].children[1]); -+} -+ -+describe("legacy", function() { -+ describe("getElements", function() { -+ var getElements = DomUtils.getElements; -+ it("returns the node with the specified ID", function() { -+ assert.deepEqual( -+ getElements({ id: "asdf" }, fixture, true, 1), -+ [expected.idAsdf] -+ ); -+ }); -+ it("returns empty array for unknown IDs", function() { -+ assert.deepEqual(getElements({ id: "asdfs" }, fixture, true), []); -+ }); -+ it("returns the nodes with the specified tag name", function() { -+ assert.deepEqual( -+ getElements({ tag_name:"tag2" }, fixture, true), -+ expected.tag2 -+ ); -+ }); -+ it("returns empty array for unknown tag names", function() { -+ assert.deepEqual( -+ getElements({ tag_name : "asdfs" }, fixture, true), -+ [] -+ ); -+ }); -+ it("returns the nodes with the specified tag type", function() { -+ assert.deepEqual( -+ getElements({ tag_type: "script" }, fixture, true), -+ expected.typeScript -+ ); -+ }); -+ it("returns empty array for unknown tag types", function() { -+ assert.deepEqual( -+ getElements({ tag_type: "video" }, fixture, true), -+ [] -+ ); -+ }); -+ }); -+ -+ describe("getElementById", function() { -+ var getElementById = DomUtils.getElementById; -+ it("returns the specified node", function() { -+ assert.equal( -+ expected.idAsdf, -+ getElementById("asdf", fixture, true) -+ ); -+ }); -+ it("returns `null` for unknown IDs", function() { -+ assert.equal(null, getElementById("asdfs", fixture, true)); -+ }); -+ }); -+ -+ describe("getElementsByTagName", function() { -+ var getElementsByTagName = DomUtils.getElementsByTagName; -+ it("returns the specified nodes", function() { -+ assert.deepEqual( -+ getElementsByTagName("tag2", fixture, true), -+ expected.tag2 -+ ); -+ }); -+ it("returns empty array for unknown tag names", function() { -+ assert.deepEqual( -+ getElementsByTagName("tag23", fixture, true), -+ [] -+ ); -+ }); -+ }); -+ -+ describe("getElementsByTagType", function() { -+ var getElementsByTagType = DomUtils.getElementsByTagType; -+ it("returns the specified nodes", function() { -+ assert.deepEqual( -+ getElementsByTagType("script", fixture, true), -+ expected.typeScript -+ ); -+ }); -+ it("returns empty array for unknown tag types", function() { -+ assert.deepEqual( -+ getElementsByTagType("video", fixture, true), -+ [] -+ ); -+ }); -+ }); -+ -+ describe("getOuterHTML", function() { -+ var getOuterHTML = DomUtils.getOuterHTML; -+ it("Correctly renders the outer HTML", function() { -+ assert.equal( -+ getOuterHTML(fixture[1]), -+ "<tag1 id=\"asdf\"> <script>text</script> <!-- comment --> <tag2> text </tag2></tag1>" -+ ); -+ }); -+ }); -+ -+ describe("getInnerHTML", function() { -+ var getInnerHTML = DomUtils.getInnerHTML; -+ it("Correctly renders the inner HTML", function() { -+ assert.equal( -+ getInnerHTML(fixture[1]), -+ " <script>text</script> <!-- comment --> <tag2> text </tag2>" -+ ); -+ }); -+ }); -+ -+}); ---- /dev/null -+++ b/domutils/test/utils.js -@@ -0,0 +1,9 @@ -+var htmlparser = require("htmlparser2"); -+ -+exports.makeDom = function(markup) { -+ var handler = new htmlparser.DomHandler(), -+ parser = new htmlparser.Parser(handler); -+ parser.write(markup); -+ parser.done(); -+ return handler.dom; -+}; diff --git a/debian/patches/series b/debian/patches/series deleted file mode 100644 index 6b15172..0000000 --- a/debian/patches/series +++ /dev/null @@ -1,3 +0,0 @@ -1000_node-domelementtype.patch -2000_node-domhandler.patch -3000_node-domutils.patch diff --git a/debian/rules b/debian/rules index 682c117..2f607a5 100755 --- a/debian/rules +++ b/debian/rules @@ -1,11 +1,13 @@ #!/usr/bin/make -f # -*- makefile -*- +# Uncomment this to turn on verbose mode. #export DH_VERBOSE=1 %: dh $@ -override_dh_install: - dh_install - rm -f $(CURDIR)/debian/node-htmlparser2/usr/lib/nodejs/htmlparser2/dom*/LICENSE +#override_dh_auto_build: + +override_dh_auto_test: + mocha -R spec diff --git a/debian/tests/control b/debian/tests/control index 3a84a63..f8c9e86 100644 --- a/debian/tests/control +++ b/debian/tests/control @@ -1,2 +1,5 @@ -Test-Command: nodejs -e "require('"'"'htmlparser2'"'"');" -Depends: @ +Tests: require +Depends: node-htmlparser2 + +Test-Command: mocha -R spec +Depends: @, @builddeps@ diff --git a/debian/tests/require b/debian/tests/require new file mode 100644 index 0000000..e199d2e --- /dev/null +++ b/debian/tests/require @@ -0,0 +1,3 @@ +#!/bin/sh +set -e +nodejs -e "require('htmlparser2');" diff --git a/debian/watch b/debian/watch new file mode 100644 index 0000000..7a7ac60 --- /dev/null +++ b/debian/watch @@ -0,0 +1,5 @@ +version=3 +opts=\ +dversionmangle=s/\+(debian|dfsg|ds|deb)(\.\d+)?$//,\ +filenamemangle=s/.*\/v?([\d\.-]+)\.tar\.gz/node-htmlparser2-$1.tar.gz/ \ + https://github.com/fb55/htmlparser2/tags .*/archive/v?([\d\.]+).tar.gz diff --git a/lib/FeedHandler.js b/lib/FeedHandler.js index 7d4f235..329c94a 100644 --- a/lib/FeedHandler.js +++ b/lib/FeedHandler.js @@ -1,13 +1,13 @@ var index = require("./index.js"), DomHandler = index.DomHandler, - DomUtils = index.DomUtils; + DomUtils = index.DomUtils; //TODO: make this a streamable handler function FeedHandler(callback, options){ this.init(callback, options); } -require("util").inherits(FeedHandler, DomHandler); +require("inherits")(FeedHandler, DomHandler); FeedHandler.prototype.init = DomHandler; @@ -28,14 +28,14 @@ function addConditionally(obj, prop, what, where, recurse){ if(tmp) obj[prop] = tmp; } -var isValidFeed = function(value) { +var isValidFeed = function(value){ return value === "rss" || value === "feed" || value === "rdf:RDF"; }; -FeedHandler.prototype.onend = function() { +FeedHandler.prototype.onend = function(){ var feed = {}, - feedRoot = getOneElement(isValidFeed, this.dom), - tmp, childs; + feedRoot = getOneElement(isValidFeed, this.dom), + tmp, childs; if(feedRoot){ if(feedRoot.name === "feed"){ @@ -57,7 +57,7 @@ FeedHandler.prototype.onend = function() { addConditionally(entry, "id", "id", item); addConditionally(entry, "title", "title", item); if((tmp = getOneElement("link", item)) && (tmp = tmp.attribs) && (tmp = tmp.href)) entry.link = tmp; - addConditionally(entry, "description", "summary", item); + if((tmp = fetch("summary", item) || fetch("content", item))) entry.description = tmp; if((tmp = fetch("updated", item))) entry.pubDate = new Date(tmp); return entry; }); diff --git a/lib/Parser.js b/lib/Parser.js index 1002813..12db240 100644 --- a/lib/Parser.js +++ b/lib/Parser.js @@ -3,7 +3,7 @@ var Tokenizer = require("./Tokenizer.js"); /* Options: - xmlMode: Special behavior for script/style tags (true by default) + xmlMode: Disables the special behavior for script/style tags (false by default) lowerCaseAttributeNames: call .toLowerCase for each attribute name (true if xmlMode is `false`) lowerCaseTags: call .toLowerCase for each tag name (true if xmlMode is `false`) */ @@ -36,10 +36,16 @@ var formTags = { var openImpliesClose = { tr : { tr:true, th:true, td:true }, th : { th:true }, - td : { thead:true, td:true }, + td : { thead:true, th:true, td:true }, body : { head:true, link:true, script:true }, li : { li:true }, p : { p:true }, + h1 : { p:true }, + h2 : { p:true }, + h3 : { p:true }, + h4 : { p:true }, + h5 : { p:true }, + h6 : { p:true }, select : formTags, input : formTags, output : formTags, @@ -78,7 +84,10 @@ var voidElements = { ellipse: true, line: true, rect: true, - use: true + use: true, + stop: true, + polyline: true, + polygon: true }; var re_nameEnd = /\s|\//; @@ -103,10 +112,15 @@ function Parser(cbs, options){ !!this._options.lowerCaseAttributeNames : !this._options.xmlMode; + if(this._options.Tokenizer) { + Tokenizer = this._options.Tokenizer; + } this._tokenizer = new Tokenizer(this._options, this); + + if(this._cbs.onparserinit) this._cbs.onparserinit(this); } -require("util").inherits(Parser, require("events").EventEmitter); +require("inherits")(Parser, require("events").EventEmitter); Parser.prototype._updatePosition = function(initialOffset){ if(this.endIndex === null){ @@ -117,7 +131,7 @@ Parser.prototype._updatePosition = function(initialOffset){ } } else this.startIndex = this.endIndex + 1; - this.endIndex = this._tokenizer._index; + this.endIndex = this._tokenizer.getAbsoluteIndex(); }; //Tokenizer event handlers @@ -135,10 +149,10 @@ Parser.prototype.onopentagname = function(name){ this._tagname = name; - if (!this._options.xmlMode && name in openImpliesClose) { + if(!this._options.xmlMode && name in openImpliesClose) { for( var el; - (el = this._stack[this._stack.length-1]) in openImpliesClose[name]; + (el = this._stack[this._stack.length - 1]) in openImpliesClose[name]; this.onclosetag(el) ); } @@ -206,7 +220,7 @@ Parser.prototype._closeCurrentTag = function(){ //self-closing tags will be on the top of the stack //(cheaper check than in onclosetag) - if(this._stack[this._stack.length-1] === name){ + if(this._stack[this._stack.length - 1] === name){ if(this._cbs.onclosetag){ this._cbs.onclosetag(name); } @@ -306,6 +320,8 @@ Parser.prototype.reset = function(){ this._attribname = ""; this._attribs = null; this._stack = []; + + if(this._cbs.onparserinit) this._cbs.onparserinit(this); }; //Parses a complete HTML document and pushes it to the handler diff --git a/lib/Stream.js b/lib/Stream.js index 29a819b..0ac49a6 100644 --- a/lib/Stream.js +++ b/lib/Stream.js @@ -6,7 +6,7 @@ function Stream(options){ Parser.call(this, new Cbs(this), options); } -require("util").inherits(Stream, Parser); +require("inherits")(Stream, Parser); Stream.prototype.readable = true; diff --git a/lib/Tokenizer.js b/lib/Tokenizer.js index 42f465e..d67427c 100644 --- a/lib/Tokenizer.js +++ b/lib/Tokenizer.js @@ -136,6 +136,7 @@ function Tokenizer(options, cbs){ this._buffer = ""; this._sectionStart = 0; this._index = 0; + this._bufferOffset = 0; //chars removed from _buffer this._baseState = TEXT; this._special = SPECIAL_NONE; this._cbs = cbs; @@ -165,6 +166,9 @@ Tokenizer.prototype._stateText = function(c){ Tokenizer.prototype._stateBeforeTagName = function(c){ if(c === "/"){ this._state = BEFORE_CLOSING_TAG_NAME; + } else if(c === "<"){ + this._cbs.ontext(this._getSection()); + this._sectionStart = this._index; } else if(c === ">" || this._special !== SPECIAL_NONE || whitespace(c)) { this._state = TEXT; } else if(c === "!"){ @@ -173,9 +177,6 @@ Tokenizer.prototype._stateBeforeTagName = function(c){ } else if(c === "?"){ this._state = IN_PROCESSING_INSTRUCTION; this._sectionStart = this._index + 1; - } else if(c === "<"){ - this._cbs.ontext(this._getSection()); - this._sectionStart = this._index; } else { this._state = (!this._xmlMode && (c === "s" || c === "S")) ? BEFORE_SPECIAL : IN_TAG_NAME; @@ -408,7 +409,7 @@ Tokenizer.prototype._stateAfterCdata2 = function(c){ this._cbs.oncdata(this._buffer.substring(this._sectionStart, this._index - 2)); this._state = TEXT; this._sectionStart = this._index + 1; - } else if (c !== "]") { + } else if(c !== "]") { this._state = IN_CDATA; } //else: stay in AFTER_CDATA_2 (`]]]>`) @@ -594,9 +595,10 @@ Tokenizer.prototype._stateInHexEntity = function(c){ } }; -Tokenizer.prototype._cleanup = function () { +Tokenizer.prototype._cleanup = function (){ if(this._sectionStart < 0){ this._buffer = ""; + this._bufferOffset += this._index; this._index = 0; } else if(this._running){ if(this._state === TEXT){ @@ -604,15 +606,18 @@ Tokenizer.prototype._cleanup = function () { this._cbs.ontext(this._buffer.substr(this._sectionStart)); } this._buffer = ""; + this._bufferOffset += this._index; this._index = 0; } else if(this._sectionStart === this._index){ //the section just started this._buffer = ""; + this._bufferOffset += this._index; this._index = 0; } else { //remove everything unnecessary this._buffer = this._buffer.substr(this._sectionStart); this._index -= this._sectionStart; + this._bufferOffset += this._sectionStart; } this._sectionStart = 0; @@ -879,6 +884,10 @@ Tokenizer.prototype.reset = function(){ Tokenizer.call(this, {xmlMode: this._xmlMode, decodeEntities: this._decodeEntities}, this._cbs); }; +Tokenizer.prototype.getAbsoluteIndex = function(){ + return this._bufferOffset + this._index; +}; + Tokenizer.prototype._getSection = function(){ return this._buffer.substring(this._sectionStart, this._index); }; diff --git a/lib/WritableStream.js b/lib/WritableStream.js index e65b073..9868f8a 100644 --- a/lib/WritableStream.js +++ b/lib/WritableStream.js @@ -1,21 +1,25 @@ module.exports = Stream; var Parser = require("./Parser.js"), - WritableStream = require("stream").Writable || require("readable-stream").Writable; + WritableStream = require("stream").Writable || require("readable-stream").Writable, + StringDecoder = require("string_decoder").StringDecoder, + Buffer = require("buffer").Buffer; function Stream(cbs, options){ var parser = this._parser = new Parser(cbs, options); + var decoder = this._decoder = new StringDecoder(); WritableStream.call(this, {decodeStrings: false}); this.once("finish", function(){ - parser.end(); + parser.end(decoder.end()); }); } -require("util").inherits(Stream, WritableStream); +require("inherits")(Stream, WritableStream); WritableStream.prototype._write = function(chunk, encoding, cb){ + if(chunk instanceof Buffer) chunk = this._decoder.write(chunk); this._parser.write(chunk); cb(); };
\ No newline at end of file diff --git a/lib/index.js b/lib/index.js index 66ce768..880f57e 100644 --- a/lib/index.js +++ b/lib/index.js @@ -36,7 +36,7 @@ module.exports = { return defineProp("RssHandler", this.FeedHandler); }, //helper methods - parseDOM: function(data, options) { + parseDOM: function(data, options){ var handler = new DomHandler(options); new Parser(handler, options).end(data); return handler.dom; diff --git a/package.json b/package.json index ee98bf9..95f2d54 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "htmlparser2", "description": "Fast & forgiving HTML/XML/RSS parser", - "version": "3.7.3", + "version": "3.9.2", "author": "Felix Boehm <me@feedic.com>", "keywords": [ "html", @@ -25,44 +25,32 @@ "lib": "lib/" }, "main": "lib/index.js", + "files": [ + "lib" + ], "scripts": { "lcov": "istanbul cover _mocha --report lcovonly -- -R spec", "coveralls": "npm run lint && npm run lcov && (cat coverage/lcov.info | coveralls || exit 0)", "test": "mocha && npm run lint", - "lint": "jshint {lib,test}/*.js test/*/*.js" + "lint": "eslint lib test" }, "dependencies": { - "domhandler": "2.2", - "domutils": "1.5", - "domelementtype": "1", - "readable-stream": "1.1", - "entities": "1.0" + "domelementtype": "^1.3.0", + "domhandler": "^2.3.0", + "domutils": "^1.5.1", + "entities": "^1.1.1", + "inherits": "^2.0.1", + "readable-stream": "^2.0.2" }, "devDependencies": { - "mocha": "1", - "mocha-lcov-reporter": "*", - "coveralls": "*", - "istanbul": "*", - "jshint": "2" + "coveralls": "^2.11.4", + "istanbul": "^0.4.3", + "mocha": "^2.2.5", + "eslint": "^2.12.0", + "mocha-lcov-reporter": "^1.2.0" + }, + "browser": { + "readable-stream": false }, - "license": "MIT", - "jshintConfig": { - "eqeqeq": true, - "freeze": true, - "latedef": "nofunc", - "noarg": true, - "nonbsp": true, - "quotmark": "double", - "undef": true, - "unused": true, - "trailing": true, - "eqnull": true, - "proto": true, - "smarttabs": true, - "node": true, - "globals": { - "describe": true, - "it": true - } - } + "license": "MIT" } diff --git a/test/02-stream.js b/test/02-stream.js index 3403980..fe6044d 100644 --- a/test/02-stream.js +++ b/test/02-stream.js @@ -1,7 +1,7 @@ var helper = require("./test-helper.js"), - Stream = require("..").WritableStream, - fs = require("fs"), - path = require("path"); + Stream = require("..").WritableStream, + fs = require("fs"), + path = require("path"); helper.mochaTest("Stream", __dirname, function(test, cb){ var filePath = path.join(__dirname, "Documents", test.file); diff --git a/test/03-feed.js b/test/03-feed.js index b97bbba..7849953 100644 --- a/test/03-feed.js +++ b/test/03-feed.js @@ -1,9 +1,9 @@ //Runs tests for feeds var helper = require("./test-helper.js"), - FeedHandler = require("..").RssHandler, - fs = require("fs"), - path = require("path"); + FeedHandler = require("..").RssHandler, + fs = require("fs"), + path = require("path"); helper.mochaTest("Feeds", __dirname, function(test, cb){ fs.readFile( diff --git a/test/Documents/Atom_Example.xml b/test/Documents/Atom_Example.xml index 7349745..f836380 100644 --- a/test/Documents/Atom_Example.xml +++ b/test/Documents/Atom_Example.xml @@ -19,7 +19,7 @@ <link rel="edit" href="http://example.org/2003/12/13/atom03/edit"/> <id>urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a</id> <updated>2003-12-13T18:30:02Z</updated> - <summary>Some text.</summary> + <content type="html"><p>Some content.</p></content> </entry> -</feed>
\ No newline at end of file +</feed> diff --git a/test/Documents/RDF_Example.xml b/test/Documents/RDF_Example.xml index 068da17..b76dc37 100644 --- a/test/Documents/RDF_Example.xml +++ b/test/Documents/RDF_Example.xml @@ -1,61 +1,61 @@ <?xml version="1.0" encoding="UTF-8"?> <rdf:RDF xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns="http://purl.org/rss/1.0/" xmlns:ev="http://purl.org/rss/1.0/modules/event/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:syn="http://purl.org/rss/1.0/modules/syndication/" xmlns:dcterms="http://purl.org/dc/terms/" xmlns:admin="http://webns.net/mvcb/"> - <channel rdf:about="http://sfbay.craigslist.org/ccc/"> - <title>craigslist | all community in SF bay area</title> - <link>http://sfbay.craigslist.org/ccc/</link> + <channel rdf:about="https://github.com/fb55/htmlparser2/"> + <title>A title to parse and remember</title> + <link>https://github.com/fb55/htmlparser2/</link> <description/> <dc:language>en-us</dc:language> - <dc:rights>Copyright 2011 craigslist, inc.</dc:rights> - <dc:publisher>webmaster@craigslist.org</dc:publisher> - <dc:creator>webmaster@craigslist.org</dc:creator> - <dc:source>http://sfbay.craigslist.org/ccc//</dc:source> - <dc:title>craigslist | all community in SF bay area</dc:title> + <dc:rights>Copyright 2015 the authors</dc:rights> + <dc:publisher>webmaster@thisisafakedoma.in</dc:publisher> + <dc:creator>webmaster@thisisafakedoma.in</dc:creator> + <dc:source>https://github.com/fb55/htmlparser2/</dc:source> + <dc:title>A title to parse and remember</dc:title> <dc:type>Collection</dc:type> <syn:updateBase>2011-11-04T09:39:10-07:00</syn:updateBase> <syn:updateFrequency>4</syn:updateFrequency> <syn:updatePeriod>hourly</syn:updatePeriod> <items> <rdf:Seq> - <rdf:li rdf:resource="http://sfbay.craigslist.org/sby/muc/2681301534.html"/> + <rdf:li rdf:resource="http://somefakesite/path/to/something.html"/> </rdf:Seq> </items> </channel> - <item rdf:about="http://sfbay.craigslist.org/sby/muc/2681301534.html"> - <title><![CDATA[ Music Equipment Repair and Consignment ]]></title> + <item rdf:about="http://somefakesite/path/to/something.html"> + <title><![CDATA[ Fast HTML Parsing ]]></title> <link> -http://sfbay.craigslist.org/sby/muc/2681301534.html +http://somefakesite/path/to/something.html </link> <description><![CDATA[ -San Jose Rock Shop offers musical instrument repair and consignment! (408) 215-2065<br> <br> We are pleased to announce our NEW LOCATION: 1199 N 5th st. San Jose, ca 95112. Please call ahead, by appointment only.<br> <br> Recently featured by Metro Newspaper in their 2011 Best of the Silicon Valley edition see it online here:<br> <a href="http://www.metroactive.com/best-of-silicon-valley/2011/music-nightlife/editor-picks.html" rel="nofollow">http://www.metroactive.com/best-of-silicon-valley/2011/music-nightlife/editor-picks.html</a><br> <br> Guitar Set up (acoustic and electronic) $40!<!-- END CLTAGS --> +Great test content<br>A link: <a href="http://github.com">Github</a> ]]></description> <dc:date>2011-11-04T09:35:17-07:00</dc:date> <dc:language>en-us</dc:language> - <dc:rights>Copyright 2011 craigslist, inc.</dc:rights> + <dc:rights>Copyright 2015 the authors</dc:rights> <dc:source> -http://sfbay.craigslist.org/sby/muc/2681301534.html +http://somefakesite/path/to/something.html </dc:source> - <dc:title><![CDATA[ Music Equipment Repair and Consignment ]]></dc:title> + <dc:title><![CDATA[ Fast HTML Parsing ]]></dc:title> <dc:type>text</dc:type> <dcterms:issued>2011-11-04T09:35:17-07:00</dcterms:issued> </item> - <item rdf:about="http://sfbay.craigslist.org/eby/rid/2685010755.html"> + <item rdf:about="http://somefakesite/path/to/something-else.html"> <title><![CDATA[ -Ride Offered - Oakland/BART to LA/SFV - TODAY 3PM 11/04 (oakland north / temescal) +This space intentionally left blank ]]></title> <link> -http://sfbay.craigslist.org/eby/rid/2685010755.html +http://somefakesite/path/to/something-else.html </link> <description><![CDATA[ -Im offering a lift for up to two people from Oakland (or near any BART station in the East Bay/580/880 Corridor, or San Jose/Morgan Hill, Gilroy) to the San Fernando Valley / Los Angeles area. Specifically, Im leaving from Oakland between 2:30 and 3:00pm (this is flexible, but if I leave too late my girlfriend will kill me), and heading to Woodland Hills via the 580, I-5, 405, and 101.<!-- END CLTAGS --> +The early bird gets the worm ]]></description> <dc:date>2011-11-04T09:34:54-07:00</dc:date> <dc:language>en-us</dc:language> - <dc:rights>Copyright 2011 craigslist, inc.</dc:rights> + <dc:rights>Copyright 2015 the authors</dc:rights> <dc:source> -http://sfbay.craigslist.org/eby/rid/2685010755.html +http://somefakesite/path/to/something-else.html </dc:source> <dc:title><![CDATA[ -Ride Offered - Oakland/BART to LA/SFV - TODAY 3PM 11/04 (oakland north / temescal) +This space intentionally left blank ]]></dc:title> <dc:type>text</dc:type> <dcterms:issued>2011-11-04T09:34:54-07:00</dcterms:issued> diff --git a/test/Events/08-implicit-close-tags.json b/test/Events/08-implicit-close-tags.json index 3441f20..331e785 100644 --- a/test/Events/08-implicit-close-tags.json +++ b/test/Events/08-implicit-close-tags.json @@ -1,7 +1,7 @@ { "name": "Implicit close tags", "options": {}, - "html": "<ol><li class=test><div><table style=width:100%><tr><td colspan=2><h3>Heading</h3><tr><td><div>Div</div><td><div>Div2</div></table></div><li><div><h3>Heading 2</h3></div></li></ol>", + "html": "<ol><li class=test><div><table style=width:100%><tr><th>TH<td colspan=2><h3>Heading</h3><tr><td><div>Div</div><td><div>Div2</div></table></div><li><div><h3>Heading 2</h3></div></li></ol><p>Para<h4>Heading 4</h4>", "expected": [ { "event": "opentagname", "data": [ "ol" ] }, { "event": "opentag", "data": [ "ol", {} ] }, @@ -15,6 +15,10 @@ { "event": "opentag", "data": [ "table", { "style": "width:100%" } ] }, { "event": "opentagname", "data": [ "tr" ] }, { "event": "opentag", "data": [ "tr", {} ] }, + { "event": "opentagname", "data": [ "th" ] }, + { "event": "opentag", "data": [ "th", {} ] }, + { "event": "text", "data": [ "TH" ] }, + { "event": "closetag", "data": [ "th" ] }, { "event": "opentagname", "data": [ "td" ] }, { "event": "attribute", "data": [ "colspan", "2" ] }, { "event": "opentag", "data": [ "td", { "colspan": "2" } ] }, @@ -54,6 +58,14 @@ { "event": "closetag", "data": [ "h3" ] }, { "event": "closetag", "data": [ "div" ] }, { "event": "closetag", "data": [ "li" ] }, - { "event": "closetag", "data": [ "ol" ] } + { "event": "closetag", "data": [ "ol" ] }, + { "event": "opentagname", "data": [ "p" ] }, + { "event": "opentag", "data": [ "p", {} ] }, + { "event": "text", "data": [ "Para" ] }, + { "event": "closetag", "data": [ "p" ] }, + { "event": "opentagname", "data": [ "h4" ] }, + { "event": "opentag", "data": [ "h4", {} ] }, + { "event": "text", "data": [ "Heading 4" ] }, + { "event": "closetag", "data": [ "h4" ] } ] }
\ No newline at end of file diff --git a/test/Events/32-script-ending-with-lessthan.json b/test/Events/32-script-ending-with-lessthan.json new file mode 100644 index 0000000..dcf7690 --- /dev/null +++ b/test/Events/32-script-ending-with-lessthan.json @@ -0,0 +1,35 @@ +{ + "name": "Scripts ending with <", + "options": { + "handler": {}, + "parser": {} + }, + "html": "<script><</script>", + "expected": [ + { + "event": "opentagname", + "data": [ + "script" + ] + }, + { + "event": "opentag", + "data": [ + "script", + {} + ] + }, + { + "event": "text", + "data": [ + "<" + ] + }, + { + "event": "closetag", + "data": [ + "script" + ] + } + ] +} diff --git a/test/Feeds/02-atom.js b/test/Feeds/02-atom.js index 92b6de3..5b5d88e 100644 --- a/test/Feeds/02-atom.js +++ b/test/Feeds/02-atom.js @@ -12,7 +12,7 @@ exports.expected = { id: "urn:uuid:1225c695-cfb8-4ebb-aaaa-80da344efa6a", title: "Atom-Powered Robots Run Amok", link: "http://example.org/2003/12/13/atom03", - description: "Some text.", + description: "Some content.", pubDate: new Date("2003-12-13T18:30:02Z") }] -};
\ No newline at end of file +}; diff --git a/test/Feeds/03-rdf.js b/test/Feeds/03-rdf.js index b38ee13..d8f92f5 100644 --- a/test/Feeds/03-rdf.js +++ b/test/Feeds/03-rdf.js @@ -3,18 +3,18 @@ exports.file = "/RDF_Example.xml"; exports.expected = { "type": "rdf", "id": "", - "title": "craigslist | all community in SF bay area", - "link": "http://sfbay.craigslist.org/ccc/", + "title": "A title to parse and remember", + "link": "https://github.com/fb55/htmlparser2/", "items": [ { - "title": "Music Equipment Repair and Consignment", - "link": "http://sfbay.craigslist.org/sby/muc/2681301534.html", - "description": "San Jose Rock Shop offers musical instrument repair and consignment! (408) 215-2065<br> <br> We are pleased to announce our NEW LOCATION: 1199 N 5th st. San Jose, ca 95112. Please call ahead, by appointment only.<br> <br> Recently featured by Metro Newspaper in their 2011 Best of the Silicon Valley edition see it online here:<br> <a href=\"http://www.metroactive.com/best-of-silicon-valley/2011/music-nightlife/editor-picks.html\" rel=\"nofollow\">http://www.metroactive.com/best-of-silicon-valley/2011/music-nightlife/editor-picks.html</a><br> <br> Guitar Set up (acoustic and electronic) $40!<!-- END CLTAGS -->" + "title": "Fast HTML Parsing", + "link": "http://somefakesite/path/to/something.html", + "description": "Great test content<br>A link: <a href=\"http://github.com\">Github</a>" }, { - "title": "Ride Offered - Oakland/BART to LA/SFV - TODAY 3PM 11/04 (oakland north / temescal)", - "link": "http://sfbay.craigslist.org/eby/rid/2685010755.html", - "description": "Im offering a lift for up to two people from Oakland (or near any BART station in the East Bay/580/880 Corridor, or San Jose/Morgan Hill, Gilroy) to the San Fernando Valley / Los Angeles area. Specifically, Im leaving from Oakland between 2:30 and 3:00pm (this is flexible, but if I leave too late my girlfriend will kill me), and heading to Woodland Hills via the 580, I-5, 405, and 101.<!-- END CLTAGS -->" + "title": "This space intentionally left blank", + "link": "http://somefakesite/path/to/something-else.html", + "description": "The early bird gets the worm" } ] }; diff --git a/test/Stream/03-Atom.json b/test/Stream/03-Atom.json index 4c81b24..0cbf24e 100644 --- a/test/Stream/03-Atom.json +++ b/test/Stream/03-Atom.json @@ -592,28 +592,56 @@ ] }, { + "event": "opentagname", + "data": [ + "content" + ] + }, + { + "event": "attribute", + "data": [ + "type", + "html" + ] + }, + { + "event": "opentag", + "data": [ + "content", + { + "type": "html" + } + ] + }, + { "event": "opentagname", "data": [ - "summary" + "p" ] }, { "event": "opentag", "data": [ - "summary", + "p", {} ] }, { "event": "text", "data": [ - "Some text." + "Some content." + ] + }, + { + "event": "closetag", + "data": [ + "p" ] }, { "event": "closetag", "data": [ - "summary" + "content" ] }, { @@ -639,6 +667,12 @@ "data": [ "feed" ] + }, + { + "event": "text", + "data": [ + "\n" + ] } ] -}
\ No newline at end of file +} diff --git a/test/Stream/04-RDF.json b/test/Stream/04-RDF.json index 7ebf516..0150eb8 100644 --- a/test/Stream/04-RDF.json +++ b/test/Stream/04-RDF.json @@ -118,7 +118,7 @@ "event": "attribute", "data": [ "rdf:about", - "http://sfbay.craigslist.org/ccc/" + "https://github.com/fb55/htmlparser2/" ] }, { @@ -126,7 +126,7 @@ "data": [ "channel", { - "rdf:about": "http://sfbay.craigslist.org/ccc/" + "rdf:about": "https://github.com/fb55/htmlparser2/" } ] }, @@ -152,7 +152,7 @@ { "event": "text", "data": [ - "craigslist | all community in SF bay area" + "A title to parse and remember" ] }, { @@ -183,7 +183,7 @@ { "event": "text", "data": [ - "http://sfbay.craigslist.org/ccc/" + "https://github.com/fb55/htmlparser2/" ] }, { @@ -270,7 +270,7 @@ { "event": "text", "data": [ - "Copyright 2011 craigslist, inc." + "Copyright 2015 the authors" ] }, { @@ -301,7 +301,7 @@ { "event": "text", "data": [ - "webmaster@craigslist.org" + "webmaster@thisisafakedoma.in" ] }, { @@ -332,7 +332,7 @@ { "event": "text", "data": [ - "webmaster@craigslist.org" + "webmaster@thisisafakedoma.in" ] }, { @@ -363,7 +363,7 @@ { "event": "text", "data": [ - "http://sfbay.craigslist.org/ccc//" + "https://github.com/fb55/htmlparser2/" ] }, { @@ -394,7 +394,7 @@ { "event": "text", "data": [ - "craigslist | all community in SF bay area" + "A title to parse and remember" ] }, { @@ -581,7 +581,7 @@ "event": "attribute", "data": [ "rdf:resource", - "http://sfbay.craigslist.org/sby/muc/2681301534.html" + "http://somefakesite/path/to/something.html" ] }, { @@ -589,7 +589,7 @@ "data": [ "rdf:li", { - "rdf:resource": "http://sfbay.craigslist.org/sby/muc/2681301534.html" + "rdf:resource": "http://somefakesite/path/to/something.html" } ] }, @@ -651,7 +651,7 @@ "event": "attribute", "data": [ "rdf:about", - "http://sfbay.craigslist.org/sby/muc/2681301534.html" + "http://somefakesite/path/to/something.html" ] }, { @@ -659,7 +659,7 @@ "data": [ "item", { - "rdf:about": "http://sfbay.craigslist.org/sby/muc/2681301534.html" + "rdf:about": "http://somefakesite/path/to/something.html" } ] }, @@ -689,7 +689,7 @@ { "event": "text", "data": [ - " Music Equipment Repair and Consignment " + " Fast HTML Parsing " ] }, { @@ -724,7 +724,7 @@ { "event": "text", "data": [ - "\nhttp://sfbay.craigslist.org/sby/muc/2681301534.html\n" + "\nhttp://somefakesite/path/to/something.html\n" ] }, { @@ -759,7 +759,7 @@ { "event": "text", "data": [ - "\nSan Jose Rock Shop offers musical instrument repair and consignment! (408) 215-2065<br> <br> We are pleased to announce our NEW LOCATION: 1199 N 5th st. San Jose, ca 95112. Please call ahead, by appointment only.<br> <br> Recently featured by Metro Newspaper in their 2011 Best of the Silicon Valley edition see it online here:<br> <a href=\"http://www.metroactive.com/best-of-silicon-valley/2011/music-nightlife/editor-picks.html\" rel=\"nofollow\">http://www.metroactive.com/best-of-silicon-valley/2011/music-nightlife/editor-picks.html</a><br> <br> Guitar Set up (acoustic and electronic) $40!<!-- END CLTAGS -->\n" + "\nGreat test content<br>A link: <a href=\"http://github.com\">Github</a>\n" ] }, { @@ -856,7 +856,7 @@ { "event": "text", "data": [ - "Copyright 2011 craigslist, inc." + "Copyright 2015 the authors" ] }, { @@ -887,7 +887,7 @@ { "event": "text", "data": [ - "\nhttp://sfbay.craigslist.org/sby/muc/2681301534.html\n" + "\nhttp://somefakesite/path/to/something.html\n" ] }, { @@ -922,7 +922,7 @@ { "event": "text", "data": [ - " Music Equipment Repair and Consignment " + " Fast HTML Parsing " ] }, { @@ -1025,7 +1025,7 @@ "event": "attribute", "data": [ "rdf:about", - "http://sfbay.craigslist.org/eby/rid/2685010755.html" + "http://somefakesite/path/to/something-else.html" ] }, { @@ -1033,7 +1033,7 @@ "data": [ "item", { - "rdf:about": "http://sfbay.craigslist.org/eby/rid/2685010755.html" + "rdf:about": "http://somefakesite/path/to/something-else.html" } ] }, @@ -1063,7 +1063,7 @@ { "event": "text", "data": [ - "\nRide Offered - Oakland/BART to LA/SFV - TODAY 3PM 11/04 (oakland north / temescal)\n" + "\nThis space intentionally left blank\n" ] }, { @@ -1098,7 +1098,7 @@ { "event": "text", "data": [ - "\nhttp://sfbay.craigslist.org/eby/rid/2685010755.html\n" + "\nhttp://somefakesite/path/to/something-else.html\n" ] }, { @@ -1133,7 +1133,7 @@ { "event": "text", "data": [ - "\nIm offering a lift for up to two people from Oakland (or near any BART station in the East Bay/580/880 Corridor, or San Jose/Morgan Hill, Gilroy) to the San Fernando Valley / Los Angeles area. Specifically, Im leaving from Oakland between 2:30 and 3:00pm (this is flexible, but if I leave too late my girlfriend will kill me), and heading to Woodland Hills via the 580, I-5, 405, and 101.<!-- END CLTAGS -->\n" + "\nThe early bird gets the worm\n" ] }, { @@ -1230,7 +1230,7 @@ { "event": "text", "data": [ - "Copyright 2011 craigslist, inc." + "Copyright 2015 the authors" ] }, { @@ -1261,7 +1261,7 @@ { "event": "text", "data": [ - "\nhttp://sfbay.craigslist.org/eby/rid/2685010755.html\n" + "\nhttp://somefakesite/path/to/something-else.html\n" ] }, { @@ -1296,7 +1296,7 @@ { "event": "text", "data": [ - "\nRide Offered - Oakland/BART to LA/SFV - TODAY 3PM 11/04 (oakland north / temescal)\n" + "\nThis space intentionally left blank\n" ] }, { @@ -1396,4 +1396,4 @@ ] } ] -}
\ No newline at end of file +} diff --git a/test/api.js b/test/api.js index 5a8709d..a6a76ef 100644 --- a/test/api.js +++ b/test/api.js @@ -60,4 +60,44 @@ describe("API", function(){ assert(processed); }); + + it("should update the position", function(){ + var p = new htmlparser2.Parser(null); + + p.write("foo"); + + assert.equal(p.startIndex, 0); + assert.equal(p.endIndex, 2); + + p.write("<bar>"); + + assert.equal(p.startIndex, 3); + assert.equal(p.endIndex, 7); + }); + + it("should update the position when a single tag is spread across multiple chunks", function(){ + var p = new htmlparser2.Parser(null); + + p.write("<div "); + p.write("foo=bar>"); + + assert.equal(p.startIndex, 0); + assert.equal(p.endIndex, 12); + }); + + it("should support custom tokenizer", function(){ + function CustomTokenizer(options, cbs){ + htmlparser2.Tokenizer.call(this, options, cbs); + return this; + } + CustomTokenizer.prototype = Object.create(htmlparser2.Tokenizer.prototype); + CustomTokenizer.prototype.constructor = CustomTokenizer; + + var p = new htmlparser2.Parser({ + onparserinit: function(parser){ + assert(parser._tokenizer instanceof CustomTokenizer); + } + }, { Tokenizer: CustomTokenizer }); + p.done(); + }); });
\ No newline at end of file diff --git a/test/test-helper.js b/test/test-helper.js index 6c42341..3f39bf5 100644 --- a/test/test-helper.js +++ b/test/test-helper.js @@ -2,8 +2,8 @@ var htmlparser2 = require(".."), fs = require("fs"), path = require("path"), assert = require("assert"), - Parser = htmlparser2.Parser, - CollectingHandler = htmlparser2.CollectingHandler; + Parser = htmlparser2.Parser, + CollectingHandler = htmlparser2.CollectingHandler; exports.writeToParser = function(handler, options, data){ var parser = new Parser(handler, options); @@ -29,8 +29,8 @@ exports.getEventCollector = function(cb){ function eventReducer(events, arr){ if(arr[0] === "onerror" || arr[0] === "onend"); - else if(arr[0] === "ontext" && events.length && events[events.length-1].event === "text"){ - events[events.length-1].data[0] += arr[1]; + else if(arr[0] === "ontext" && events.length && events[events.length - 1].event === "text"){ + events[events.length - 1].data[0] += arr[1]; } else { events.push({ event: arr[0].substr(2), diff --git a/test/unicode.js b/test/unicode.js new file mode 100644 index 0000000..602b4ca --- /dev/null +++ b/test/unicode.js @@ -0,0 +1,21 @@ +var htmlparser2 = require(".."), + assert = require("assert"); + +describe("WritableStream", function(){ + + it("should decode fragmented unicode characters", function(){ + var processed = false; + var stream = new htmlparser2.WritableStream({ + ontext: function(text){ + assert.equal(text, "€"); + processed = true; + } + }); + + stream.write(new Buffer([0xE2, 0x82])); + stream.write(new Buffer([0xAC])); + stream.end(); + + assert(processed); + }); +});
\ No newline at end of file |
