The html can be passed as a string or as an array containing html strings. It is based on the work of Jonathan Aquino that has an online form that has a similar script implemented: http://jona.ca/blog/unclosed-tag-finder
The script run both in NodeJS and in the browser console. I added also a function that allow to test the html coming from a server using the URL.
The usage is:
凸.checkHtml("your html here")
or
凸.checkHtml(["your html here", "and here"])
If you want to check a page served by a server:
凸.buildGetPageFunction()('http://example.com', function(data){console.log(凸.checkHtml(data))})
These commands are available in the console after you copy and paste the snipped there.
The code can run also server side with
$ node closed-tag-testing.js
Be aware that in the console you can only check pages that belong to the same domain where you are in. If you try
凸.buildGetPageFunction()('http://google.com', function(data){console.log(凸.checkHtml(data))})
While being in http://example.com you will get the error:
XMLHttpRequest cannot load http://google.com/. It has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. Origin 'http://example.com' is therefore not allowed access.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// See https://coding-and-design.blogspot.de/2016/11/are-all-element-tags-closed-testing-it.html for more info | |
var 凸 = typeof global === 'undefined' ? 凸 : global.tmp; | |
var 凸 = (function (凸) { | |
凸.buildGetPageFunction = function buildGetPageFunction() { | |
if (typeof global === 'undefined') { | |
// Browser | |
return function (url, callback) { | |
var xhr = new XMLHttpRequest(); | |
xhr.onreadystatechange = function () { | |
if (xhr.readyState === XMLHttpRequest.DONE) { | |
return callback(xhr.responseText); | |
} | |
}; | |
xhr.open('GET', url, true); | |
xhr.send(null); | |
}; | |
} else { | |
// Node | |
return function (url, callback) { | |
var request = require("request"); | |
request({ url: url }, function (err, response, responseText) { | |
if (!err && response.statusCode === 200) { | |
return callback(responseText); | |
} else { | |
throw err; | |
} | |
}); | |
}; | |
} | |
}; | |
return 凸; | |
}(凸 || {})); | |
var 凸 = (function (凸) { | |
function isSelfClosingTag(tagName) { | |
return tagName.match(/area|base|br|col|embed|hr|img|input|keygen|link|menuitem|meta|param|source|track|wbr|script/i); | |
} | |
function displayLine(line, array) { | |
return ('Line #' + line + ': ' + array[line - 1]); | |
} | |
凸.checkHtml = function checkHtml(p) { | |
// Based on http://jona.ca/blog/unclosed-tag-finder | |
var | |
array1 = p.constructor === Array ? p : p.split('\n'), | |
tags = [], | |
tag, | |
openTags = [], | |
openTag, | |
i, | |
j, | |
line, | |
array2, | |
matches, | |
closingTag; | |
for (i = 0; i < array1.length; i++) { | |
line = array1[i]; | |
array2 = line.match(/<[^>]*[^/]>/g) || []; | |
for (j = 0; j < array2.length; j++) { | |
tag = array2[j]; | |
matches = tag.match(/<\/?([a-z0-9]+)/i); | |
if (matches) { | |
tags.push({ tag: tag, name: matches[1], line: i + 1, closing: tag[1] === '/' }); | |
} | |
} | |
} | |
if (tags.length === 0) { | |
return 'No tags found.'; | |
} | |
for (i = 0; i < tags.length; i++) { | |
tag = tags[i]; | |
if (tag.closing) { | |
closingTag = tag; | |
if (isSelfClosingTag(closingTag.name)) { | |
continue; | |
} | |
if (openTags.length === 0) { | |
return [ | |
'Closing tag ' + closingTag.tag + ' on line ' + closingTag.line + ' does not have corresponding open tag.', | |
displayLine(closingTag.line, array1), | |
].join('\n'); | |
} | |
openTag = openTags[openTags.length - 1]; | |
if (closingTag.name !== openTag.name) { | |
return [ | |
'Closing tag ' + closingTag.tag + ' on line ' + closingTag.line + ' does not match open tag ' + openTag.tag + ' on line ' + openTag.line + '.', | |
displayLine(openTag.line, array1), | |
displayLine(closingTag.line, array1) | |
].join('\n'); | |
} else { | |
openTags.pop(); | |
} | |
} else { | |
openTag = tag; | |
if (isSelfClosingTag(openTag.name)) { | |
continue; | |
} | |
openTags.push(openTag); | |
} | |
} | |
if (openTags.length > 0) { | |
openTag = openTags[openTags.length - 1]; | |
return [ | |
'Open tag ' + openTag.tag + ' on line ' + openTag.line + ' does not have a corresponding closing tag.', | |
displayLine(openTag.line, array1), | |
].join('\n'); | |
} | |
return 'Success: No unclosed tags found.'; | |
}; | |
return 凸; | |
}(凸 || {})); | |
(function () { | |
var url = (typeof global === 'undefined') ? document.location.href : "http://example.com"; | |
凸.buildGetPageFunction()(url, function (page) { | |
console.log("### Checking " + url + "\n\n" + 凸.checkHtml(page) + "\n\n"); | |
}); | |
console.log("### Checking <div>\n\n" + 凸.checkHtml("<div>") + "\n\n"); | |
console.log("### Checking ['<div>']\n\n" + 凸.checkHtml(["<div>"]) + "\n\n"); | |
})(); |
No comments :
Post a Comment