From f2176e04c13761b897b002b58ea84dbee05e0f8d Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 17:08:25 +0200 Subject: [PATCH 01/61] rename file --- htmlparser.js | 316 ------------------------------ jquery.sp-html-parser.js | 408 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 408 insertions(+), 316 deletions(-) delete mode 100644 htmlparser.js create mode 100644 jquery.sp-html-parser.js diff --git a/htmlparser.js b/htmlparser.js deleted file mode 100644 index f0f547d..0000000 --- a/htmlparser.js +++ /dev/null @@ -1,316 +0,0 @@ -/* - * HTML5 Parser By Sam Blowes - * - * Designed for HTML5 documents - * - * Original code by John Resig (ejohn.org) - * http://ejohn.org/blog/pure-javascript-html-parser/ - * Original code by Erik Arvidsson, Mozilla Public License - * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js - * - * // Use like so: - * HTMLParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - * // or to get an XML string: - * HTMLtoXML(htmlString); - * - * // or to get an XML DOM Document - * HTMLtoDOM(htmlString); - * - * // or to inject into an existing document/DOM node - * HTMLtoDOM(htmlString, document); - * HTMLtoDOM(htmlString, document.body); - * - */ - -(function () { - - // Regular Expressions for parsing tags and attributes - var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, - endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, - attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; - - // Empty Elements - HTML 5 - var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed"); - - // Block Elements - HTML 5 - var block = makeMap("address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"); - - // Inline Elements - HTML 5 - var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); - - // Elements that you can, intentionally, leave open - // (and which close themselves) - var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); - - // Attributes that have their values filled in disabled="disabled" - var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); - - // Special Elements (can contain anything) - var special = makeMap("script,style"); - - var HTMLParser = this.HTMLParser = function (html, handler) { - var index, chars, match, stack = [], last = html; - stack.last = function () { - return this[this.length - 1]; - }; - - while (html) { - chars = true; - - // Make sure we're not in a script or style element - if (!stack.last() || !special[stack.last()]) { - - // Comment - if (html.indexOf(""); - - if (index >= 0) { - if (handler.comment) - handler.comment(html.substring(4, index)); - html = html.substring(index + 3); - chars = false; - } - - // end tag - } else if (html.indexOf("]*>"), function (all, text) { - text = text.replace(/|/g, "$1$2"); - if (handler.chars) - handler.chars(text); - - return ""; - }); - - parseEndTag("", stack.last()); - } - - if (html == last) - throw "Parse Error: " + html; - last = html; - } - - // Clean up any remaining tags - parseEndTag(); - - function parseStartTag(tag, tagName, rest, unary) { - tagName = tagName.toLowerCase(); - - if (block[tagName]) { - while (stack.last() && inline[stack.last()]) { - parseEndTag("", stack.last()); - } - } - - if (closeSelf[tagName] && stack.last() == tagName) { - parseEndTag("", tagName); - } - - unary = empty[tagName] || !!unary; - - if (!unary) - stack.push(tagName); - - if (handler.start) { - var attrs = []; - - rest.replace(attr, function (match, name) { - var value = arguments[2] ? arguments[2] : - arguments[3] ? arguments[3] : - arguments[4] ? arguments[4] : - fillAttrs[name] ? name : ""; - - attrs.push({ - name: name, - value: value, - escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //" - }); - }); - - if (handler.start) - handler.start(tagName, attrs, unary); - } - } - - function parseEndTag(tag, tagName) { - // If no tag name is provided, clean shop - if (!tagName) - var pos = 0; - - // Find the closest opened tag of the same type - else - for (var pos = stack.length - 1; pos >= 0; pos--) - if (stack[pos] == tagName) - break; - - if (pos >= 0) { - // Close all the open elements, up the stack - for (var i = stack.length - 1; i >= pos; i--) - if (handler.end) - handler.end(stack[i]); - - // Remove the open elements from the stack - stack.length = pos; - } - } - }; - - this.HTMLtoXML = function (html) { - var results = ""; - - HTMLParser(html, { - start: function (tag, attrs, unary) { - results += "<" + tag; - - for (var i = 0; i < attrs.length; i++) - results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; - results += ">"; - }, - end: function (tag) { - results += ""; - }, - chars: function (text) { - results += text; - }, - comment: function (text) { - results += ""; - } - }); - - return results; - }; - - this.HTMLtoDOM = function (html, doc) { - // There can be only one of these elements - var one = makeMap("html,head,body,title"); - - // Enforce a structure for the document - var structure = { - link: "head", - base: "head" - }; - - if (!doc) { - if (typeof DOMDocument != "undefined") - doc = new DOMDocument(); - else if (typeof document != "undefined" && document.implementation && document.implementation.createDocument) - doc = document.implementation.createDocument("", "", null); - else if (typeof ActiveX != "undefined") - doc = new ActiveXObject("Msxml.DOMDocument"); - - } else - doc = doc.ownerDocument || - doc.getOwnerDocument && doc.getOwnerDocument() || - doc; - - var elems = [], - documentElement = doc.documentElement || - doc.getDocumentElement && doc.getDocumentElement(); - - // If we're dealing with an empty document then we - // need to pre-populate it with the HTML document structure - if (!documentElement && doc.createElement) (function () { - var html = doc.createElement("html"); - var head = doc.createElement("head"); - head.appendChild(doc.createElement("title")); - html.appendChild(head); - html.appendChild(doc.createElement("body")); - doc.appendChild(html); - })(); - - // Find all the unique elements - if (doc.getElementsByTagName) - for (var i in one) - one[i] = doc.getElementsByTagName(i)[0]; - - // If we're working with a document, inject contents into - // the body element - var curParentNode = one.body; - - HTMLParser(html, { - start: function (tagName, attrs, unary) { - // If it's a pre-built element, then we can ignore - // its construction - if (one[tagName]) { - curParentNode = one[tagName]; - if (!unary) { - elems.push(curParentNode); - } - return; - } - - var elem = doc.createElement(tagName); - - for (var attr in attrs) - elem.setAttribute(attrs[attr].name, attrs[attr].value); - - if (structure[tagName] && typeof one[structure[tagName]] != "boolean") - one[structure[tagName]].appendChild(elem); - - else if (curParentNode && curParentNode.appendChild) - curParentNode.appendChild(elem); - - if (!unary) { - elems.push(elem); - curParentNode = elem; - } - }, - end: function (tag) { - elems.length -= 1; - - // Init the new parentNode - curParentNode = elems[elems.length - 1]; - }, - chars: function (text) { - curParentNode.appendChild(doc.createTextNode(text)); - }, - comment: function (text) { - // create comment node - } - }); - - return doc; - }; - - function makeMap(str) { - var obj = {}, items = str.split(","); - for (var i = 0; i < items.length; i++) - obj[items[i]] = true; - return obj; - } -})(); diff --git a/jquery.sp-html-parser.js b/jquery.sp-html-parser.js new file mode 100644 index 0000000..3a40994 --- /dev/null +++ b/jquery.sp-html-parser.js @@ -0,0 +1,408 @@ +/** + * This plugin is used to parse and transform an HTML/XML document to a new one. It can also be used + * to fix a bad-formed HTML/XML document. + * + * This code was originally designed by Erik Arvidsson: + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * and then changed by John Resig: + * http://ejohn.org/files/htmlparser.js + * + * and then changed by Sam Blowes: + * https://github.com/soloproyectos/jquery.htmlparser/blob/master/htmlparser.js + * + * and then changed by me to work as a jQuery plugin: + * https://github.com/soloproyectos/jquery.htmlparser + * + * ### Examples of use: + * + * // Example 1: fixes a bad-formed HTML document + * $html = $.htmlParser('

Bad formed
html document'); + * + * // Example 2: parses an HTML document + * var html = + * '

Actually we do not exist.
' + + * 'But before we can prove it, we will have already disappeared.

'; + * $.htmlParser(html, { + * start: function () { + * // 'this' is a jQuery object representing the current node + * console.log('Start tag: <' + this.prop('tagName') + '>'); + * }, + * end: function () { + * console.log('End tag: '); + * }, + * text: function () { + * console.log('Text: ' + this.text()); + * }, + * comment: function (text) { + * console.log('Comment: ' + this.text()); + * } + * }); + * + * // Example 3: transform a HTML document to another one + * // This examples replaces the following CSS properties: + * // 1. 'font-weight: bold' is replaced by '' + * // 2. 'font-style: italic' is replaced by '' + * // 3. 'text-decoration: underline' is replaced by '' + * var html = + * 'The quick brown fox jumps over the ' + + * 'lazy dog and feels as if ' + + * 'he were in the ' + + * 'seventh heaven of ' + + * 'typography together with Hermann Zapf, the most famous artist of the...'; + * var str = $.htmlParser(html, function () { + * var ret = this; + * var replacements = [ + * {style: 'font-weight', value: 'bold', entity: 'strong'}, + * {style: 'font-style', value: 'italic', entity: 'em'}, + * {style: 'text-decoration', value: 'underline', entity: 'u'} + * ]; + * + * // 'this' is an object representing the current node + * if (this.prop('tagName') == 'SPAN') { + * var target = this; + * + * $.each(replacements, function () { + * if (target.css(this.style) == this.value) { + * // wraps the result around the corresponding entity + * ret = $('<' + this.entity + ' />').append(ret); + * + * // removes the css style + * target.css(this.style, ''); + * + * // removes the 'span' node if it doesn't have any attribute + * if (target[0].attributes.length == 0) { + * target.replaceWith(target.contents()); + * } + * } + * }); + * } + * + * return ret; + * }); + * console.log(str); + * + * This code was originally designed by Erik Arvidsson: + * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js + * + * and then changed by John Resig: + * http://ejohn.org/files/htmlparser.js + * + * and then changed by Sam Blowes: + * https://github.com/soloproyectos/jquery.htmlparser/blob/master/htmlparser.js + * + * and then changed by me to work as a jQuery plugin: + * https://github.com/soloproyectos/jquery.htmlparser + * + * @author Gonzalo Chumillas + * @license http://www.apache.org/licenses/LICENSE-2.0.html Apache Software License 2.0 + * @link https://github.com/soloproyectos/jquery.htmlparser + */ +(function ($) { + + // Regular Expressions for parsing tags and attributes + var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, + endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, + attr = /([a-zA-Z_:][-a-zA-Z0-9_:.]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; + + // Empty Elements - HTML 5 + var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,isindex,link,meta,param,embed"); + + // Block Elements - HTML 5 + var block = makeMap("address,article,applet,aside,audio,blockquote,button,canvas,center,dd,del,dir,div,dl,dt,fieldset,figcaption,figure,footer,form,frameset,h1,h2,h3,h4,h5,h6,header,hgroup,hr,iframe,ins,isindex,li,map,menu,noframes,noscript,object,ol,output,p,pre,section,script,table,tbody,td,tfoot,th,thead,tr,ul,video"); + + // Inline Elements - HTML 5 + var inline = makeMap("a,abbr,acronym,applet,b,basefont,bdo,big,br,button,cite,code,del,dfn,em,font,i,iframe,img,input,ins,kbd,label,map,object,q,s,samp,script,select,small,span,strike,strong,sub,sup,textarea,tt,u,var"); + + // Elements that you can, intentionally, leave open + // (and which close themselves) + var closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"); + + // Attributes that have their values filled in disabled="disabled" + var fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"); + + // Special Elements (can contain anything) + var special = makeMap("script,style"); + + /** + * This class parses an HTML/XML document. + * + * @param {String} html HTML/XML document + * @param {Object} handler Plain object + * + * @return {HTMLParser} + */ + var HTMLParser = function (html, handler) { + var index, chars, match, stack = [], last = html; + stack.last = function () { + return this[this.length - 1]; + }; + + while (html) { + chars = true; + + // Make sure we're not in a script or style element + if (!stack.last() || !special[stack.last()]) { + + // Comment + if (html.indexOf(""); + + if (index >= 0) { + if (handler.comment) + handler.comment(html.substring(4, index)); + html = html.substring(index + 3); + chars = false; + } + + // end tag + } else if (html.indexOf("]*>"), function (all, text) { + text = text.replace(/|/g, "$1$2"); + if (handler.chars) + handler.chars(text); + + return ""; + }); + + parseEndTag("", stack.last()); + } + + if (html == last) + throw "Parse Error: " + html; + last = html; + } + + // Clean up any remaining tags + parseEndTag(); + + function parseStartTag(tag, tagName, rest, unary) { + tagName = tagName.toLowerCase(); + + if (block[tagName]) { + while (stack.last() && inline[stack.last()]) { + parseEndTag("", stack.last()); + } + } + + if (closeSelf[tagName] && stack.last() == tagName) { + parseEndTag("", tagName); + } + + unary = empty[tagName] || !!unary; + + if (!unary) + stack.push(tagName); + + if (handler.start) { + var attrs = []; + + rest.replace(attr, function (match, name) { + var value = arguments[2] ? arguments[2] : + arguments[3] ? arguments[3] : + arguments[4] ? arguments[4] : + fillAttrs[name] ? name : ""; + + attrs.push({ + name: name, + value: value, + escaped: value.replace(/(^|[^\\])"/g, '$1\\\"') //" + }); + }); + + if (handler.start) + handler.start(tagName, attrs, unary); + } + } + + function parseEndTag(tag, tagName) { + // If no tag name is provided, clean shop + if (!tagName) + var pos = 0; + + // Find the closest opened tag of the same type + else + for (var pos = stack.length - 1; pos >= 0; pos--) + if (stack[pos] == tagName) + break; + + if (pos >= 0) { + // Close all the open elements, up the stack + for (var i = stack.length - 1; i >= pos; i--) + if (handler.end) + handler.end(stack[i]); + + // Remove the open elements from the stack + stack.length = pos; + } + } + }; + + function makeMap(str) { + var obj = {}, items = str.split(","); + for (var i = 0; i < items.length; i++) + obj[items[i]] = true; + return obj; + } + + /** + * Transforms a bad-formed HTML/XML document to a well-formed document. + * + * @param {String} html HTML/XML document + * + * @return {String} + */ + var html2xml = function (html) { + var results = ""; + + HTMLParser(html, { + start: function (tag, attrs, unary) { + results += "<" + tag; + + for (var i = 0; i < attrs.length; i++) + results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; + results += unary? " />" : ">"; + }, + end: function (tag) { + results += ""; + }, + chars: function (text) { + results += text; + }, + comment: function (text) { + results += ""; + } + }); + + return results; + }; + + /** + * This plugin parses an HTML/XML document. + * + * @param {String} html HTML/XML document + * @param {Object|Function} handler Handler (not required) + * + * @return {String} + */ + $.htmlParser = function (html, handler) { + var nodes = [$('
')]; + + // executes a handler + function exec(handler, node) { + var item = $.proxy(handler, node)(); + return item !== undefined? item : node; + } + + // adds a node to nodes + function pushNode(tagName, attrs, handler) { + var node = $('<' + tagName + '/>'); + + // appends attributes + $.each(attrs, function () { + node.attr(this.name, this.value); + }); + + nodes.push(handler !== undefined? exec(handler, node) : node); + } + + // removes the last node from nodes + function popNode(handler) { + var node = nodes.pop(); + var parentNode = nodes[nodes.length - 1]; + + parentNode.append(handler !== undefined? exec(handler, node) : node); + } + + // appends a text node to the last element of nodes + function appendText(text, handler) { + var node = $(document.createTextNode(text)); + var parentNode = nodes[nodes.length - 1]; + + parentNode.append(handler !== undefined? exec(handler, node) : node); + } + + // appends a comment node to the last element of nodes + function appendComment(text, handler) { + var node = $(document.createComment(text)); + var parentNode = nodes[nodes.length - 1]; + + parentNode.append(handler !== undefined? exec(handler, node) : node); + } + + if ($.isPlainObject(handler)) { + new HTMLParser(html, { + start: function (tagName, attrs, unary) { + pushNode(tagName, attrs, handler.start); + + if (unary) { + popNode(handler.end); + } + }, + end: function (tagName) { + popNode(handler.end); + }, + chars: function (text) { + appendText(text, handler.text); + }, + comment: function (text) { + appendComment(text, handler.comment); + } + }); + } else + if ($.type(handler) == 'function') { + new HTMLParser(html, { + start: function (tagName, attrs, unary) { + pushNode(tagName, attrs); + + if (unary) { + popNode(handler); + } + }, + end: function (tagName) { + popNode(handler); + }, + chars: function (text) { + appendText(text, handler); + }, + comment: function (text) { + appendComment(text, handler); + } + }); + } else { + return html2xml(html); + } + + return nodes.pop().html(); + }; +})(jQuery); From 88496af4b49ea1fa577325c28edb06ef83b5da84 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 17:09:32 +0200 Subject: [PATCH 02/61] rename file --- jquery.sp-html-parser.js => jquery.htmlparser.js | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename jquery.sp-html-parser.js => jquery.htmlparser.js (100%) diff --git a/jquery.sp-html-parser.js b/jquery.htmlparser.js similarity index 100% rename from jquery.sp-html-parser.js rename to jquery.htmlparser.js From efa38c93e2d2500c7a6a3f8a779a03a29e08805f Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 17:10:04 +0200 Subject: [PATCH 03/61] delete demo.html --- demo.html | 79 ------------------------------------------------------- 1 file changed, 79 deletions(-) delete mode 100644 demo.html diff --git a/demo.html b/demo.html deleted file mode 100644 index b4a63e2..0000000 --- a/demo.html +++ /dev/null @@ -1,79 +0,0 @@ - - - - Pure JavaScript HTML5 Parser - Demo - - - -
-
-
-
-

