# # 簡易HTMLチェッカ # # Usage:gawk -f htmlchk.awk htmlfile # BEGIN { # このプログラムはすべての入力を終えてから処理を行うため、 # 一括して複数ファイルを処理することができません。 if ( ARGC != 2 ) { printf( "Usage:gawk -f htmlchk.awk htmlfile\n" ); exit; } # 終了タグがなくてもかまわないタグのリスト # ここに含まれているタグの終了タグがあるとエラー扱いされるので、 # 適宜修正してください NonPairTag[1] = ""; NonPairTag[2] = ""; NonPairTag[3] = "
  • "; NonPairTag[4] = "
    "; NonPairTag[5] = ""; NonPairTag[6] = "
    "; NonPairTag[7] = ""; NonPairTag[8] = ""をテキスト中に直接書いた場合 if ( substr( Data, i, 1 ) == ">" ) { line = get_linedata( i ); eout( "", line, 1 ); i++; continue; } # コメントの処理 if ( substr( Data, i, 4 ) == "" ) { 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( "", 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] ); }