htmlのチェック

htmlの文法的チェックを行う

同等のperlプログラムへ

プログラム

プログラムは以下のようにawkとしてはかなり大きめです。 以下をタイプして、適当な名前でセーブしてください。ここでは htmlchk.awk という名前でセーブしたことにして話を進めます。
#
#	簡易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] );

}

入力データ

入力データは任意のhtmlです。 ここではエラーを含んだ error.html を用意します。

実行結果

ここでは、awk処理系としてgawkを利用し、 また、OSはWindowsを利用していると 仮定します。 DOS prompt 上で、
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>:


メニューに戻る
Tkensaku

TKENSAKU top へ

Copyright © 2004 TKEN