Pure JavaScript HTML5 Parser

-

All-in-one: XML Serializer, DOM Builder, DOM Document Creator, A SAX-style API

-

- Learn more -

-
-
-
-
-
-
-
-
-
- -
-
-
- -
-
-
-
-

While this library doesn't cover the full gamut of possible weirdness that HTML provides, it does handle a lot of the most obvious stuff. All of the following are accounted for:

-
    -
  • Unclosed Tags: -
    HTMLtoXML("<p><b>Hello") == '<p><b>Hello</b></p>'
    -
  • -
  • Empty Elements: -
    HTMLtoXML("<img src=test.jpg>") == '<img src="test.jpg">'
    -
  • -
  • Block vs. Inline Elements: -
    HTMLtoXML("<b>Hello <p>John") == '<b>Hello </b><p>John</p>'
    -
  • -
  • Self-closing Elements: -
    HTMLtoXML("<p>Hello<p>World") == '<p>Hello</p><p>World</p>'
    -
  • -
  • Attributes Without Values: -
    HTMLtoXML("<input disabled>") == '<input disabled="disabled">'
    -
  • -
-
-
Note: It does not take into account where in the document an element should exist. Right now you can put block elements in a head or th inside a p and it'll happily accept them. It's not entirely clear how the logic should work for those, but it's something that I'm open to exploring.
-
-
-
-
- - - - From 6f2d158d330affef3ad4da47027f4e010fd32b5b Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 17:26:05 +0200 Subject: [PATCH 04/61] add json file --- htmlparser.jquery.json | 38 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 38 insertions(+) create mode 100644 htmlparser.jquery.json diff --git a/htmlparser.jquery.json b/htmlparser.jquery.json new file mode 100644 index 0000000..9874472 --- /dev/null +++ b/htmlparser.jquery.json @@ -0,0 +1,38 @@ +{ + "name": "htmlparser", + "title": "Parse, fix or transform an HTML/XML docment", + "description": "This plugin parses, fixes or transforms an HTML/XML document", + "keywords": [ + "jquery", + "html", + "parser", + "transform", + "fix", + "clean", + "plugins" + ], + "version": "0.1.0", + "author": { + "name": "Gonzalo Chumillas", + "url": "https://github.com/soloproyectos" + }, + "maintainers": [ + { + "name": "Gonzalo Chumillas", + "url": "https://github.com/soloproyectos" + } + ], + "licenses": [ + { + "type": "Apache Software License 2.0", + "url": "http://www.apache.org/licenses/LICENSE-2.0.html" + } + ], + "bugs": "https://github.com/soloproyectos/jquery.htmlparser/issues", + "homepage": "https://github.com/soloproyectos/jquery.htmlparser", + "docs": "https://github.com/soloproyectos/jquery.htmlparser", + "download": "https://github.com/soloproyectos/jquery.htmlparser/archive/master.zip", + "dependencies": { + "jquery": ">=1.9" + } +} From aff51c8d5dbf8c996d95c5282446c577315cbcb5 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 17:27:55 +0200 Subject: [PATCH 05/61] change download link --- htmlparser.jquery.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htmlparser.jquery.json b/htmlparser.jquery.json index 9874472..09976ca 100644 --- a/htmlparser.jquery.json +++ b/htmlparser.jquery.json @@ -31,7 +31,7 @@ "bugs": "https://github.com/soloproyectos/jquery.htmlparser/issues", "homepage": "https://github.com/soloproyectos/jquery.htmlparser", "docs": "https://github.com/soloproyectos/jquery.htmlparser", - "download": "https://github.com/soloproyectos/jquery.htmlparser/archive/master.zip", + "download": "https://github.com/soloproyectos/jquery.htmlparser/releases", "dependencies": { "jquery": ">=1.9" } From c264d144138a16d94bb7409658d7aa046e86e9fd Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 17:49:00 +0200 Subject: [PATCH 06/61] add demo file --- demo.html | 79 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 demo.html diff --git a/demo.html b/demo.html new file mode 100644 index 0000000..010de83 --- /dev/null +++ b/demo.html @@ -0,0 +1,79 @@ + + + + jQuery.htmlParser test file + + + + + + + +

