04 May 2011

(Perl) ローカルにインストールしたW3C Markup Validatorを用いてWebService::Validator::HTML::W3Cでのチェックを行う

前回の記事 『 (Perl) sitemap.xml の全URLをW3C HTML Validatorでチェックするスクリプト 』で、W3Cのサーバのアクセス制限を超えるおそれがあると分かった。

そこで、ローカルホストにW3C Markup Validatorをインストールして、それをWebService::Validator::HTML::W3Cから利用するようにすれば、何の制限を受けることもなくなる。

■ W3C Markup Validator のインストール

公式ページ Source code availability for the W3C Markup Validator には次のように書かれている。

Debian GNU/Linux package : Starting with Debian 3.1 ("Sarge"), the package and all its dependencies are included in the official Debian distribution, and can be installed by running the command apt-get install w3c-markup-validator as root.

Ubuntu 10.04LTSでもこの方法を用いて簡単にセットアップできる。

# apt-get install w3c-markup-validator

幾つかの依存パッケージがインストールされて、自動的にセットアップが完了する。 / /etc/apache2/conf.d には w3c-markup-validator.conf (シンボリックリンク) が作成され、http://localhost/w3c-markup-validator/にアクセスするとW3C Markup Validatorの画面が出現する。

画面のデザインは公式サイト(http://validator.w3.org/)よりセンスが良い。


■ WebService::Validator::HTML::W3C から利用する

前回の記事 『 (Perl) sitemap.xml の全URLをW3C HTML Validatorでチェックするスクリプト 』に掲載したソースコードに少し手を入れる(赤で着色した部分を追加)。

my $v = WebService::Validator::HTML::W3C->new(detailed => 1, validator_uri => 'http://localhost/w3c-markup-validator/check');

ココまでの設定でうまく行くのなら、こんな記事はかかない。うまく行かないのである… (笑

エラーが発生するページをValidateすると、Can't call method "getChildNode" on an undefined value at /usr/local/share/perl/5.10.1/WebService/Validator/HTML/W3C.pm line 348.というエラーが発生する。W3C.pmの348行目辺りを眺めてみると、XMLを読み込んでいる部分のようだ。

foreach my $msg ( @messages ) { my $err = WebService::Validator::HTML::W3C::Error->new({ line => $xp->find( './m:line', $msg )->get_node(1)->getChildNode(1)->getValue, col => $xp->find( './m:col', $msg )->get_node(1)->getChildNode(1)->getValue, msg => $xp->find( './m:message', $msg )->get_node(1)->getChildNode(1)->getValue, msgid => $xp->find( './m:messageid', $msg )->get_node(1)->getChildNode(1)->getValue, explanation => $xp->find( './m:explanation', $msg )->get_node(1)->getChildNode(1)->getValue, }); push @errs, $err; }

W3C Markup Validatorは通常のWebインターフェース以外に、xmlを返すWebAPIも実装されているようだ。Apache2のログ(/var/log/apache2/access.log)を見てみると、"GET /w3c-markup-validator/check?uri=http%3A%2F%2Fnetlog.jpn.org%2Fr271-635%2F2011%2F05%2Flinux_conky_and_screenlets.html;output=soap12 HTTP/1.1" 200 1167 "のような感じになっているので、URLパラメータにoutput=soap12を付けるとxmlを吐くようである。

ローカルにインストールされたW3C Markup Validatorと、公式ページのW3C Markup Validatorの出力xmlを比較すると、幾つかのエレメントが足りない(緑色で着色した部分)。m:messageidm:explanationが存在しないのがエラーの原因のようだ。

ローカルにインストールしたValidatorの出力
<m:error> <m:line>191</m:line> <m:col>18</m:col> <m:message>character "&" is the first character of a delimiter but occurred as data</m:message> </m:error>
公式Validatorの出力
<m:error> <m:line>191</m:line> <m:col>19</m:col> <m:message>xmlParseEntityRef: no name </m:message> <m:messageid>libxml2-68</m:messageid> <m:explanation> <![CDATA[ <p class="helpwanted"> <a href="feedback.html?uri=http%3A%2F%2Fnetlog.jpn.org%2Fr271-635%2F2010%2F02%2Fubuntugimpexif.html;errmsg_id=libxml2-68#errormsg" title="Suggest improvements on this error message through our feedback channels" >✉</a> </p> ]]> </m:explanation> <m:source><![CDATA[ * set the date <strong title="Position where error was detected.">&</strong> time image was saved<br />]]></m:source> </m:error>

Validatorのソースコード /usr/lib/cgi-bin/check をちょろっと読んでみると、URLパラメータの"option=soap12"は863行目辺りで解釈され、872行目$template = $SOAPT;で何らかのテンプレートが設定されている。$SOAPTは318行目で$CFG->{Paths}->{Templates}ディレクトリのsoap_output.tmplファイルということになるので、104行目辺りで設定されているW3C_VALIDATOR_HOMEである /usr/share/w3c-markup-validator ディレクトリ辺りを探しに行き、テンプレートファイルを書き換える(赤で着色した部分を追加。なお、値は適当に'0'でも放りこんでおいた)。

/usr/share/w3c-markup-validator/templates/en_US/soap_output.tmpl
<m:errors> <m:errorcount><TMPL_VAR NAME="valid_errors_num"></m:errorcount> <m:errorlist> <TMPL_LOOP NAME="file_errors"><TMPL_IF NAME="err_type_err"> <m:error> <m:line><TMPL_VAR NAME="line"></m:line> <m:col><TMPL_VAR NAME="char"></m:col> <m:message><TMPL_VAR NAME="msg" ESCAPE="HTML"></m:message> <m:messageid>0</m:messageid> <m:explanation>0</m:explanation> </m:error> </TMPL_IF></TMPL_LOOP> </m:errorlist> </m:errors> <m:warnings> <m:warningcount><TMPL_VAR NAME="valid_warnings_num"></m:warningcount> <m:warninglist> <TMPL_IF NAME="have_warnings"><TMPL_INCLUDE NAME="soap_warnings.tmpl"></TMPL_IF> <TMPL_LOOP NAME="file_errors"><TMPL_IF NAME="err_type_warn"> <m:warning> <m:line><TMPL_VAR NAME="line"></m:line> <m:col><TMPL_VAR NAME="char"></m:col> <m:message><TMPL_VAR NAME="msg" ESCAPE="HTML"></m:message> <m:messageid>0</m:messageid> <m:explanation>0</m:explanation> </m:warning> </TMPL_IF></TMPL_LOOP> </m:warninglist> </m:warnings>

この変更で、エラーを受信することは可能になるが、警告を受信することは出来ない。公式Validatorと挙動が違うようなので、ローカルValidatorの挙動を無理やり変えてみることにする。/usr/lib/cgi-bin/checkの1604行目辺り、警告(warn)の場合も無理やりエラー(err)となるように変更する(赤で着色した部分)。

/usr/lib/cgi-bin/check
elsif (($err->{type} eq 'W') or ($err->{type} eq 'X') ) { # $err->{class} = 'msg_warn'; $err->{class} = 'msg_err'; # $err->{err_type_err} = 0; $err->{err_type_err} = 1; # $err->{err_type_warn} = 1; $err->{err_type_warn} = 0; $err->{err_type_info} = 0; # $number_of_warnings += 1; $number_of_errors += 1; }

これらの変更で、うまくValidatorを使えるようになる。 これで、公式ページを”DOS攻撃的利用”しなくても、Validatorを使えるようになった。