htmlのチェック

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

同等のawkプログラムへ

プログラム

プログラムは以下のようになります。 以下をタイプして、適当な名前でセーブしてください。ここでは htmlchk.pl という名前でセーブしたことにして話を進めます。
#
#	簡易HTMLチェッカ
#
#	Usage:perl htmlchk.pl htmlfile ..
#


# 終了タグがなくてもかまわないタグのリスト
# ここに含まれているタグの終了タグがあるとエラー扱いされるので、
# 適宜修正してください
@NonPairTag = (
	"<:META>",
	"<:IMG>",
	"<:LI>",
	"<:BR>",
	"<:INPUT>",
	"<:HR>",
	"<:AREA>",
	"<:BASE>",
	"<:!DOCTYPE>",
);

for ( $file = 0; $file <:= $#ARGV; $file++ ) {

	$Data = "";

	open(IN, $ARGV[$file]);

	$FileName = $ARGV[$file];

	$Line[0] = 0;

	$TagNum = 1;
	$LineNum = 1;

	while(<:IN>) {
		s/[\r\n]//g;

		$OrigData[$LineNum] = $_;
		$Data = $Data . $_;
		$Line[$LineNum] = $Line[$LineNum-1] + length( $_ );
		$LineNum++;
	}

	close(IN);

	&check();
}

sub check {
	# まず、タグの目印である"<:"に注目して
	# 単純なエラーチェックを行う
	$len = length( $Data );
	$i = 0;

	while ( $i <:= $len ) {

		# ">"をテキスト中に直接書いた場合
		if ( substr( $Data, $i, 1 ) eq ">" ) {
			$line = &get_linedata( $i );
			&eout( "",  $line, 1 );
			$i++;
			next;
		}

		# コメントの処理
		if ( substr( $Data, $i, 4 ) eq "<:!--" ) {

			$j = $i + 4;

			while( substr( $Data, $j, 3 ) ne "-->" ) {

				$j++;
				# 閉じていないコメント
				if ( $j > $len ) {

					$line = &get_linedata( $i );
					&eout( "", $line, 5 );

					$i = $j;
					last;
				}
			}

			if ( $i > $len ) {
				last;
			}

			$i = $j + 3;
			next;
		}

		if ( substr( $Data, $i, 1 ) eq "<:" ) {

			$j = $i + 1;

			while( substr( $Data, $j, 1 ) ne ">" ) {

				$j++;
				# 閉じていないタグ
				if ( $j > $len ) {

					$line = &get_linedata( $i );
					&eout( "", $line, 1 );

					$i = $j;
					last;
				}
			}

			if ( $i > $len ) {
				last;
			}

			# タグを保存する
			$TagData[$TagNum] =
				substr( $Data, $i, $j + 1 - $i );

			# 終了タグ以外は正規化する。
			if ( substr( $TagData[$TagNum], 1, 1 ) ne "/" ) {
				$TagData[$TagNum] = 
					&make_normalized_tag( 
						$TagData[$TagNum] );
			}

			$TagData[$TagNum] =~ tr/a-z/A-Z/;

			$TagLine[$TagNum] = &get_linedata( $i );

			$TagNum++;

			$i = $j + 1;
		}
		else {
			$i++;
		}
	}

	# タグ形式チェックを行う
	for ( $i = 1; $i <: $TagNum; $i++ ) {

		# "<:" とタグ名の間に空白文字があってはいけない
		if ( substr( $TagData[$i], 1, 1 ) eq " " ) {
			eout( $TagData[$i], $TagLine[$i], 2 );
		}
			
		if ( substr( $TagData[$i], 1, 1 ) eq "\t " ) {
			eout( $TagData[$i], $TagLine[$i], 2 );
		}
	}

	# タグ対応チェックを行う
	for ( $i = 1; $i <: $TagNum; $i++ ) {
		$TagPair[$i] = 0;
		$TagDone[$i] = 0;
		$TagRecursive[$i] = 0;
	}

	for ( $i = 1; $i <: $TagNum; $i++ ) {

		# すでに対応チェック済み
		if ( $TagDone[$i] != 0 ) {
			next;
		}

		# 対にしなくてもよいタグ
		if ( &nonpair_tag( $TagData[$i] ) ) {
			$TagDone[$i] = 1;
			next;
		}

		# 対応する開始タグがない終了タグ
		if ( substr( $TagData[$i], 1, 1 ) eq "/" ) {
			&eout( $TagData[$i], $TagLine[$i], 3 );
			next;
		}
			
		$tmptag = &make_ending_tag( $TagData[$i] );

		for ( $j = $i + 1; $j <: $TagNum; $j++ ) {

			if ( $TagDone[$j] != 0 ) {
				next;
			}

			# 再帰的に使われているタグ
			if ( $TagData[$j] eq $TagData[$i] ) {
				$TagRecursive[$i]++;
			}

			if ( substr( $TagData[$j], 1, 1 ) ne "/" ) {
				next;
			}

			if ( $TagData[$j] eq $tmptag ) {

				if ( $TagRecursive[$i] != 0 ) {
					$TagRecursive[$i]--;
				}
				else {
					$TagDone[$i] = 2;
					$TagDone[$j] = 2;

					$TagPair[$i] = $j;
					last;
				}
			}
		}

		# 対応する終了タグがない開始タグ
		if ( $TagDone[$i] == 0 ) {
			&eout( $TagData[$i], $TagLine[$i], 4 );
			next;
		}
	}

	# 交差(crossing)チェックを行う
	for ( $i = 1; $i <: $TagNum; $i++ ) {

		if ( $TagDone[$i] != 2 ) {
			next;
		}

		for ( $j = $i + 1; $j <: $TagPair[$i]; $j++ ) {

			if ( $TagDone[$j] != 2 ) {
				next;
			}

			# 交差エラー発見
			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]] );

				# 一回だけ警告すれば十分
				last;
			}
		}
	}
}

