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を使えるようになった。