#
# 簡易HTMLチェッカ
#
# Usage:gawk -f htmlchk.awk htmlfile
#
BEGIN {
# このプログラムはすべての入力を終えてから処理を行うため、
# 一括して複数ファイルを処理することができません。
if ( ARGC != 2 ) {
printf( "Usage:gawk -f htmlchk.awk htmlfile\n" );
exit;
}
# 終了タグがなくてもかまわないタグのリスト
# ここに含まれているタグの終了タグがあるとエラー扱いされるので、
# 適宜修正してください
NonPairTag[1] = "<META>";
NonPairTag[2] = "<IMG>";
NonPairTag[3] = "<LI>";
NonPairTag[4] = "<BR>";
NonPairTag[5] = "<INPUT>";
NonPairTag[6] = "<HR>";
NonPairTag[7] = "<AREA>";
NonPairTag[8] = "<BASE";
NonPairTag[9] = "<!DOCTYPE>";
NonPairNum = 9;
TagNum = 1;
Line[0] = 0;
}
{
OrigData[NR] = $0;
Data = Data $0;
Line[NR] = Line[NR-1] + length( $0 );
}
END {
# まず、タグの目印である"<"に注目して
# 単純なエラーチェックを行う
len = length( Data );
i = 1;
while ( i <= len ) {
# ">"をテキスト中に直接書いた場合
if ( substr( Data, i, 1 ) == ">" ) {
line = get_linedata( i );
eout( "", line, 1 );
i++;
continue;
}
# コメントの処理
if ( substr( Data, i, 4 ) == "<!--" ) {
j = i + 4;
while( substr( Data, j, 3 ) != "-->" ) {
j++;
# 閉じていないコメント
if ( j > len ) {
line = get_linedata( i );
eout( "", line, 5 );
i = j;
break;
}
}
if ( i > len ) {
break;
}
i = j + 3;
continue;
}
if ( substr( Data, i, 1 ) == "<" ) {
j = i + 1;
while( substr( Data, j, 1 ) != ">" ) {
j++;
# 閉じていないタグ
if ( j > len ) {
line = get_linedata( i );
eout( "", line, 1 );
i = j;
break;
}
}
if ( i > len ) {
break;
}
# タグを保存する
TagData[TagNum] = toupper( \
substr( Data, i, j + 1 - i ) );
if ( substr( TagData[TagNum], 2, 1 ) \
!= "/" ) {
TagData[TagNum] = \
make_normalized_tag( TagData[TagNum] );
}
TagLine[TagNum] = get_linedata( i );
TagNum++;
i = j + 1;
}
else {
i++;
}
}
# タグ形式チェックを行う
for ( i = 1; i < TagNum; i++ ) {
# "<" とタグ名の間に空白文字があってはいけない
if ( substr( TagData[i], 2, 1 ) == " " ) {
eout( TagData[i], TagLine[i], 2 );
}
if ( substr( TagData[i], 2, 1 ) == "\t" ) {
eout( TagData[i], TagLine[i], 2 );
}
}
# タグ対応チェックを行う
for ( i = 1; i < TagNum; i++ ) {
TagDone[i] = 0;
TagRecursive[i] = 0;
}
for ( i = 1; i < TagNum; i++ ) {
# すでに対応チェック済み
if ( TagDone[i] != 0 ) {
continue;
}
# 対にしなくてもよいタグ
if ( nonpair_tag( TagData[i] ) ) {
TagDone[i] = 1;
continue;
}
# 対応する開始タグがない終了タグ
if ( substr( TagData[i], 2, 1 ) == "/" ) {
eout( TagData[i], TagLine[i], 3 );
continue;
}
tmptag = make_ending_tag( TagData[i] );
for ( j = i + 1; j < TagNum; j++ ) {
if ( TagDone[j] != 0 ) {
continue;
}
# 再帰的に使われているタグ
if ( TagData[i] == TagData[j] ) {
TagRecursive[i]++;
}
if ( substr( TagData[j], 2, 1 ) != "/" ) {
continue;
}
if ( substr( TagData[j], 1, length( tmptag ) ) \
== tmptag ) {
if ( TagRecursive[i] != 0 ) {
TagRecursive[i]--;
}
else {
TagDone[i] = 2;
TagDone[j] = 2;
TagPair[i] = j;
break;
}
}
}
# 対応する終了タグがない開始タグ
if ( TagDone[i] == 0 ) {
eout( TagData[i], TagLine[i], 4 );
continue;
}
}
# 交差(crossing)チェックを行う
for ( i = 1; i < TagNum; i++ ) {
if ( TagDone[i] != 2 ) {
contiune;
}
for ( j = i + 1; j < TagPair[i]; j++ ) {
if ( TagDone[j] != 2 ) {
contiune;
}
# 交差エラー発見
if ( TagPair[j] > TagPair[i] ) {
eout2( TagData[i], \
TagLine[i], \
TagData[TagPair[i]], \
TagLine[TagPair[i]], \
TagData[j], \
TagLine[j], \
TagData[TagPair[j]], \
TagLine[TagPair[j]] )
# 一回だけ警告すれば十分
break;
}
}
}
}
# タグを正規化する
function make_normalized_tag( tagdata, i, newtag )
{
for ( i = 3; i <= length( tagdata ); i++ ) {
if ( substr( tagdata, i, 1 ) == " ") {
break;
}
if ( substr( tagdata, i, 1 ) == ">" ) {
break;
}
}
newtag = sprintf( "<%s>", substr( tagdata, 2, i - 2 ) );
return newtag;
}
# 終了タグを生成する
function make_ending_tag( tagdata, i, newtag )
{
for ( i = 2; i <= length( tagdata ); i++ ) {
if ( substr( tagdata, i, 1 ) == " ") {
break;
}
if ( substr( tagdata, i, 1 ) == ">" ) {
break;
}
}
newtag = sprintf( "</%s>", substr( tagdata, 2, i - 2 ) );
return newtag;
}
# 終了タグを必要としないタグがどうかを判定する
function nonpair_tag( tagdata, i )
{
for ( i = 1; i <= NonPairNum; i++ ) {
if ( NonPairTag[i] == "" ) {
continue;
}
if ( tagdata == NonPairTag[i] ) {
return 1;
}
}
return 0;
}
# ファイル中の文字位置から出現した行番号を求める
function get_linedata( cindex, i )
{
for ( i = 1; i <= NR; i++ ) {
if ( ( cindex > Line[i-1] ) && ( cindex <= Line[i] ) ) {
return i;
}
}
printf( "Line[i] = %d\n", Line[i] );
printf( "内部エラーです:cindex = %d\n", cindex );
exit 1;
}
# エラーメッセージを出力する関数1
function eout( tag, line, type )
{
printf( "Warning:%d行目の", line );
# "<>"の対応
if ( type == 1 ) {
printf( "<と>の対応が異常です\n" );
}
# 余計な空白
if ( type == 2 ) {
printf( "タグ(%s)に余計な空白があります\n", tag );
}
# 開始タグがない
if ( type == 3 ) {
printf( "タグ(%s)の開始タグがありません\n", tag );
}
# 終了タグがない
if ( type == 4 ) {
printf( "タグ(%s)の終了タグがありません\n", tag );
}
# コメントが閉じていない
if ( type == 5 ) {
printf( "コメントが正しく閉じていません\n", tag );
}
printf( "%s:%d:%s\n\n", FILENAME, line, OrigData[line] );
return 0;
}
# エラーメッセージを出力する関数2
function eout2( tag1, line1, tag1_p, line1_p, \
tag2, line2, tag2_p, line2_p )
{
printf( "Warning:" );
printf( "【%d行目のタグ(%s)", line1, tag1 );
printf( ",%d行目のタグ(%s)の組】と\n", \
line1_p, tag1_p );
printf( " 【%s%d行目のタグ(%s)", sp, line2, tag2 );
printf( ",%d行目のタグ(%s)の組】が交差しています\n", \
line2_p, tag2_p );
printf( "%s:%d:%s\n", FILENAME, line1, OrigData[line1] );
printf( "%s:%d:%s\n", FILENAME, line2, OrigData[line2] );
printf( "%s:%d:%s\n", FILENAME, line1_p, OrigData[line1_p] );
printf( "%s:%d:%s\n\n", FILENAME, line2_p, OrigData[line2_p] );
}
とタイプします。c:\awk> gawk -f htmlchk.awk error.html
Warning:24行目の<と>:の対応が異常です
error.html:24:本文中の >: など
Warning:42行目のコメントが正しく閉じていません
error.html:42:<!--
Warning:28行目のタグ(< H3>:)に余計な空白があります
error.html:28:< H3>:空白が先頭にあるタグ</H3>:
Warning:12行目のタグ(<H2>:)の終了タグがありません
error.html:12:<H2 align="CENTER">:閉じていないタグ
Warning:26行目のタグ(</H3>:)の開始タグがありません
error.html:26:</H3>: 終了タグで開始する
Warning:28行目のタグ(< H3>:)の終了タグがありません
error.html:28:< H3>:空白が先頭にあるタグ</H3>:
Warning:28行目のタグ(</H3>:)の開始タグがありません
error.html:28:< H3>:空白が先頭にあるタグ</H3>:
Warning:【17行目のタグ(<A>:),20行目のタグ(</A>:)の組】と
【18行目のタグ(<B>:),21行目のタグ(</B>:)の組】が交差しています
error.html:17:<A HREF="error.html">:
error.html:18:<B>:
error.html:20:</A>:
error.html:21:</B>:
Warning:【29行目のタグ(<SMALL>:),37行目のタグ(</SMALL>:)の組】と
【30行目のタグ(<BLOCKQUOTE>:),38行目のタグ(</BLOCKQUOTE>:)の組】が交差しています
error.html:29:<SMALL>:
error.html:30:<BLOCKQUOTE>:
error.html:37:</SMALL>:
error.html:38:</BLOCKQUOTE>:
Copyright © 2004 TKEN