Example 1: fix a bad-formed HTML/XML document

+ This example fixes a bad-formed HTML/XML document:
+
+ + +

Example 2: transform an HTML/XML document

+ This example replaces the following CSS properties: +
    +
  • font-weight:bold => <strong>
  • +
  • font-style:italic => <em>
  • +
  • text-decoration:underline => <u>
  • +
+
+ + + From a483a80b3084ff2c5822d241860c249219d03ebc Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 17:55:53 +0200 Subject: [PATCH 07/61] Update README.md --- README.md | 100 +++++------------------------------------------------- 1 file changed, 8 insertions(+), 92 deletions(-) diff --git a/README.md b/README.md index 60fda8d..aef8fab 100644 --- a/README.md +++ b/README.md @@ -1,96 +1,12 @@ -# Pure JavaScript HTML5 Parser # +jQuery.htmlParser plugin +======================== +This plugin can parse, clean or transform an HTML/XML document. -A working demo can be seen [here](http://htmlpreview.github.io/?https://github.com/blowsie/Pure-JavaScript-HTML-Parser/blob/master/demo.html). +Installation +------------ -_Credit goes to John Resig for his [code](http://ejohn.org/blog/pure-javascript-html-parser/) written back in 2008 and Erik Arvidsson for his [code](http://erik.eae.net/simplehtmlparser/simplehtmlparser.js) written piror to that._ +Download the jquery.htmlparser.js file in your project. -This code has been updated to work with HTML 5 to fix several problems. - - - - -## 4 Libraries in One! ## - -### A SAX-style API ### - -Handles tag, text, and comments with callbacks. For example, let’s say you wanted to implement a simple HTML to XML serialization scheme – you could do so using the following: - - var results = ""; - - HTMLParser("

hello world", { - start: function( tag, attrs, unary ) { - results += "<" + tag; - - for ( var i = 0; i < attrs.length; i++ ) - results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; - - results += ">"; - }, - end: function( tag ) { - results += ""; - }, - chars: function( text ) { - results += text; - }, - comment: function( text ) { - results += ""; - } - }); - - results == '

hello world

" - -### XML Serializer ### - -Now, there’s no need to worry about implementing the above, since it’s included directly in the library, as well. Just feed in HTML and it spits back an XML string. - - var results = HTMLtoXML("

Data: ") - results == '

Data:

' - - -### DOM Builder ### - -If you’re using the HTML parser to inject into an existing DOM document (or within an existing DOM element) then htmlparser.js provides a simple method for handling that: - - // The following is appended into the document body - HTMLtoDOM("

Hello World", document) - - // The follow is appended into the specified element - HTMLtoDOM("

Hello World", document.getElementById("test")) - - -### DOM Document Creator ### - -This is a more-advanced version of the DOM builder – it includes logic for handling the overall structure of a web page, returning a new DOM document. - -A couple points are enforced by this method: - - - There will always be a html, head, body, and title element. - - There will only be one html, head, body, and title element (if the user specifies more, then will be moved to the appropriate locations and merged). -link and base elements are forced into the head. - -You would use the method like so: - - var dom = HTMLtoDOM("

Data: "); - dom.getElementsByTagName("body").length == 1 - dom.getElementsByTagName("p").length == 1 - - -While this library doesn’t cover the full gamut of possible weirdness that HTML provides, it does handle a lot of the most obvious stuff. All of the following are accounted for: - -**Unclosed Tags:** - - HTMLtoXML("

Hello") == '

Hello

' -**Empty Elements:** - - HTMLtoXML("") == '' - -**Block vs. Inline Elements:** - - HTMLtoXML("Hello

John") == 'Hello

John

' -**Self-closing Elements:** - - HTMLtoXML("

Hello

World") == '

Hello

World

' -**Attributes Without Values:** - - HTMLtoXML("") == '' +Examples: +--------- From c575f4b8ef2c82c679dd97614c7449a404cfb904 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:14:16 +0200 Subject: [PATCH 08/61] Update README.md --- README.md | 86 ++++++++++++++++++++++++++++++++++++++++++++++++++++--- 1 file changed, 82 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index aef8fab..1152566 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,88 @@ jQuery.htmlParser plugin This plugin can parse, clean or transform an HTML/XML document. -Installation ------------- +This code was originally written by Erik Arvidsson: +http://erik.eae.net/simplehtmlparser/simplehtmlparser.js +and then changed by John Resig: +http://ejohn.org/files/htmlparser.js +and then changed by Sam Blowes: +https://github.com/soloproyectos/jquery.htmlparser/blob/master/htmlparser.js +and then changed by me. +### Installation Download the jquery.htmlparser.js file in your project. -Examples: ---------- +### Examples + +#### Examples 1: Clean a bad-formed HTML/XML document + + // fixes a bad-formed HTML document + $html = $.htmlParser('

Bad formed
html document'); + +#### Example 2: Parse an HTML/XML document + + // Example 2: parses an HTML document + var html = + '

Actually we do not exist.
' + + 'But before we can prove it, we will have already disappeared.

'; + $.htmlParser(html, { + start: function () { + // 'this' is a jQuery object representing the current node + console.log('Start tag: <' + this.prop('tagName') + '>'); + }, + end: function () { + console.log('End tag: '); + }, + text: function () { + console.log('Text: ' + this.text()); + }, + comment: function (text) { + console.log('Comment: ' + this.text()); + } + }); + + +##### Example 3: Transform an HTML/XML document to a new one + + // Example 3: transform a HTML document to another one + // This examples replaces the following CSS properties: + // 1. 'font-weight: bold' is replaced by '' + // 2. 'font-style: italic' is replaced by '' + // 3. 'text-decoration: underline' is replaced by '' + var html = + 'The quick brown fox jumps over the ' + + 'lazy dog and feels as if ' + + 'he were in the ' + + 'seventh heaven of ' + + 'typography together with Hermann Zapf, the most famous artist of the...'; + var str = $.htmlParser(html, function () { + var ret = this; + var replacements = [ + {style: 'font-weight', value: 'bold', entity: 'strong'}, + {style: 'font-style', value: 'italic', entity: 'em'}, + {style: 'text-decoration', value: 'underline', entity: 'u'} + ]; + + // 'this' is an object representing the current node + if (this.prop('tagName') == 'SPAN') { + var target = this; + + $.each(replacements, function () { + if (target.css(this.style) == this.value) { + // wraps the result around the corresponding entity + ret = $('<' + this.entity + ' />').append(ret); + + // removes the css style + target.css(this.style, ''); + + // removes the 'span' node if it doesn't have any attribute + if (target[0].attributes.length == 0) { + target.replaceWith(target.contents()); + } + } + }); + } + + return ret; + }); + console.log(str); From 00716c522ebc55a01727aeb792f6b87ef3e97163 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:15:56 +0200 Subject: [PATCH 09/61] update changes --- demo.html | 130 ++++++++-------- jquery.htmlparser.js | 348 +++++++++++++++++++++---------------------- 2 files changed, 239 insertions(+), 239 deletions(-) diff --git a/demo.html b/demo.html index 010de83..c844dc2 100644 --- a/demo.html +++ b/demo.html @@ -6,74 +6,74 @@ -

Example 1: fix a bad-formed HTML/XML document

- This example fixes a bad-formed HTML/XML document:
-
- - -

Example 2: transform an HTML/XML document

- This example replaces the following CSS properties: -
    -
  • font-weight:bold => <strong>
  • -
  • font-style:italic => <em>
  • -
  • text-decoration:underline => <u>
  • -
-
- +

Example 1: fix a bad-formed HTML/XML document

+ This example fixes a bad-formed HTML/XML document:
+
+ + +

Example 2: transform an HTML/XML document

+ This example replaces the following CSS properties: +
    +
  • font-weight:bold => <strong>
  • +
  • font-style:italic => <em>
  • +
  • text-decoration:underline => <u>
  • +
+
+ diff --git a/jquery.htmlparser.js b/jquery.htmlparser.js index 3a40994..bca7593 100644 --- a/jquery.htmlparser.js +++ b/jquery.htmlparser.js @@ -21,66 +21,66 @@ * * // Example 2: parses an HTML document * var html = - * '

Actually we do not exist.
' + - * 'But before we can prove it, we will have already disappeared.

'; + * '

Actually we do not exist.
' + + * 'But before we can prove it, we will have already disappeared.

