diff --git a/package.json b/package.json index 7f96a40..7ac313a 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/slowparse.js b/slowparse.js index 56b109a..2499e34 100644 --- a/slowparse.js +++ b/slowparse.js @@ -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(); } @@ -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); } @@ -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 @@ -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 diff --git a/src/HTMLParser.js b/src/HTMLParser.js index a54d3dc..7f6feb8 100644 --- a/src/HTMLParser.js +++ b/src/HTMLParser.js @@ -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(); } @@ -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); } diff --git a/src/ParseErrorBuilders.js b/src/ParseErrorBuilders.js index b1c44ae..f2fd105 100644 --- a/src/ParseErrorBuilders.js +++ b/src/ParseErrorBuilders.js @@ -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 @@ -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 diff --git a/test.js b/test.js index e5c763b..32a672b 100755 --- a/test.js +++ b/test.js @@ -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); diff --git a/test/test-slowparse.js b/test/test-slowparse.js index 29733c4..a5d5401 100644 --- a/test/test-slowparse.js +++ b/test/test-slowparse.js @@ -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 = '