# タグを正規化する
sub make_normalized_tag {

	local( $tagdata ) = $_[0];
	local( $i );
	local( $newtag );

	for ( $i = 2; $i <: length( $tagdata ); $i++ ) {

		if ( substr( $tagdata, $i, 1 ) eq " ") {
			last;
		}

		if ( substr( $tagdata, $i, 1 ) eq ">" ) {
			last;
		}
	}

	$newtag = sprintf( "<:%s>", substr( $tagdata, 1, $i - 1 ) );

	return $newtag;
}

# 終了タグを生成する
sub make_ending_tag {

	local( $tagdata ) = $_[0];
	local( $i );
	local( $newtag );

	for ( $i = 1; $i <: length( $tagdata ); $i++ ) {

		if ( substr( $tagdata, $i, 1 ) eq " ") {
			last;
		}

		if ( substr( $tagdata, $i, 1 ) eq ">" ) {
			last;
		}
	}

	$newtag = sprintf( "<:/%s>", substr( $tagdata, 1, $i - 1 ) );

	return $newtag;
}

# 終了タグを必要としないタグがどうかを判定する
sub nonpair_tag {

	local( $tagdata ) = $_[0];

	foreach $tag ( @NonPairTag )  {

		if ( $tagdata eq $tag ) {
			return 1;
		}
	}

	return 0;
}

# ファイル中の文字位置から出現した行番号を求める
sub get_linedata {

	local( $cindex ) = $_[0];
	local( $i );

	for ( $i = 1; $i <:= $LineNum; $i++ ) {
		if ( ( $cindex >= $Line[$i-1] ) 
		  && ( $cindex <: $Line[$i] ) ) {
			return $i;
		}
	}
}

# エラーメッセージを出力する
sub eout {

	local( $tag ) = $_[0];
	local( $line ) = $_[1];
	local( $type ) = $_[2];

	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" );
	}

	printf( "%s:%d:%s\n\n", $FileName, $line, $OrigData[$line] );

	return 0;
}

# エラーメッセージを出力する
sub eout2 {

	local( $tag1 ) = $_[0];
	local( $line1 ) = $_[1];
	local( $tag1_p ) = $_[2];
	local( $line1_p ) = $_[3];
	local( $tag2 ) = $_[4];
	local( $line2 ) = $_[5];
	local( $tag2_p ) = $_[6];
	local( $line2_p ) = $_[7];

	printf( "Warning:" );
	printf( "【%d行目のタグ(%s)", $line1, $tag1 );
	printf( ",%d行目のタグ(%s)の組】と\n",
			$line1_p, $tag1_p );

	printf( "        【%d行目のタグ(%s)", $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", $FileName, $line2_p, $OrigData[$line2_p] );
	printf( "\n" );

}

exit;

入力データ

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

実行結果

OSはWindowsを利用していると 仮定します。 DOS prompt 上で、
c:\perl> perl htmlchk.pl 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