'; * $.htmlParser(html, { - * start: function () { - * // 'this' is a jQuery object representing the current node - * console.log('Start tag: <' + this.prop('tagName') + '>'); - * }, - * end: function () { - * console.log('End tag: '); - * }, - * text: function () { - * console.log('Text: ' + this.text()); - * }, - * comment: function (text) { - * console.log('Comment: ' + this.text()); - * } + * start: function () { + * // 'this' is a jQuery object representing the current node + * console.log('Start tag: <' + this.prop('tagName') + '>'); + * }, + * end: function () { + * console.log('End tag: '); + * }, + * text: function () { + * console.log('Text: ' + this.text()); + * }, + * comment: function (text) { + * console.log('Comment: ' + this.text()); + * } * }); * * // Example 3: transform a HTML document to another one * // This examples replaces the following CSS properties: - * // 1. 'font-weight: bold' is replaced by '' - * // 2. 'font-style: italic' is replaced by '' - * // 3. 'text-decoration: underline' is replaced by '' + * // 1. 'font-weight: bold' is replaced by '' + * // 2. 'font-style: italic' is replaced by '' + * // 3. 'text-decoration: underline' is replaced by '' * var html = - * 'The quick brown fox jumps over the ' + - * 'lazy dog and feels as if ' + - * 'he were in the ' + - * 'seventh heaven of ' + - * 'typography together with Hermann Zapf, the most famous artist of the...'; - * var str = $.htmlParser(html, function () { - * var ret = this; - * var replacements = [ - * {style: 'font-weight', value: 'bold', entity: 'strong'}, - * {style: 'font-style', value: 'italic', entity: 'em'}, - * {style: 'text-decoration', value: 'underline', entity: 'u'} - * ]; - * - * // 'this' is an object representing the current node - * if (this.prop('tagName') == 'SPAN') { - * var target = this; - * - * $.each(replacements, function () { - * if (target.css(this.style) == this.value) { - * // wraps the result around the corresponding entity - * ret = $('<' + this.entity + ' />').append(ret); - * - * // removes the css style - * target.css(this.style, ''); - * - * // removes the 'span' node if it doesn't have any attribute - * if (target[0].attributes.length == 0) { - * target.replaceWith(target.contents()); - * } - * } - * }); - * } - * - * return ret; - * }); - * console.log(str); + * 'The quick brown fox jumps over the ' + + * 'lazy dog and feels as if ' + + * 'he were in the ' + + * 'seventh heaven of ' + + * 'typography together with Hermann Zapf, the most famous artist of the...'; + * var str = $.htmlParser(html, function () { + * var ret = this; + * var replacements = [ + * {style: 'font-weight', value: 'bold', entity: 'strong'}, + * {style: 'font-style', value: 'italic', entity: 'em'}, + * {style: 'text-decoration', value: 'underline', entity: 'u'} + * ]; + * + * // 'this' is an object representing the current node + * if (this.prop('tagName') == 'SPAN') { + * var target = this; + * + * $.each(replacements, function () { + * if (target.css(this.style) == this.value) { + * // wraps the result around the corresponding entity + * ret = $('<' + this.entity + ' />').append(ret); + * + * // removes the css style + * target.css(this.style, ''); + * + * // removes the 'span' node if it doesn't have any attribute + * if (target[0].attributes.length == 0) { + * target.replaceWith(target.contents()); + * } + * } + * }); + * } + * + * return ret; + * }); + * console.log(str); * * This code was originally designed by Erik Arvidsson: * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js @@ -124,14 +124,14 @@ // Special Elements (can contain anything) var special = makeMap("script,style"); - /** - * This class parses an HTML/XML document. - * - * @param {String} html HTML/XML document - * @param {Object} handler Plain object - * - * @return {HTMLParser} - */ + /** + * This class parses an HTML/XML document. + * + * @param {String} html HTML/XML document + * @param {Object} handler Plain object + * + * @return {HTMLParser} + */ var HTMLParser = function (html, handler) { var index, chars, match, stack = [], last = html; stack.last = function () { @@ -282,30 +282,30 @@ * * @return {String} */ - var html2xml = function (html) { - var results = ""; - - HTMLParser(html, { - start: function (tag, attrs, unary) { - results += "<" + tag; - - for (var i = 0; i < attrs.length; i++) - results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; - results += unary? " />" : ">"; - }, - end: function (tag) { - results += ""; - }, - chars: function (text) { - results += text; - }, - comment: function (text) { - results += ""; - } - }); - - return results; - }; + var html2xml = function (html) { + var results = ""; + + HTMLParser(html, { + start: function (tag, attrs, unary) { + results += "<" + tag; + + for (var i = 0; i < attrs.length; i++) + results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; + results += unary? " />" : ">"; + }, + end: function (tag) { + results += ""; + }, + chars: function (text) { + results += text; + }, + comment: function (text) { + results += ""; + } + }); + + return results; + }; /** * This plugin parses an HTML/XML document. @@ -316,93 +316,93 @@ * @return {String} */ $.htmlParser = function (html, handler) { - var nodes = [$('
')]; - - // executes a handler - function exec(handler, node) { - var item = $.proxy(handler, node)(); - return item !== undefined? item : node; - } - - // adds a node to nodes - function pushNode(tagName, attrs, handler) { - var node = $('<' + tagName + '/>'); - - // appends attributes - $.each(attrs, function () { - node.attr(this.name, this.value); - }); - - nodes.push(handler !== undefined? exec(handler, node) : node); - } - - // removes the last node from nodes - function popNode(handler) { - var node = nodes.pop(); - var parentNode = nodes[nodes.length - 1]; - - parentNode.append(handler !== undefined? exec(handler, node) : node); - } - - // appends a text node to the last element of nodes - function appendText(text, handler) { - var node = $(document.createTextNode(text)); - var parentNode = nodes[nodes.length - 1]; - - parentNode.append(handler !== undefined? exec(handler, node) : node); - } - - // appends a comment node to the last element of nodes - function appendComment(text, handler) { - var node = $(document.createComment(text)); - var parentNode = nodes[nodes.length - 1]; - - parentNode.append(handler !== undefined? exec(handler, node) : node); - } - - if ($.isPlainObject(handler)) { - new HTMLParser(html, { - start: function (tagName, attrs, unary) { - pushNode(tagName, attrs, handler.start); - - if (unary) { - popNode(handler.end); - } - }, - end: function (tagName) { - popNode(handler.end); - }, - chars: function (text) { - appendText(text, handler.text); - }, - comment: function (text) { - appendComment(text, handler.comment); - } - }); - } else - if ($.type(handler) == 'function') { - new HTMLParser(html, { - start: function (tagName, attrs, unary) { - pushNode(tagName, attrs); - - if (unary) { - popNode(handler); - } - }, - end: function (tagName) { - popNode(handler); - }, - chars: function (text) { - appendText(text, handler); - }, - comment: function (text) { - appendComment(text, handler); - } - }); - } else { - return html2xml(html); - } - - return nodes.pop().html(); + var nodes = [$('
')]; + + // executes a handler + function exec(handler, node) { + var item = $.proxy(handler, node)(); + return item !== undefined? item : node; + } + + // adds a node to nodes + function pushNode(tagName, attrs, handler) { + var node = $('<' + tagName + '/>'); + + // appends attributes + $.each(attrs, function () { + node.attr(this.name, this.value); + }); + + nodes.push(handler !== undefined? exec(handler, node) : node); + } + + // removes the last node from nodes + function popNode(handler) { + var node = nodes.pop(); + var parentNode = nodes[nodes.length - 1]; + + parentNode.append(handler !== undefined? exec(handler, node) : node); + } + + // appends a text node to the last element of nodes + function appendText(text, handler) { + var node = $(document.createTextNode(text)); + var parentNode = nodes[nodes.length - 1]; + + parentNode.append(handler !== undefined? exec(handler, node) : node); + } + + // appends a comment node to the last element of nodes + function appendComment(text, handler) { + var node = $(document.createComment(text)); + var parentNode = nodes[nodes.length - 1]; + + parentNode.append(handler !== undefined? exec(handler, node) : node); + } + + if ($.isPlainObject(handler)) { + new HTMLParser(html, { + start: function (tagName, attrs, unary) { + pushNode(tagName, attrs, handler.start); + + if (unary) { + popNode(handler.end); + } + }, + end: function (tagName) { + popNode(handler.end); + }, + chars: function (text) { + appendText(text, handler.text); + }, + comment: function (text) { + appendComment(text, handler.comment); + } + }); + } else + if ($.type(handler) == 'function') { + new HTMLParser(html, { + start: function (tagName, attrs, unary) { + pushNode(tagName, attrs); + + if (unary) { + popNode(handler); + } + }, + end: function (tagName) { + popNode(handler); + }, + chars: function (text) { + appendText(text, handler); + }, + comment: function (text) { + appendComment(text, handler); + } + }); + } else { + return html2xml(html); + } + + return nodes.pop().html(); }; })(jQuery); From 4b31c927965634cca0c74b8a8915c920f502809d Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:17:31 +0200 Subject: [PATCH 10/61] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1152566..c714e42 100644 --- a/README.md +++ b/README.md @@ -3,7 +3,7 @@ jQuery.htmlParser plugin This plugin can parse, clean or transform an HTML/XML document. -This code was originally written by Erik Arvidsson: +This code was originally written by Erik Arvidsson: http://erik.eae.net/simplehtmlparser/simplehtmlparser.js and then changed by John Resig: http://ejohn.org/files/htmlparser.js From 2a07e2b4d5f0eaccfb3b3f8e5816aec222d08995 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:21:07 +0200 Subject: [PATCH 11/61] Update README.md --- README.md | 128 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 67 insertions(+), 61 deletions(-) diff --git a/README.md b/README.md index c714e42..1e852c9 100644 --- a/README.md +++ b/README.md @@ -18,73 +18,79 @@ Download the jquery.htmlparser.js file in your project. #### Examples 1: Clean a bad-formed HTML/XML document - // fixes a bad-formed HTML document - $html = $.htmlParser('

Bad formed
html document'); +```JavaScript +// fixes a bad-formed HTML document +$html = $.htmlParser('

Bad formed
html document'); +``` #### Example 2: Parse an HTML/XML document - // Example 2: parses an HTML document - var html = - '

Actually we do not exist.
' + - 'But before we can prove it, we will have already disappeared.

'; - $.htmlParser(html, { - start: function () { - // 'this' is a jQuery object representing the current node - console.log('Start tag: <' + this.prop('tagName') + '>'); - }, - end: function () { - console.log('End tag: '); - }, - text: function () { - console.log('Text: ' + this.text()); - }, - comment: function (text) { - console.log('Comment: ' + this.text()); - } - }); +```JavaScript +// Example 2: parses an HTML document +var html = + '

Actually we do not exist.
' + + 'But before we can prove it, we will have already disappeared.

'; +$.htmlParser(html, { + start: function () { + // 'this' is a jQuery object representing the current node + console.log('Start tag: <' + this.prop('tagName') + '>'); + }, + end: function () { + console.log('End tag: '); + }, + text: function () { + console.log('Text: ' + this.text()); + }, + comment: function (text) { + console.log('Comment: ' + this.text()); + } +}); +``` ##### Example 3: Transform an HTML/XML document to a new one - // Example 3: transform a HTML document to another one - // This examples replaces the following CSS properties: - // 1. 'font-weight: bold' is replaced by '' - // 2. 'font-style: italic' is replaced by '' - // 3. 'text-decoration: underline' is replaced by '' - var html = - 'The quick brown fox jumps over the ' + - 'lazy dog and feels as if ' + - 'he were in the ' + - 'seventh heaven of ' + - 'typography together with Hermann Zapf, the most famous artist of the...'; - var str = $.htmlParser(html, function () { - var ret = this; - var replacements = [ - {style: 'font-weight', value: 'bold', entity: 'strong'}, - {style: 'font-style', value: 'italic', entity: 'em'}, - {style: 'text-decoration', value: 'underline', entity: 'u'} - ]; +```JavaScript +// Example 3: transform a HTML document to another one +// This examples replaces the following CSS properties: +// 1. 'font-weight: bold' is replaced by '' +// 2. 'font-style: italic' is replaced by '' +// 3. 'text-decoration: underline' is replaced by '' +var html = + 'The quick brown fox jumps over the ' + + 'lazy dog and feels as if ' + + 'he were in the ' + + 'seventh heaven of ' + + 'typography together with Hermann Zapf, the most famous artist of the...'; +var str = $.htmlParser(html, function () { + var ret = this; + var replacements = [ + {style: 'font-weight', value: 'bold', entity: 'strong'}, + {style: 'font-style', value: 'italic', entity: 'em'}, + {style: 'text-decoration', value: 'underline', entity: 'u'} + ]; + + // 'this' is an object representing the current node + if (this.prop('tagName') == 'SPAN') { + var target = this; - // 'this' is an object representing the current node - if (this.prop('tagName') == 'SPAN') { - var target = this; - - $.each(replacements, function () { - if (target.css(this.style) == this.value) { - // wraps the result around the corresponding entity - ret = $('<' + this.entity + ' />').append(ret); - - // removes the css style - target.css(this.style, ''); - - // removes the 'span' node if it doesn't have any attribute - if (target[0].attributes.length == 0) { - target.replaceWith(target.contents()); - } + $.each(replacements, function () { + if (target.css(this.style) == this.value) { + // wraps the result around the corresponding entity + ret = $('<' + this.entity + ' />').append(ret); + + // removes the css style + target.css(this.style, ''); + + // removes the 'span' node if it doesn't have any attribute + if (target[0].attributes.length == 0) { + target.replaceWith(target.contents()); } - }); - } - - return ret; - }); - console.log(str); + } + }); + } + + return ret; +}); +console.log(str); +``` From 45db7d4bf09a4ce6a0eea9f09bfa443c3fe36c5d Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:22:02 +0200 Subject: [PATCH 12/61] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 1e852c9..319c47c 100644 --- a/README.md +++ b/README.md @@ -48,7 +48,7 @@ $.htmlParser(html, { ``` -##### Example 3: Transform an HTML/XML document to a new one +#### Example 3: Transform an HTML/XML document to a new one ```JavaScript // Example 3: transform a HTML document to another one From 77149e30cb8d2195d4aec06562d9d2b8b3df3273 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:29:36 +0200 Subject: [PATCH 13/61] change version --- htmlparser.jquery.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htmlparser.jquery.json b/htmlparser.jquery.json index 09976ca..71d417a 100644 --- a/htmlparser.jquery.json +++ b/htmlparser.jquery.json @@ -11,7 +11,7 @@ "clean", "plugins" ], - "version": "0.1.0", + "version": "0.1.1", "author": { "name": "Gonzalo Chumillas", "url": "https://github.com/soloproyectos" From c7e4729cfaf5799fa6b4af5d00f08b52b1338e2e Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:30:41 +0200 Subject: [PATCH 14/61] change version --- htmlparser.jquery.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htmlparser.jquery.json b/htmlparser.jquery.json index 71d417a..6b07658 100644 --- a/htmlparser.jquery.json +++ b/htmlparser.jquery.json @@ -11,7 +11,7 @@ "clean", "plugins" ], - "version": "0.1.1", + "version": "0.1.2", "author": { "name": "Gonzalo Chumillas", "url": "https://github.com/soloproyectos" From bed819a8b97e1f3ec7cb535971a8565f3969dcfe Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:32:44 +0200 Subject: [PATCH 15/61] change version --- htmlparser.jquery.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htmlparser.jquery.json b/htmlparser.jquery.json index 6b07658..2ac9382 100644 --- a/htmlparser.jquery.json +++ b/htmlparser.jquery.json @@ -11,7 +11,7 @@ "clean", "plugins" ], - "version": "0.1.2", + "version": "0.1.3", "author": { "name": "Gonzalo Chumillas", "url": "https://github.com/soloproyectos" From 78044a943f22df5349f439279c9ec48e401d0d4a Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:36:26 +0200 Subject: [PATCH 16/61] typo --- htmlparser.jquery.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/htmlparser.jquery.json b/htmlparser.jquery.json index 2ac9382..ddc3e35 100644 --- a/htmlparser.jquery.json +++ b/htmlparser.jquery.json @@ -1,6 +1,6 @@ { "name": "htmlparser", - "title": "Parse, fix or transform an HTML/XML docment", + "title": "Parse, fix or transform an HTML/XML document", "description": "This plugin parses, fixes or transforms an HTML/XML document", "keywords": [ "jquery", From 59b8149c2136da17f3bf78e615066d600750729d Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:40:55 +0200 Subject: [PATCH 17/61] typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 319c47c..40dec0b 100644 --- a/README.md +++ b/README.md @@ -51,8 +51,8 @@ $.htmlParser(html, { #### Example 3: Transform an HTML/XML document to a new one ```JavaScript -// Example 3: transform a HTML document to another one -// This examples replaces the following CSS properties: +// Example 3: transform an HTML document to another one +// This example replaces the following CSS properties: // 1. 'font-weight: bold' is replaced by '' // 2. 'font-style: italic' is replaced by '' // 3. 'text-decoration: underline' is replaced by '' From 544649b3112d7f401a74a183dd0e9934a06c7e0a Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 18:41:55 +0200 Subject: [PATCH 18/61] typo --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 40dec0b..e494d54 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ Download the jquery.htmlparser.js file in your project. #### Examples 1: Clean a bad-formed HTML/XML document ```JavaScript -// fixes a bad-formed HTML document +// Example 1: fixes a bad-formed HTML document $html = $.htmlParser('

Bad formed
html document'); ``` @@ -51,7 +51,7 @@ $.htmlParser(html, { #### Example 3: Transform an HTML/XML document to a new one ```JavaScript -// Example 3: transform an HTML document to another one +// Example 3: transforms an HTML document to another one // This example replaces the following CSS properties: // 1. 'font-weight: bold' is replaced by '' // 2. 'font-style: italic' is replaced by '' From 964404f32650e0d6ae3df2404e135b745abb7e0f Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 20:45:22 +0200 Subject: [PATCH 19/61] typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e494d54..cdc8a26 100644 --- a/README.md +++ b/README.md @@ -8,7 +8,7 @@ http://erik.eae.net/simplehtmlparser/simplehtmlparser.js and then changed by John Resig: http://ejohn.org/files/htmlparser.js and then changed by Sam Blowes: -https://github.com/soloproyectos/jquery.htmlparser/blob/master/htmlparser.js +https://github.com/blowsie/Pure-JavaScript-HTML5-Parser and then changed by me. ### Installation From e40b9c7b2ea3facc2acfe274adf143a10e00ac71 Mon Sep 17 00:00:00 2001 From: Gonzalo Chumillas Date: Sun, 18 May 2014 21:12:00 +0200 Subject: [PATCH 20/61] fix link --- jquery.htmlparser.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/jquery.htmlparser.js b/jquery.htmlparser.js index bca7593..55fc2f6 100644 --- a/jquery.htmlparser.js +++ b/jquery.htmlparser.js @@ -89,7 +89,7 @@ * http://ejohn.org/files/htmlparser.js * * and then changed by Sam Blowes: - * https://github.com/soloproyectos/jquery.htmlparser/blob/master/htmlparser.js + * https://github.com/blowsie/Pure-JavaScript-HTML5-Parser * * and then changed by me to work as a jQuery plugin: * https://github.com/soloproyectos/jquery.htmlparser From c20d176245f4a2eeece30d04daf274a4c2cb075e Mon Sep 17 00:00:00 2001 From: Munawwar Firoz Date: Tue, 5 Aug 2014 23:50:44 +0400 Subject: [PATCH 21/61] JSHinting. --- htmlparser.js | 58 ++++++++++++++++++++++++++++----------------------- 1 file changed, 32 insertions(+), 26 deletions(-) diff --git a/htmlparser.js b/htmlparser.js index e4275e3..415da56 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -1,8 +1,11 @@ +/*global DOMDocument, ActiveXObject*/ + /* - * HTML5 Parser By Sam Blowes + * HTML5 Parser * * Designed for HTML5 documents * + * Original Code from HTML5 Parser By Sam Blowes (https://github.com/blowsie/Pure-JavaScript-HTML5-Parser) * Original code by John Resig (ejohn.org) * http://ejohn.org/blog/pure-javascript-html-parser/ * Original code by Erik Arvidsson, Mozilla Public License @@ -67,7 +70,7 @@ if (!stack.last() || !special[stack.last()]) { // Comment - if (html.indexOf(""); if (index >= 0) { @@ -78,7 +81,7 @@ } // end tag - } else if (html.indexOf("]*>"), function (all, text) { - text = text.replace(/|/g, "$1$2"); + text = text.replace(/|/g, "$1$2"); if (handler.chars) handler.chars(text); @@ -120,7 +123,7 @@ parseEndTag("", stack.last()); } - if (html == last) + if (html === last) throw "Parse Error: " + html; last = html; } @@ -137,7 +140,7 @@ } } - if (closeSelf[tagName] && stack.last() == tagName) { + if (closeSelf[tagName] && stack.last() === tagName) { parseEndTag("", tagName); } @@ -168,19 +171,20 @@ } function parseEndTag(tag, tagName) { + var pos; // If no tag name is provided, clean shop if (!tagName) - var pos = 0; + pos = 0; // Find the closest opened tag of the same type else - for (var pos = stack.length - 1; pos >= 0; pos--) - if (stack[pos] == tagName) + for (pos = stack.length - 1; pos >= 0; pos -= 1) + if (stack[pos] === tagName) break; if (pos >= 0) { // Close all the open elements, up the stack - for (var i = stack.length - 1; i >= pos; i--) + for (var i = stack.length - 1; i >= pos; i -= 1) if (handler.end) handler.end(stack[i]); @@ -197,7 +201,7 @@ start: function (tag, attrs, unary) { results += "<" + tag; - for (var i = 0; i < attrs.length; i++) + for (var i = 0; i < attrs.length; i += 1) results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; results += ">"; }, @@ -226,11 +230,11 @@ }; if (!doc) { - if (typeof DOMDocument != "undefined") + if (typeof DOMDocument !== "undefined") doc = new DOMDocument(); - else if (typeof document != "undefined" && document.implementation && document.implementation.createDocument) + else if (typeof document !== "undefined" && document.implementation && document.implementation.createDocument) doc = document.implementation.createDocument("", "", null); - else if (typeof ActiveX != "undefined") + else if (typeof ActiveX !== "undefined") doc = new ActiveXObject("Msxml.DOMDocument"); } else @@ -244,14 +248,16 @@ // If we're dealing with an empty document then we // need to pre-populate it with the HTML document structure - if (!documentElement && doc.createElement) (function () { - var html = doc.createElement("html"); - var head = doc.createElement("head"); - head.appendChild(doc.createElement("title")); - html.appendChild(head); - html.appendChild(doc.createElement("body")); - doc.appendChild(html); - })(); + if (!documentElement && doc.createElement) { + (function () { + var html = doc.createElement("html"); + var head = doc.createElement("head"); + head.appendChild(doc.createElement("title")); + html.appendChild(head); + html.appendChild(doc.createElement("body")); + doc.appendChild(html); + }()); + } // Find all the unique elements if (doc.getElementsByTagName) @@ -279,7 +285,7 @@ for (var attr in attrs) elem.setAttribute(attrs[attr].name, attrs[attr].value); - if (structure[tagName] && typeof one[structure[tagName]] != "boolean") + if (structure[tagName] && typeof one[structure[tagName]] !== "boolean") one[structure[tagName]].appendChild(elem); else if (curParentNode && curParentNode.appendChild) @@ -309,8 +315,8 @@ function makeMap(str) { var obj = {}, items = str.split(","); - for (var i = 0; i < items.length; i++) + for (var i = 0; i < items.length; i += 1) obj[items[i]] = true; return obj; } -})(); +}()); From 770fe91e40b6e92f497057c2dc9dc2e7f9e37402 Mon Sep 17 00:00:00 2001 From: Munawwar Firoz Date: Wed, 6 Aug 2014 00:00:26 +0400 Subject: [PATCH 22/61] Detect doctype and create it. Work only on IE9+. --- htmlparser.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/htmlparser.js b/htmlparser.js index 415da56..793969b 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -35,7 +35,8 @@ // Regular Expressions for parsing tags and attributes var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, - endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, + endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, + doctypeTag = /^\s]+)))?/g; // Empty Elements - HTML 5 @@ -91,6 +92,15 @@ } // start tag + } else if (doctypeTag.test(html)) { + index = html.indexOf(">"); + + if (index >= 0) { + if (handler.doctype) + handler.doctype(html.substring(0, index)); + html = html.substring(index + 1); + chars = false; + } } else if (html.indexOf("<") === 0) { match = html.match(startTag); @@ -307,6 +317,11 @@ }, comment: function (text) { // create comment node + curParentNode.appendChild(doc.createComment(text)); + }, + doctype: function (text) { + //Since we support only HTML5 we create HTML5 doctype. This won't work on IE8-. + doc.insertBefore(doc.implementation.createDocumentType('html', '', ''), doc.firstChild); } }); From 77ee9e7640dce2e1c3696dd90d89c6bdaf6047c5 Mon Sep 17 00:00:00 2001 From: Munawwar Firoz Date: Wed, 6 Aug 2014 00:09:45 +0400 Subject: [PATCH 23/61] Added UMD (use jsdom with nodejs). Dropped IE8- support. Dropped HtmlToXML function. HTMLParser will now be available through HTMLToDOM.Parser. --- htmlparser.js | 68 +++++++++++++++++---------------------------------- 1 file changed, 22 insertions(+), 46 deletions(-) diff --git a/htmlparser.js b/htmlparser.js index 793969b..d5b889a 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -11,18 +11,7 @@ * Original code by Erik Arvidsson, Mozilla Public License * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js * - * // Use like so: - * HTMLParser(htmlString, { - * start: function(tag, attrs, unary) {}, - * end: function(tag) {}, - * chars: function(text) {}, - * comment: function(text) {} - * }); - * - * // or to get an XML string: - * HTMLtoXML(htmlString); - * - * // or to get an XML DOM Document + * // or to get an DOM Document * HTMLtoDOM(htmlString); * * // or to inject into an existing document/DOM node @@ -30,8 +19,20 @@ * HTMLtoDOM(htmlString, document.body); * */ - -(function () { +(function (root, factory) { + if (typeof define === 'function' && define.amd) { + define(factory.bind(this)); + } else if (typeof exports === 'object') { //nodejs + var jsdom = require('jsdom').jsdom, + window = jsdom('').parentWindow; + module.exports = factory(window); + } else { + root.HTMLtoDOM = factory(); + } +}(this, function (window) { + //browser and jsdom compatibility + window = window || this; + var document = window.document; // Regular Expressions for parsing tags and attributes var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, @@ -58,7 +59,7 @@ // Special Elements (can contain anything) var special = makeMap("script,style"); - var HTMLParser = this.HTMLParser = function (html, handler) { + var HTMLParser = function (html, handler) { var index, chars, match, stack = [], last = html; stack.last = function () { return this[this.length - 1]; @@ -204,32 +205,7 @@ } }; - this.HTMLtoXML = function (html) { - var results = ""; - - HTMLParser(html, { - start: function (tag, attrs, unary) { - results += "<" + tag; - - for (var i = 0; i < attrs.length; i += 1) - results += " " + attrs[i].name + '="' + attrs[i].escaped + '"'; - results += ">"; - }, - end: function (tag) { - results += ""; - }, - chars: function (text) { - results += text; - }, - comment: function (text) { - results += ""; - } - }); - - return results; - }; - - this.HTMLtoDOM = function (html, doc) { + var HTMLtoDOM = function (html, doc) { // There can be only one of these elements var one = makeMap("html,head,body,title"); @@ -244,9 +220,6 @@ doc = new DOMDocument(); else if (typeof document !== "undefined" && document.implementation && document.implementation.createDocument) doc = document.implementation.createDocument("", "", null); - else if (typeof ActiveX !== "undefined") - doc = new ActiveXObject("Msxml.DOMDocument"); - } else doc = doc.ownerDocument || doc.getOwnerDocument && doc.getOwnerDocument() || @@ -333,5 +306,8 @@ for (var i = 0; i < items.length; i += 1) obj[items[i]] = true; return obj; - } -}()); + } + + HTMLtoDOM.Parser = HTMLParser; + return HTMLtoDOM; +})); From e318b5eb7e98467d2eb943134354cfb43c046d51 Mon Sep 17 00:00:00 2001 From: Munawwar Firoz Date: Wed, 6 Aug 2014 00:35:29 +0400 Subject: [PATCH 24/61] Added first test. Made parser trim trailing spaces,\n,\t,\r. --- Makefile | 4 ++++ htmlparser.js | 5 ++++- package.json | 31 +++++++++++++++++++++++++++++++ test/test.html | 20 ++++++++++++++++++++ test/test.js | 21 +++++++++++++++++++++ 5 files changed, 80 insertions(+), 1 deletion(-) create mode 100644 Makefile create mode 100644 package.json create mode 100644 test/test.html create mode 100644 test/test.js diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..a96b06a --- /dev/null +++ b/Makefile @@ -0,0 +1,4 @@ +test: + ./node_modules/.bin/mocha --reporter spec + +.PHONY: test diff --git a/htmlparser.js b/htmlparser.js index d5b889a..fe63625 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -59,7 +59,10 @@ // Special Elements (can contain anything) var special = makeMap("script,style"); - var HTMLParser = function (html, handler) { + var HTMLParser = function (html, handler) { + //remove trailing spaces + html = html.trim(); + var index, chars, match, stack = [], last = html; stack.last = function () { return this[this.length - 1]; diff --git a/package.json b/package.json new file mode 100644 index 0000000..c046095 --- /dev/null +++ b/package.json @@ -0,0 +1,31 @@ +{ + "name": "pure-javaScript-html5-parser", + "author": "munawwar", + "description": "HTML5 Parser", + "version": "0.0.1", + "main": "htmlparser.js", + "dependencies": { + "jsdom": "0.10.3" + }, + "devDependencies": { + "mocha": "1.18.2" + }, + "directories": { + "test": "test" + }, + "scripts": { + "test": "make test" + }, + "repository": { + "type": "git", + "url": "https://github.com/Munawwar/Pure-JavaScript-HTML5-Parser" + }, + "keywords": [ + "html parser" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/Munawwar/Pure-JavaScript-HTML5-Parser/issues" + }, + "homepage": "https://github.com/Munawwar/Pure-JavaScript-HTML5-Parser" +} diff --git a/test/test.html b/test/test.html new file mode 100644 index 0000000..ac8bb8b --- /dev/null +++ b/test/test.html @@ -0,0 +1,20 @@ + + + + + + + + + +

+
+
+
+ + + diff --git a/test/test.js b/test/test.js new file mode 100644 index 0000000..0e97826 --- /dev/null +++ b/test/test.js @@ -0,0 +1,21 @@ +/*global describe, it*/ + +var assert = require("assert"), + fs = require('fs'), + HTMLToDOM = require('../htmlparser.js'); + +describe('run HTMLToDOM test', function () { + var html = fetch('test/test.html'), + doc = HTMLToDOM(html); + it('it should have two nodes - doctype and html tag', function () { + assert.equal(2, doc.childNodes.length); + }); + it('html tag should have two child elements - head and body tag', function () { + assert.equal(2, doc.childNodes[1].children.length); + }); +}); + +/*Utility functions*/ +function fetch(pathToTextFile) { + return fs.readFileSync(pathToTextFile, {encoding: 'utf8'}); +} From 5882c1ace1e73564c6a07946049b43c091a1323b Mon Sep 17 00:00:00 2001 From: Munawwar Firoz Date: Sun, 22 Feb 2015 00:28:52 +0400 Subject: [PATCH 25/61] API and implementation changes. And several bug fixes. Look at longer commit message for details. 1. Second parameter to HTMLtoDOM() removed. 2. HTMLtoDOM() now returns DocumentFragment (except when doctype is used it returns Document). 3. head,html,body tags won't be auto-created anymore. 4. Fixed CDATA detection, stray < angle brackets, colon in HTML tag name, unicode in attribute name 5. Robust handling of extra end tags. Also robustness on trying to add text nodes to first level of Document. 6. Updated test. Added browser test to be able to debug code. I prefer debugging with browser than with nodejs. --- htmlparser.js | 222 ++++++++++++++++++++++------------------------ test/browser.html | 54 +++++++++++ test/test.html | 10 ++- test/test.js | 6 +- 4 files changed, 169 insertions(+), 123 deletions(-) create mode 100644 test/browser.html diff --git a/htmlparser.js b/htmlparser.js index fe63625..d6016c7 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -1,5 +1,3 @@ -/*global DOMDocument, ActiveXObject*/ - /* * HTML5 Parser * @@ -35,10 +33,11 @@ var document = window.document; // Regular Expressions for parsing tags and attributes - var startTag = /^<([-A-Za-z0-9_]+)((?:\s+[a-zA-Z_:][-a-zA-Z0-9_:.]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, - endTag = /^<\/([-A-Za-z0-9_]+)[^>]*>/, - doctypeTag = /^\s]+)))?/g; + var startTag = /^<([-\w:]+)((?:\s+[^\s\/>"'=]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)>/, + endTag = /^<\/([-\w:]+)[^>]*>/, + doctypeTag = /^/i, + attr = /([^\s\/>"'=]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/g; // Empty Elements - HTML 5 var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"); @@ -71,70 +70,83 @@ while (html) { chars = true; - // Make sure we're not in a script or style element - if (!stack.last() || !special[stack.last()]) { + //Handle script and style tags + if (special[stack.last()]) { + html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) { + if (handler.chars) + handler.chars(text); + return ""; + }); + parseEndTag("", stack.last()); // Comment - if (html.indexOf(""); - - if (index >= 0) { - if (handler.comment) - handler.comment(html.substring(4, index)); - html = html.substring(index + 3); - chars = false; - } + } else if (html.substring(0, 4) === ""); + + if (index >= 0) { + if (handler.comment) + handler.comment(html.substring(4, index)); + html = html.substring(index + 3); + chars = false; + } - // end tag - } else if (html.indexOf(""); + //CDATA + } else if (html.substring(0, 9).toUpperCase() === '= 0) { - if (handler.doctype) - handler.doctype(html.substring(0, index)); - html = html.substring(index + 1); - chars = false; - } - } else if (html.indexOf("<") === 0) { - match = html.match(startTag); - - if (match) { - html = html.substring(match[0].length); - match[0].replace(startTag, parseStartTag); - chars = false; - } + if (match) { + if (handler.cdata) + handler.cdata(match[1]); + html = html.substring(match[0].length); + chars = false; } - if (chars) { - index = html.indexOf("<"); - - var text = index < 0 ? html : html.substring(0, index); - html = index < 0 ? "" : html.substring(index); + // doctype + } else if (doctypeTag.test(html)) { + index = html.indexOf(">"); - if (handler.chars) - handler.chars(text); + if (index >= 0) { + if (handler.doctype) + handler.doctype(html.substring(0, index)); + html = html.substring(index + 1); + chars = false; + } + // start tag + } else if (html.charAt(0) === "<") { + match = html.match(startTag); + + if (match) { + html = html.substring(match[0].length); + match[0].replace(startTag, parseStartTag); + chars = false; + } else { //ignore the angle bracket + html = html.substring(1); + if (handler.chars) { + handler.chars('<'); + } + chars = false; } + } - } else { - html = html.replace(new RegExp("([\\s\\S]*?)<\/" + stack.last() + "[^>]*>"), function (all, text) { - text = text.replace(/|/g, "$1$2"); - if (handler.chars) - handler.chars(text); + if (chars) { + index = html.indexOf("<"); - return ""; - }); + var text = index < 0 ? html : html.substring(0, index); + html = index < 0 ? "" : html.substring(index); - parseEndTag("", stack.last()); + if (handler.chars) { + handler.chars(text); + } } if (html === last) @@ -145,7 +157,8 @@ // Clean up any remaining tags parseEndTag(); - function parseStartTag(tag, tagName, rest, unary) { + function parseStartTag(tag, tagName, rest, unary) { + var casePreservedTagName = tagName; tagName = tagName.toLowerCase(); if (block[tagName]) { @@ -180,7 +193,7 @@ }); if (handler.start) - handler.start(tagName, attrs, unary); + handler.start(casePreservedTagName, attrs, unary); } } @@ -208,66 +221,29 @@ } }; - var HTMLtoDOM = function (html, doc) { - // There can be only one of these elements - var one = makeMap("html,head,body,title"); - - // Enforce a structure for the document - var structure = { - link: "head", - base: "head" - }; - - if (!doc) { - if (typeof DOMDocument !== "undefined") - doc = new DOMDocument(); - else if (typeof document !== "undefined" && document.implementation && document.implementation.createDocument) - doc = document.implementation.createDocument("", "", null); - } else - doc = doc.ownerDocument || - doc.getOwnerDocument && doc.getOwnerDocument() || - doc; - - var elems = [], - documentElement = doc.documentElement || - doc.getDocumentElement && doc.getDocumentElement(); - - // If we're dealing with an empty document then we - // need to pre-populate it with the HTML document structure - if (!documentElement && doc.createElement) { - (function () { - var html = doc.createElement("html"); - var head = doc.createElement("head"); - head.appendChild(doc.createElement("title")); - html.appendChild(head); - html.appendChild(doc.createElement("body")); - doc.appendChild(html); - }()); - } - - // Find all the unique elements - if (doc.getElementsByTagName) - for (var i in one) - one[i] = doc.getElementsByTagName(i)[0]; - - // If we're working with a document, inject contents into - // the body element - var curParentNode = one.body; + var HTMLtoDOM = function (html) { + var doc = document, + newDoc = doc.createDocumentFragment(), + // There can be only one of these elements + one = makeMap("html,head,body,title"), + // Enforce a structure for the document + structure = { + link: "head", + base: "head" + }, + elems = [newDoc], + curParentNode = newDoc; HTMLParser(html, { start: function (tagName, attrs, unary) { - // If it's a pre-built element, then we can ignore - // its construction - if (one[tagName]) { - curParentNode = one[tagName]; - if (!unary) { - elems.push(curParentNode); + var elem = doc.createElement(tagName); + if (tagName in one) { + if (one[tagName] !== true) { + return; } - return; + one[tagName] = elem; //remember important tags } - var elem = doc.createElement(tagName); - for (var attr in attrs) elem.setAttribute(attrs[attr].name, attrs[attr].value); @@ -288,20 +264,30 @@ // Init the new parentNode curParentNode = elems[elems.length - 1]; }, - chars: function (text) { - curParentNode.appendChild(doc.createTextNode(text)); + chars: function (text) { + if (newDoc.nodeType === 11 || curParentNode !== newDoc) { //webkit throws error when trying to add text directly to a document. + curParentNode.appendChild(doc.createTextNode(text)); + } }, comment: function (text) { // create comment node curParentNode.appendChild(doc.createComment(text)); }, doctype: function (text) { - //Since we support only HTML5 we create HTML5 doctype. This won't work on IE8-. - doc.insertBefore(doc.implementation.createDocumentType('html', '', ''), doc.firstChild); + if (!newDoc.firstChild) { + newDoc = doc = document.implementation.createDocument("", "", null); //create empty document + + elems = [newDoc]; + curParentNode = newDoc; + + //Since we support only HTML5 we create HTML5 doctype. This won't work on IE8-. + newDoc.insertBefore(newDoc.implementation.createDocumentType('html', '', ''), newDoc.firstChild); + } } }); - return doc; + newDoc.normalize(); + return newDoc; }; function makeMap(str) { diff --git a/test/browser.html b/test/browser.html new file mode 100644 index 0000000..09587ee --- /dev/null +++ b/test/browser.html @@ -0,0 +1,54 @@ + + + + + + + + + diff --git a/test/test.html b/test/test.html index ac8bb8b..1e7d2ea 100644 --- a/test/test.html +++ b/test/test.html @@ -2,19 +2,25 @@ + + + -
+ < div>
+ + diff --git a/test/test.js b/test/test.js index 0e97826..0769917 100644 --- a/test/test.js +++ b/test/test.js @@ -2,11 +2,11 @@ var assert = require("assert"), fs = require('fs'), - HTMLToDOM = require('../htmlparser.js'); + HTMLtoDOM = require('../htmlparser.js'); -describe('run HTMLToDOM test', function () { +describe('run HTMLtoDOM test', function () { var html = fetch('test/test.html'), - doc = HTMLToDOM(html); + doc = HTMLtoDOM(html); it('it should have two nodes - doctype and html tag', function () { assert.equal(2, doc.childNodes.length); }); From a1d35dfd6b86bd3f97c363ce5d28df7c45cba542 Mon Sep 17 00:00:00 2001 From: Munawwar Firoz Date: Sun, 22 Feb 2015 01:26:12 +0400 Subject: [PATCH 26/61] Minor changes + comments. --- htmlparser.js | 15 +++++---------- test/test.html | 2 +- 2 files changed, 6 insertions(+), 11 deletions(-) diff --git a/htmlparser.js b/htmlparser.js index d6016c7..095fb97 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -9,13 +9,8 @@ * Original code by Erik Arvidsson, Mozilla Public License * http://erik.eae.net/simplehtmlparser/simplehtmlparser.js * - * // or to get an DOM Document + * // To get a DocumentFragment. If doctype is defined then it returns a Document. * HTMLtoDOM(htmlString); - * - * // or to inject into an existing document/DOM node - * HTMLtoDOM(htmlString, document); - * HTMLtoDOM(htmlString, document.body); - * */ (function (root, factory) { if (typeof define === 'function' && define.amd) { @@ -96,7 +91,7 @@ if (match) { html = html.substring(match[0].length); - match[0].replace(endTag, parseEndTag); + parseEndTag.apply(this, match); chars = false; } @@ -122,12 +117,12 @@ chars = false; } // start tag - } else if (html.charAt(0) === "<") { + } else if (html[0] === "<") { match = html.match(startTag); if (match) { html = html.substring(match[0].length); - match[0].replace(startTag, parseStartTag); + parseStartTag.apply(this, match); chars = false; } else { //ignore the angle bracket html = html.substring(1); @@ -244,7 +239,7 @@ one[tagName] = elem; //remember important tags } - for (var attr in attrs) + for (var attr = 0; attr < attrs.length; attr += 1) elem.setAttribute(attrs[attr].name, attrs[attr].value); if (structure[tagName] && typeof one[structure[tagName]] !== "boolean") diff --git a/test/test.html b/test/test.html index 1e7d2ea..270b2b5 100644 --- a/test/test.html +++ b/test/test.html @@ -4,7 +4,7 @@ - +

", + "expected": [ + { + "event": "opentag", + "data": [ + "p" + ] + }, + { + "event": "opentag", + "data": [ + "script", + ["type", "text/template"] + ] + }, + { + "event": "text", + "data": [ + "

Heading1

" + ] + }, + { + "event": "closetag", + "data": [ + "script" + ] + }, + { + "event": "closetag", + "data": [ + "p" + ] + } + ] +} diff --git a/test/test.js b/test/test.js index 28d50ee..bf7c611 100644 --- a/test/test.js +++ b/test/test.js @@ -23,6 +23,10 @@ describe('run element and text test - 01-simple.json', function () { runJSONTest('test/01-simple.json'); }); +describe('run script templates test - 02-template.json', function () { + runJSONTest('test/02-template.json'); +}); + describe('run CDATA test - 04-cdata.json', function () { runJSONTest('test/04-cdata.json'); }); @@ -65,10 +69,10 @@ function runJSONTest(filePath) { }); exp.data.slice(1).forEach(function (expAttr, index) { - it('attribute number ' + (index + 1) +'\'s name should be ' + expAttr[0], function () { + it('attribute number ' + (index + 1) + '\'s name should be ' + expAttr[0], function () { assert.equal(expAttr[0], attrs[index].name); }); - it('attribute number ' + (index + 1) +'\'s value should be ' + expAttr[1], function () { + it('attribute number ' + (index + 1) + '\'s value should be ' + expAttr[1], function () { assert.equal(expAttr[1], attrs[index].value); }); }); From 2e60c6e857f91fdb43b67e179b4e9057f4a1ecea Mon Sep 17 00:00:00 2001 From: Munawwar Date: Sat, 1 Aug 2015 21:06:43 +0400 Subject: [PATCH 58/61] New tests. No spacing between attributes is valid now. Implicitly values for certain attributes like "disabled" is no longer needed in HTML5. --- htmlparser.js | 9 ++---- test/05-cdata-special.json | 28 ++++++++++++++++++ test/06-leading-lt.json | 16 ++++++++++ test/08-implicit-close-tags.json | 50 ++++++++++++++++++++++++++++++++ test/09-attributes.json | 32 ++++++++++++++++++++ test/test.js | 12 ++++++++ 6 files changed, 141 insertions(+), 6 deletions(-) create mode 100644 test/05-cdata-special.json create mode 100644 test/06-leading-lt.json create mode 100644 test/08-implicit-close-tags.json create mode 100644 test/09-attributes.json diff --git a/htmlparser.js b/htmlparser.js index 7d9435d..379df57 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -27,10 +27,10 @@ var HTMLParser = (function () { // Regular Expressions for parsing tags and attributes - var startTag = /^<([-\w:]+)((?:\s+[^\s\/>"'=]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, + var startTag = /^<([-\w:]+)((?:\s*[^\s\/>"'=]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, endTag = /^<\/([-\w:]+)[^>]*>/, cdataTag = /^/i, - attr = /^\s+([^\s\/>"'=]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/; + attr = /^\s*([^\s\/>"'=]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/; // Empty Elements - HTML 5 var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"), @@ -45,9 +45,6 @@ // (and which close themselves) closeSelf = makeMap("colgroup,dd,dt,li,options,p,td,tfoot,th,thead,tr"), - // Attributes that have their values filled in disabled="disabled" - fillAttrs = makeMap("checked,compact,declare,defer,disabled,ismap,multiple,nohref,noresize,noshade,nowrap,readonly,selected"), - // Special Elements (can contain anything) special = { script: /^([\s\S]*?)<\/script[^>]*>/i, @@ -183,7 +180,7 @@ rest = rest.substr(match[0].length); name = match[1]; - value = match[2] || match[3] || match[4] || (fillAttrs[name] ? name : ""); + value = match[2] || match[3] || match[4] || ''; attrs.push({ name: name, diff --git a/test/05-cdata-special.json b/test/05-cdata-special.json new file mode 100644 index 0000000..1022e12 --- /dev/null +++ b/test/05-cdata-special.json @@ -0,0 +1,28 @@ +{ + "name": "CDATA (inside special)", + "options": { + "handler": {}, + "parser": {} + }, + "html": "", + "expected": [ + { + "event": "opentag", + "data": [ + "script" + ] + }, + { + "event": "text", + "data": [ + "/*<> fo/*]]>*/" + ] + }, + { + "event": "closetag", + "data": [ + "script" + ] + } + ] +} diff --git a/test/06-leading-lt.json b/test/06-leading-lt.json new file mode 100644 index 0000000..ac0b521 --- /dev/null +++ b/test/06-leading-lt.json @@ -0,0 +1,16 @@ +{ + "name": "leading lt", + "options": { + "handler": {}, + "parser": {} + }, + "html": ">a>", + "expected": [ + { + "event": "text", + "data": [ + ">a>" + ] + } + ] +} diff --git a/test/08-implicit-close-tags.json b/test/08-implicit-close-tags.json new file mode 100644 index 0000000..f52a050 --- /dev/null +++ b/test/08-implicit-close-tags.json @@ -0,0 +1,50 @@ +{ + "name": "Implicit close tags", + "options": {}, + "html": "
  1. TH

    Heading

    Div
    Div2
  2. Heading 2

Para

Heading 4

", + "expected": [ + { "event": "opentag", "data": [ "ol" ] }, + { "event": "opentag", "data": [ "li", [ "class", "test" ] ] }, + { "event": "opentag", "data": [ "div" ] }, + { "event": "opentag", "data": [ "table", [ "style", "width:100%" ] ] }, + { "event": "opentag", "data": [ "tr" ] }, + { "event": "opentag", "data": [ "th" ] }, + { "event": "text", "data": [ "TH" ] }, + { "event": "closetag", "data": [ "th" ] }, + { "event": "opentag", "data": [ "td", [ "colspan", "2" ] ] }, + { "event": "opentag", "data": [ "h3" ] }, + { "event": "text", "data": [ "Heading" ] }, + { "event": "closetag", "data": [ "h3" ] }, + { "event": "closetag", "data": [ "td" ] }, + { "event": "closetag", "data": [ "tr" ] }, + { "event": "opentag", "data": [ "tr" ] }, + { "event": "opentag", "data": [ "td" ] }, + { "event": "opentag", "data": [ "div" ] }, + { "event": "text", "data": [ "Div" ] }, + { "event": "closetag", "data": [ "div" ] }, + { "event": "closetag", "data": [ "td" ] }, + { "event": "opentag", "data": [ "td" ] }, + { "event": "opentag", "data": [ "div" ] }, + { "event": "text", "data": [ "Div2" ] }, + { "event": "closetag", "data": [ "div" ] }, + { "event": "closetag", "data": [ "td" ] }, + { "event": "closetag", "data": [ "tr" ] }, + { "event": "closetag", "data": [ "table" ] }, + { "event": "closetag", "data": [ "div" ] }, + { "event": "closetag", "data": [ "li" ] }, + { "event": "opentag", "data": [ "li" ] }, + { "event": "opentag", "data": [ "div" ] }, + { "event": "opentag", "data": [ "h3" ] }, + { "event": "text", "data": [ "Heading 2" ] }, + { "event": "closetag", "data": [ "h3" ] }, + { "event": "closetag", "data": [ "div" ] }, + { "event": "closetag", "data": [ "li" ] }, + { "event": "closetag", "data": [ "ol" ] }, + { "event": "opentag", "data": [ "p" ] }, + { "event": "text", "data": [ "Para" ] }, + { "event": "closetag", "data": [ "p" ] }, + { "event": "opentag", "data": [ "h4" ] }, + { "event": "text", "data": [ "Heading 4" ] }, + { "event": "closetag", "data": [ "h4" ] } + ] +} diff --git a/test/09-attributes.json b/test/09-attributes.json new file mode 100644 index 0000000..0824b62 --- /dev/null +++ b/test/09-attributes.json @@ -0,0 +1,32 @@ +{ + "name": "attributes (no white space, no value, no quotes)", + "options": { + "handler": {}, + "parser": {} + }, + "html": "", + "expected": [ + { + "event": "opentag", + "data": [ + "button", + ["class", "test0"], + ["title", "test1"], + ["disabled", ""], + ["value", "test2"] + ] + }, + { + "event": "text", + "data": [ + "adsf" + ] + }, + { + "event": "closetag", + "data": [ + "button" + ] + } + ] +} diff --git a/test/test.js b/test/test.js index bf7c611..9fdd57b 100644 --- a/test/test.js +++ b/test/test.js @@ -31,10 +31,22 @@ describe('run CDATA test - 04-cdata.json', function () { runJSONTest('test/04-cdata.json'); }); +describe('run script templates test - 05-cdata-special.json', function () { + runJSONTest('test/05-cdata-special.json'); +}); + +describe('run script templates test - 06-leading-lt.json', function () { + runJSONTest('test/06-leading-lt.json'); +}); + describe('run self-closing tag test - 07-self-closing.json', function () { runJSONTest('test/07-self-closing.json'); }); +describe('run self-closing tag test - 09-attributes.json', function () { + runJSONTest('test/09-attributes.json'); +}); + describe('run isolated less than angle bracket - 15-lt-whitespace.json', function () { runJSONTest('test/15-lt-whitespace.json'); }); From 292a0247df57fa158a422d3885eab2a290652c65 Mon Sep 17 00:00:00 2001 From: Munawwar Date: Tue, 6 Oct 2015 01:09:49 +0400 Subject: [PATCH 59/61] Update benchmark.js --- benchmark.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/benchmark.js b/benchmark.js index 7bba88b..954ffdc 100644 --- a/benchmark.js +++ b/benchmark.js @@ -1,5 +1,5 @@ var benchmark = require('htmlparser-benchmark'); -var HTMLtoDOM = require("./htmlparser.js"); +var HTMLtoDOM = require("./htmlparser.js")(); var bench = benchmark(function (html, callback) { var noop = function () {}; From 876120d833e63027a8177732789843c83fcfc338 Mon Sep 17 00:00:00 2001 From: Munawwar Date: Thu, 8 Oct 2015 14:23:31 +0400 Subject: [PATCH 60/61] Revert allowing no spaces between attributes. The regex goes into a seemigly infinite loop for specific badly constructed attrbiutes. Try this ().match(/^<([-\w:]+)((?:\s*[^\s\/>"'=]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/) and watch the browser/node going into infinite loop. If you remove the href link, then it returns. Not still sure why. --- htmlparser.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/htmlparser.js b/htmlparser.js index 379df57..41ff3d4 100644 --- a/htmlparser.js +++ b/htmlparser.js @@ -27,10 +27,10 @@ var HTMLParser = (function () { // Regular Expressions for parsing tags and attributes - var startTag = /^<([-\w:]+)((?:\s*[^\s\/>"'=]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, + var startTag = /^<([-\w:]+)((?:\s+[^\s\/>"'=]+(?:\s*=\s*(?:(?:"[^"]*")|(?:'[^']*')|[^>\s]+))?)*)\s*(\/?)\s*>/, endTag = /^<\/([-\w:]+)[^>]*>/, cdataTag = /^/i, - attr = /^\s*([^\s\/>"'=]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/; + attr = /^\s+([^\s\/>"'=]+)(?:\s*=\s*(?:(?:"((?:\\.|[^"])*)")|(?:'((?:\\.|[^'])*)')|([^>\s]+)))?/; // Empty Elements - HTML 5 var empty = makeMap("area,base,basefont,br,col,frame,hr,img,input,link,meta,param,embed,command,keygen,source,track,wbr"), From 1ab1bbcab4d31f3335094538d8a69af0a9d59f94 Mon Sep 17 00:00:00 2001 From: Ger Hobbelt Date: Sun, 11 Sep 2016 22:55:21 +0200 Subject: [PATCH 61/61] added htmlparser-benchmark to the packages: this is required to make ./benchmark.js run. --- .gitignore | 5 +++++ package.json | 58 ++++++++++++++++++++++++++-------------------------- 2 files changed, 34 insertions(+), 29 deletions(-) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..13203c3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ + +node_modules/ + +*.bak +*~ diff --git a/package.json b/package.json index ec3564f..0c157a5 100644 --- a/package.json +++ b/package.json @@ -1,31 +1,31 @@ { - "name": "neutron-html5parser", - "author": "munawwar", - "description": "Small Pure-JS HTML5 Parser", - "version": "0.2.0", - "main": "htmlparser.js", - "dependencies": { - }, - "devDependencies": { - "jsdom": "3.1.2", - "mocha": "2.2.5" - }, - "directories": { - "test": "test" - }, - "scripts": { - "test": "make test" - }, - "repository": { - "type": "git", - "url": "https://github.com/Munawwar/neutron-html5parser" - }, - "keywords": [ - "html parser" - ], - "license": "MIT", - "bugs": { - "url": "https://github.com/Munawwar/neutron-html5parser/issues" - }, - "homepage": "https://github.com/Munawwar/neutron-html5parser" + "name": "neutron-html5parser", + "author": "munawwar", + "description": "Small Pure-JS HTML5 Parser", + "version": "0.2.0", + "main": "htmlparser.js", + "dependencies": {}, + "devDependencies": { + "htmlparser-benchmark": "^1.1.3", + "jsdom": "9.5.0", + "mocha": "3.0.2" + }, + "directories": { + "test": "test" + }, + "scripts": { + "test": "make test" + }, + "repository": { + "type": "git", + "url": "https://github.com/Munawwar/neutron-html5parser" + }, + "keywords": [ + "html parser" + ], + "license": "MIT", + "bugs": { + "url": "https://github.com/Munawwar/neutron-html5parser/issues" + }, + "homepage": "https://github.com/Munawwar/neutron-html5parser" }