Skip to content
This repository has been archived by the owner on Mar 28, 2019. It is now read-only.

Autoclose tags #89

Closed
wants to merge 2 commits into from
Closed
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
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
},
"scripts": {
"build": "browserify ./src/index.js -o slowparse.js --standalone Slowparse",
"test": "node test.js",
"dev": "npm run build && npm run test"
"debug": "npm run build && node inspect test.js",
"test": "npm run build && node test.js"
},
"repository": {
"type": "git",
Expand Down
65 changes: 61 additions & 4 deletions slowparse.js
Original file line number Diff line number Diff line change
Expand Up @@ -972,9 +972,21 @@ module.exports = (function(){
start: token.interval.start
};
var openTagName = this.domBuilder.currentNode.nodeName.toLowerCase();
if (closeTagName != openTagName)
throw new ParseError("MISMATCHED_CLOSE_TAG", this, openTagName,
closeTagName, token);
if (closeTagName != openTagName) {
// Are we dealing with a rogue </ here?
if (closeTagName === "") {
throw new ParseError("MISSING_CLOSING_TAG_NAME", token, openTagName, this.domBuilder.currentNode.closeWarnings);
}

// Are we dealing with a tag that is closed in the source,
// even though based on DOM parsing it already got closed?
if (this.domBuilder.currentNode.closeWarnings) {
throw new ParseError("MISMATCHED_CLOSE_TAG_DUE_TO_EARLIER_AUTO_CLOSING", this, closeTagName, token);
}

// This is just a regular old mismatched closing tag.
throw new ParseError("MISMATCHED_CLOSE_TAG", this, openTagName, closeTagName, token);
}
this._parseEndCloseTag();
}

Expand All @@ -999,12 +1011,25 @@ module.exports = (function(){
var activeTagName = activeTagNode.nodeName.toLowerCase();
if(this._knownOmittableCloseTags(activeTagName, tagName)) {
this.domBuilder.popElement();

if (!this.domBuilder.currentNode.closeWarnings) {
this.domBuilder.currentNode.closeWarnings = [];
}

var childNodes = this.domBuilder.currentNode.childNodes,
position = childNodes.length - 1;

this.domBuilder.currentNode.closeWarnings.push({
tagName: activeTagName,
position: position,
parseInfo: childNodes[position].parseInfo
});
}
}
// Store currentNode as the parentTagNode
parentTagNode = this.domBuilder.currentNode;
this.domBuilder.pushElement(tagName, parseInfo, nameSpace);

this.domBuilder.pushElement(tagName, parseInfo, nameSpace);
if (!this.stream.end())
this._parseEndOpenTag(tagName);
}
Expand Down Expand Up @@ -1459,6 +1484,23 @@ module.exports = (function() {
cursor: openTag.start
};
},
MISSING_CLOSING_TAG_NAME: function(token, openTagName, autocloseWarnings) {
var openTag = this._combine({
name: openTagName
}, token.interval);

if (autocloseWarnings) {
var tag = autocloseWarnings[0];
openTag = this._combine({
name: tag.tagName
}, tag.parseInfo.openTag);
}

return {
openTag: openTag,
cursor: token.interval.start
};
},
UNEXPECTED_CLOSE_TAG: function(parser, closeTagName, token) {
var closeTag = this._combine({
name: closeTagName
Expand All @@ -1468,6 +1510,21 @@ module.exports = (function() {
cursor: closeTag.start
};
},
MISMATCHED_CLOSE_TAG_DUE_TO_EARLIER_AUTO_CLOSING: function(parser, closeTagName, token) {
var warnings = parser.domBuilder.currentNode.closeWarnings,
tag = warnings[0],
openTag = this._combine({
name: tag.tagName
}, tag.parseInfo.openTag),
closeTag = this._combine({
name: closeTagName
}, token.interval);
return {
openTag: openTag,
closeTag: closeTag,
cursor: closeTag.start
};
},
MISMATCHED_CLOSE_TAG: function(parser, openTagName, closeTagName, token) {
var openTag = this._combine({
name: openTagName
Expand Down
33 changes: 29 additions & 4 deletions src/HTMLParser.js
Original file line number Diff line number Diff line change
Expand Up @@ -314,9 +314,21 @@ module.exports = (function(){
start: token.interval.start
};
var openTagName = this.domBuilder.currentNode.nodeName.toLowerCase();
if (closeTagName != openTagName)
throw new ParseError("MISMATCHED_CLOSE_TAG", this, openTagName,
closeTagName, token);
if (closeTagName != openTagName) {
// Are we dealing with a rogue </ here?
if (closeTagName === "") {
throw new ParseError("MISSING_CLOSING_TAG_NAME", token, openTagName, this.domBuilder.currentNode.closeWarnings);
}

// Are we dealing with a tag that is closed in the source,
// even though based on DOM parsing it already got closed?
if (this.domBuilder.currentNode.closeWarnings) {
throw new ParseError("MISMATCHED_CLOSE_TAG_DUE_TO_EARLIER_AUTO_CLOSING", this, closeTagName, token);
}

// This is just a regular old mismatched closing tag.
throw new ParseError("MISMATCHED_CLOSE_TAG", this, openTagName, closeTagName, token);
}
this._parseEndCloseTag();
}

Expand All @@ -341,12 +353,25 @@ module.exports = (function(){
var activeTagName = activeTagNode.nodeName.toLowerCase();
if(this._knownOmittableCloseTags(activeTagName, tagName)) {
this.domBuilder.popElement();

if (!this.domBuilder.currentNode.closeWarnings) {
this.domBuilder.currentNode.closeWarnings = [];
}

var childNodes = this.domBuilder.currentNode.childNodes,
position = childNodes.length - 1;

this.domBuilder.currentNode.closeWarnings.push({
tagName: activeTagName,
position: position,
parseInfo: childNodes[position].parseInfo
});
}
}
// Store currentNode as the parentTagNode
parentTagNode = this.domBuilder.currentNode;
this.domBuilder.pushElement(tagName, parseInfo, nameSpace);

this.domBuilder.pushElement(tagName, parseInfo, nameSpace);
if (!this.stream.end())
this._parseEndOpenTag(tagName);
}
Expand Down
32 changes: 32 additions & 0 deletions src/ParseErrorBuilders.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,23 @@ module.exports = (function() {
cursor: openTag.start
};
},
MISSING_CLOSING_TAG_NAME: function(token, openTagName, autocloseWarnings) {
var openTag = this._combine({
name: openTagName
}, token.interval);

if (autocloseWarnings) {
var tag = autocloseWarnings[0];
openTag = this._combine({
name: tag.tagName
}, tag.parseInfo.openTag);
}

return {
openTag: openTag,
cursor: token.interval.start
};
},
UNEXPECTED_CLOSE_TAG: function(parser, closeTagName, token) {
var closeTag = this._combine({
name: closeTagName
Expand All @@ -51,6 +68,21 @@ module.exports = (function() {
cursor: closeTag.start
};
},
MISMATCHED_CLOSE_TAG_DUE_TO_EARLIER_AUTO_CLOSING: function(parser, closeTagName, token) {
var warnings = parser.domBuilder.currentNode.closeWarnings,
tag = warnings[0],
openTag = this._combine({
name: tag.tagName
}, tag.parseInfo.openTag),
closeTag = this._combine({
name: closeTagName
}, token.interval);
return {
openTag: openTag,
closeTag: closeTag,
cursor: closeTag.start
};
},
MISMATCHED_CLOSE_TAG: function(parser, openTagName, closeTagName, token) {
var openTag = this._combine({
name: openTagName
Expand Down
9 changes: 7 additions & 2 deletions test.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,12 @@ var Slowparse = require("./slowparse.js"),
validators = require("./test/node/qunit-shim.js")(Slowparse, JSDOM);

console.log("Testing Slowparse library:");
var failureCount = require("./test/test-slowparse.js")(Slowparse, window, document, validators);
if (failureCount > 0) { console.log(failureCount + " tests failed."); }

var testRunner = require("./test/test-slowparse.js");
var failureCount = testRunner(Slowparse, window, document, validators);

if (failureCount > 0) {
console.log(`${failureCount} test${failureCount>1 ? 's' : ''} failed.`);
}

process.exit(failureCount);
32 changes: 32 additions & 0 deletions test/test-slowparse.js
Original file line number Diff line number Diff line change
Expand Up @@ -809,6 +809,38 @@ module.exports = function(Slowparse, window, document, validators) {
});


test("correctly flag the opening tag for an auto-closed closing tag", function () {
var html = '<body><p><h1><a href="">test</a></h1></p></body>';
var result = parse(html);
equal(result.error, {
type: 'MISMATCHED_CLOSE_TAG_DUE_TO_EARLIER_AUTO_CLOSING',
openTag: { name: 'p', start: 6, end: 9 },
closeTag: { name: 'p', start: 37, end: 40 },
cursor: 37
});
});

test("correctly flag the opening tag in the source for a block closer with auto-closed flow element", function () {
var html = '<body><p><h1>lol</h1></h2></p></body>';
var result = parse(html);
equal(result.error, {
type: 'MISMATCHED_CLOSE_TAG_DUE_TO_EARLIER_AUTO_CLOSING',
openTag: { name: 'p', start: 6, end: 9 },
closeTag: { name: 'h2', start: 21, end: 25 },
cursor: 21
});
});

test("testing </ and auto-closed tags", function () {
var html = '<body><div><p><h1>lol</h1></</div></body>';
var result = parse(html);
equal(result.error, {
type: 'MISSING_CLOSING_TAG_NAME',
openTag: { name: 'p', start: 11, end: 14 },
cursor: 26
});
});

// specifically CSS testing

test("parsing empty CSS document", function() {
Expand Down