#
# 簡易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;
とタイプします。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>:
Copyright © 2004 TKEN