diff --git a/src/DOMBuilder.js b/src/DOMBuilder.js index c3fae32..12046dc 100644 --- a/src/DOMBuilder.js +++ b/src/DOMBuilder.js @@ -35,6 +35,12 @@ module.exports = (function(){ popElement: function() { this.currentNode = this.currentNode.parentNode; }, + popElements: function(n) { + while (n>0) { + this.currentNode = this.currentNode.parentNode; + n--; + } + }, // record the cursor position for a context change (text/html/css/script) pushContext: function(context, position) { this.contexts.push({ diff --git a/src/HTMLParser.js b/src/HTMLParser.js index a54d3dc..3aa2324 100644 --- a/src/HTMLParser.js +++ b/src/HTMLParser.js @@ -116,7 +116,8 @@ module.exports = (function(){ // http://www.w3.org/TR/html5/syntax.html#optional-tags // HTML elements that with omittable close tag - omittableCloseTagHtmlElements: ["p", "li", "td", "th"], + omittableCloseTagHtmlElements: ["p", "li", "thead", "tbody", "tfoot", "tr", "td", "th", + "dt", "dd", "rb", "rt", "rtc", "rp", "option", "optgroup" ], // HTML elements that paired with omittable close tag list omittableCloseTags: { @@ -126,7 +127,19 @@ module.exports = (function(){ "section", "table", "ul"], "th": ["th", "td"], "td": ["th", "td"], - "li": ["li"] + "tr": ["tr"], + "li": ["li"], + "dt": ["dt", "dd"], + "dd": ["dt", "dd"], + "rb": ["rb","rt","rtc","rp"], + "rt": ["rb","rt","rtc","rp"], + "rtc": ["rb","rtc","rp"], + "rp": ["rb","rt","rtc","rp"], + "optgroup": ["optgroup"], + "option": ["option", "optgroup"], + "thead": ["tbody", "tfoot"], + "tbody": ["tbody", "tfoot"], + "tfoot": ["tbody"] }, // We keep a list of all valid HTML5 elements. @@ -224,6 +237,38 @@ module.exports = (function(){ return this.attributeNamespaces.indexOf(ns) !== -1; }, + _isNextTagEnder: function (stream) { + var closeTag=isNextCloseTag(stream); + var currNode = this.domBuilder.currentNode ; + var n=1; + if (!closeTag) {return false;} + closeTag=closeTag.toLowerCase(); + while (currNode && currNode.nodeName) { + var tagName = currNode.nodeName.toLowerCase(); + if (closeTag == tagName) { return {'n': +n}; } + if (!this._knownOmittableCloseTagHtmlElement(tagName)) { return false;} + currNode=currNode.parentNode; + n++; + } + return false; + }, + + _popOmited: function(tag) { + var currNode = this.domBuilder.currentNode ; + var n=1; + tag=tag.toLowerCase(); + while (currNode && currNode.nodeName) { + var tagName = currNode.nodeName.toLowerCase(); + if (!this._knownOmittableCloseTagHtmlElement(tagName)) { return false;} + n++; + if (this._knownOmittableCloseTags(tagName,tag)) break; + //if (tag == tagName) { return {'n': +n}; } + currNode=currNode.parentNode; + } + this.domBuilder.popElements(n-1); + + }, + // #### The HTML Master Parse Function // // The HTML master parse function works the same as the CSS @@ -337,12 +382,16 @@ module.exports = (function(){ // If the preceding tag and the active tag is omittableCloseTag pairs, // we tell our DOM builder that we're done. + // FIXME TODO get rid of activeTagNode; add loop that can pop nested omitable FIXME TODO + this._popOmited(tagName); + /* if (activeTagNode && parentTagNode != this.domBuilder.fragment.node){ var activeTagName = activeTagNode.nodeName.toLowerCase(); if(this._knownOmittableCloseTags(activeTagName, tagName)) { this.domBuilder.popElement(); } } + */ // Store currentNode as the parentTagNode parentTagNode = this.domBuilder.currentNode; this.domBuilder.pushElement(tagName, parseInfo, nameSpace); @@ -456,6 +505,7 @@ module.exports = (function(){ // If the open tag represents a optional-omit-close-tag element, there may be // an optional closing element, so we save the currentNode into activeTag for next step check. + // FIXME TODO kill activeTagNode activeTagNode = false; if (tagName && this._knownOmittableCloseTagHtmlElement(tagName)){ activeTagNode = this.domBuilder.currentNode; @@ -486,8 +536,15 @@ module.exports = (function(){ this.domBuilder.pushContext("html", this.stream.pos); } + // FIXME TODO isNextParent is replaced by _isNextTagEnder(stream) + // also it can pop more than one element FIXME TODO // if there is no more content in the parent element, we tell DOM builder that we're done. - if(parentTagNode && parentTagNode != this.domBuilder.fragment.node) { + // this. _isNextTagEnder(this.stream); + var tagEnds=this. _isNextTagEnder(this.stream); + if (tagEnds && tagEnds.n > 1) { + this.domBuilder.popElements(tagEnds.n-1); + } + /* if(parentTagNode && parentTagNode != this.domBuilder.fragment.node) { var parentTagName = parentTagNode.nodeName.toLowerCase(), nextIsParent = isNextTagParent(this.stream, parentTagName), needsEndTag = !allowsOmmitedEndTag(parentTagName, tagName), @@ -499,6 +556,7 @@ module.exports = (function(){ } } } + */ return; } diff --git a/test/test-slowparse.js b/test/test-slowparse.js index b3b4df2..3b93ec6 100644 --- a/test/test-slowparse.js +++ b/test/test-slowparse.js @@ -597,6 +597,60 @@ module.exports = function(Slowparse, window, document, validators) { ok(!result.error, "no error on omitted

"); }); + test("parsing elements with optional close tags: ", function() { + var html = ''; + var result = parse(html); + ok(!result.error, "no error on omitted and "); + }); + + test("parsing elements with optional close tags: ", function() { + var html = ''; + var result = parse(html); + ok(!result.error, "no error on omitted "); + }); + + test("parsing elements with optional close tags:
", function() { + var html = '
'; + var result = parse(html); + ok(!result.error, "no error on omitted before "); + }); + + test("parsing elements with optional close tags:
", function() { + var html = '
'; + var result = parse(html); + ok(!result.error, "no error on omitted "); + }); + + test("parsing elements with optional close tags:
", function() { + var html = '
'; + var result = parse(html); + ok(!result.error, "no error on omitted and "); + }); + + test("parsing elements with optional close tags:
", function() { + var html = '
'; + var result = parse(html); + ok(!result.error, "no error on omitted and "); + }); + + test("parsing elements with optional close tags:
Coffee
Black hot drink
Milk
White cold drink
", function() { + var html = '
Coffee
Black hot drink
Milk
White cold drink
'; + var result = parse(html); + ok(!result.error, "no error on omitted and "); + }); + + test("parsing elements with optional close tags: (Kan)(ji)", function() { + var html = '(Kan)(ji)'; + var result = parse(html); + ok(!result.error, "no error on omitted and "); + }); + + test("parsing elements with optional close tags: 10312002MonthDayYearExpiration Date", function() { + var html = '10312002MonthDayYearExpiration Date'; + var result = parse(html); + ok(!result.error, "no error on omitted , ,and "); + }); + test("intentional fail for optional close tag (incorrect use). pass = not accepted", function() { var html = '

text\nmore text

'; var result = parse(html);