/* * tagcheck.js * タグの対応エラーをチェックし、結果ウィンドウに表示する * @author tockri * 改変、再配布、諸々自由にやってください */ (function(w) { // remove inplanted script tag (function() { var scriptTag = document.body.lastChild; if (scriptTag.tagName == 'SCRIPT' && scriptTag.id == 'tagcheck-script') { document.body.removeChild(scriptTag); } })(); if (!w) { return; } // prepare result window (function() { var wd = w.document; wd.open(); var resultHTML = [ '', '', 'tagcheck result', '', '', '', '
now computing...', '
', '
', '

チェック結果概要

', '
', '

タグ対応エラー一覧

', '
', '

HTMLソース

', '
', '
', '
', '', '' ]; wd.write(resultHTML.join("\n")); wd.close(); var $ = function(id) { return w.document.getElementById(id); } var pos = function(elem) { var html = document.documentElement; var rect = elem.getBoundingClientRect(); var left = rect.left - html.clientLeft; var top = rect.top - html.clientTop; return {left:left, top:top}; } w.showResult = function(html, closed, errors) { // hide loading message var ldiv = $('loading'); ldiv.parentNode.removeChild(ldiv); //ldiv.className = 'none'; var errorCount = errors.length; // show summary (function() { var sdiv = $('summary'); var message = ( errorCount == 0 ? '素晴らしい!とりあえずタグの対応だけは完璧です!' : errorCount == 1 ? '惜しい!1個だけきちんと対応していないタグがあります。' : '残念、' + errorCount + '個のタグがきちんと対応していません。' ); sdiv.innerHTML = message; sdiv.className = errorCount ? 'fail' : 'success'; })(); // show sourcecode (function() { var sourceLine = 1; // make source code html var re = function(htmlCode) { return htmlCode.replace(/[<>&\r\n \t]/g, function(c) { switch(c) { case '<': return '<'; case '>': return '>'; case '&': return '&'; case "\r": return ''; case "\n": var cls = sourceLine % 2 == 0 ? 'e' : 'o'; return '\n
' + (++sourceLine) + '
 '; case "\t": return "    "; case " ": return " "; } }); } var sourceCode = ['
1
 ']; errors.sort(function(a, b) { return a.head - b.head; }); var rular = 0; for (var i = 0, l = errors.length; i < l; i++) { var uc = errors[i]; if (rular < uc.tail) { var head = re(html.substring(rular, uc.head)); var tag = re(html.substring(uc.head, uc.tail)); sourceCode.push(head, '', '', tag, ''); uc.lineNumber = sourceLine; rular = uc.tail; } } sourceCode.push(re(html.substring(rular)), '
'); var div = $('source'); div.innerHTML = sourceCode.join(""); })(); // show list (function() { var listHTML = ['
    ']; for (var i = 0, l = errors.length; i < l; i++) { var uc = errors[i]; listHTML.push('
  1. ', '(' + uc.lineNumber + '行目) ', '<', uc.tagName + uc.attr, '> : ', uc.message, '
  2. '); } listHTML.push('
'); $('list').innerHTML = listHTML.join(""); })(); $('resultarea').className = 'show'; //$('debug').innerHTML = closed.toSource(); }; w.go = function(id) { var before = w.scrollY || 0; var elem = $(id); elem.focus(); var after = w.scrollY || 0; var wh = w.innerHeight; var top = pos(elem).top; var M = 50; if (top < M) { w.scrollBy(0, -wh / 2); } else if (wh - M < top) { w.scrollBy(0, wh / 2); } }; return w; })(); // Get html code by re-request var html = (function() { var ajax = (function() { try { return window.XMLHttpRequest ? new XMLHttpRequest() : (ActiveXObject ? new ActiveXObject('Msxml2.XMLHTTP') : null); } catch (e) { return new ActiveXObject('Microsoft.XMLHTTP'); } })(); ajax.open("GET", location.href, false); ajax.send(''); return ajax.responseText; })(); var opened = {}; var closed = {}; var errors = []; var debug = []; var ignoring = []; // そもそも空要素のタグ var EMPTYTAG = ['img', 'link', 'meta', 'br', 'hr', 'input', 'embed', 'area', 'base', 'basefont', 'bgsound', 'param', 'wbr', 'col']; EMPTYTAG.indexOf = EMPTYTAG.indexOf || function(str) { for (var i = 0, l = this.length; i < l; i++) { if (this[i] == str) { return i; } } return -1; }; // 無視ゾーンを検索する (function() { var ignorePattern = /(]*>[\s\S]*?<\/script>)|(<\!--[\s\S]*?-->)/igm; var found = null; while (found = ignorePattern.exec(html)) { var head = found.index; var tail = head + found[0].length; ignoring.push({ head: head, tail: tail }); ignorePattern.lastIndex = tail; //console.debug(found[0]); } })(); function inIgnoring(index) { for (var i = 0; i < ignoring.length; i++) { var ig = ignoring[i]; if (ig.head <= index && index < ig.tail) { return true; } else if (index < ig.head) { break; } } return false; } // 開いたまま閉じていないタグを検索する (function() { // 閉じタグの開始位置を返す var closure = function(html, index, tagName) { var closeRe = new RegExp("<(/)?" + tagName + "( [^>]*)?>", "igm"); closeRe.lastIndex = index; var depth = 1; var r = null; while (r = closeRe.exec(html)) { if (r[1] == '/') { if (--depth == 0) { // すでに他の閉じタグになってる場合はfalse return closed[r.index] ? false : { head:r.index, tail:r.index + r[0].length }; } } else { depth ++; } } return false; }; var openPattern = /<([a-zA-Z1-9:]+)([^>]*)>/gm; var found = null; while(found = openPattern.exec(html)) { if (inIgnoring(found.index)) { continue; } var head = found.index; var tail = head + found[0].length; var tagName = found[1].toLowerCase(); var attr = found[2]; if (EMPTYTAG.indexOf(tagName) >= 0 || (attr && attr.charAt(attr.length - 1) == '/')) { // 空要素タグ closed[head] = { open: head, openTail: tail, close: head, closeTail: tail, tagName: tagName, attr: attr }; } else { var cls = closure(html, tail, tagName); if (cls) { opened[head] = closed[cls.head] = { open: head, openTail: tail, close: cls.head, closeTail: cls.tail, tagName: tagName, attr: attr }; } else { errors.push({ id: errors.length, head:head, tail:tail, tagName: tagName, attr: attr, message: "タグが閉じていません" }); } } openPattern.lastIndex = tail; } })(); // 開きタグがない閉じタグを検索する (function() { var closePattern = /<\/([a-zA-Z1-9:]+)>/gm; var found = null; while(found = closePattern.exec(html)) { if (inIgnoring(found.index)) { continue; } var head = found.index; var tail = head + found[0].length; var tagName = found[1].toLowerCase(); var attr = ''; if (EMPTYTAG.indexOf(tagName) < 0) { if (!closed[found.index]) { errors.push({ id: errors.length, head:head, tail:tail, tagName: '/' + tagName, attr: attr, message: "開きタグがありません" }); } } closePattern.lastIndex = tail; } })(); // 先に開いたタグが先に閉じているような箇所がないかチェックする (function() { var checked = []; for (var i in opened) { var cl = opened[i]; for (var j = checked.length - 1; j >= 0; j--) { var ch = checked[j]; if (ch.open < cl.open && cl.open < ch.close && ch.close < cl.close) { // 親開く-子開く-親閉じる-子閉じるの順 errors.push({ id: errors.length, head: ch.close, tail: ch.closeTail, tagName: '/' + ch.tagName, attr: '', message: '<' + cl.tagName + cl.attr + '>よりも先に閉じてしまっています' }); errors.push({ id: errors.length, head: cl.close, tail: cl.closeTail, tagName: '/' + cl.tagName, attr: '', message: '<' + ch.tagName + ch.attr + '>よりも後で閉じてしまっています' }); } else if (ch.close < cl.open) { // 注目している地点ですでに閉じてるのはチェックから外す checked.splice(j, 1); } } checked.push(cl); } })(); w.showResult(html, closed, errors); })(w);