Skip to content
This repository was archived by the owner on Mar 28, 2019. It is now read-only.
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions src/DOMBuilder.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ module.exports = (function(){
popElement: function() {
this.currentNode = this.currentNode.parentNode;
},
popElements: function(n) {
while (n>0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since we're terminating on n=0, we can say while(n--) here, so that we don't need the decrement in the body.

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({
Expand Down
64 changes: 61 additions & 3 deletions src/HTMLParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -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: {
Expand All @@ -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.
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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),
Expand All @@ -499,6 +556,7 @@ module.exports = (function(){
}
}
}
*/
return;
}

Expand Down
54 changes: 54 additions & 0 deletions test/test-slowparse.js
Original file line number Diff line number Diff line change
Expand Up @@ -597,6 +597,60 @@ module.exports = function(Slowparse, window, document, validators) {
ok(!result.error, "no error on omitted </p>");
});

test("parsing elements with optional close tags: <select><optgroup label='X'><option>V<option>S<optgroup label='Z'><option>M<option>A</select>", function() {
var html = '<select><optgroup label="X"><option>V<option>S<optgroup label="Z"><option>M<option>A</select>';
var result = parse(html);
ok(!result.error, "no error on omitted </option> and </optgroup>");
});

test("parsing elements with optional close tags: <datalist><option>a<option>b</datalist>", function() {
var html = '<datalist><option>a<option>b</datalist>';
var result = parse(html);
ok(!result.error, "no error on omitted </option>");
});

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

given the large number of entries in addition to option you added, it's important to add test cases for those, too.

test("parsing elements with optional close tags: <table><tr><td></td><tr><td></td></tr></table>", function() {
var html = '<table><tr><td></td><tr><td></td></tr></table>';
var result = parse(html);
ok(!result.error, "no error on omitted </tr> before <tr>");
});

test("parsing elements with optional close tags: <table><tr><td></td><tr><td></td></table>", function() {
var html = '<table><tr><td></td><tr><td></td></table>';
var result = parse(html);
ok(!result.error, "no error on omitted </tr>");
});

test("parsing elements with optional close tags: <table><tr><td><tr><td></table>", function() {
var html = '<table><tr><td><tr><td></table>';
var result = parse(html);
ok(!result.error, "no error on omitted </tr> and </td>");
});

test("parsing elements with optional close tags: <table><thead><tr><td><tbody><tr><td><tr><td><tfoot></table>", function() {
var html = '<table><thead><tr><td><tbody><tr><td><tr><td><tfoot></table>';
var result = parse(html);
ok(!result.error, "no error on omitted </tr> and </td>");
});

test("parsing elements with optional close tags: <dl><dt>Coffee<dd>Black hot drink<dt>Milk<dd>White cold drink</dl>", function() {
var html = '<dl><dt>Coffee<dd>Black hot drink<dt>Milk<dd>White cold drink</dl>';
var result = parse(html);
ok(!result.error, "no error on omitted </dt> and </dd>");
});

test("parsing elements with optional close tags: <ruby>漢<rp>(<rt>Kan<rp>)</rp>字<rp>(<rt>ji<rp>)</ruby>", function() {
var html = '<ruby>漢<rp>(<rt>Kan<rp>)</rp>字<rp>(<rt>ji<rp>)</ruby>';
var result = parse(html);
ok(!result.error, "no error on omitted </rp> and </rt>");
});

test("parsing elements with optional close tags: <ruby><rb>10<rb>31<rb>2002<rtc><rt>Month<rt>Day<rt>Year<rtc><rt>Expiration Date</ruby>", function() {
var html = '<ruby><rb>10<rb>31<rb>2002<rtc><rt>Month<rt>Day<rt>Year<rtc><rt>Expiration Date</ruby>';
var result = parse(html);
ok(!result.error, "no error on omitted </rb> , </rt> ,and </rtc>");
});

test("intentional fail for optional close tag (incorrect use). pass = not accepted", function() {
var html = '<div><p>text\n<a>more text</a></div>';
var result = parse(html);
Expand Down