RssRollingではRSSの解析に Perl の XML::RSS を使っています。XML::RSS は内部で XML::Parser を使っています。
時折 RssRolling で巡回している際に、「not well-formed (invalid token) at line 14, column 13, byte 464 at /usr/lib/perl5/site_perl/5.6.0/i586-linux/XML/Parser.pm line 185」というエラーを吐き出すことがあります。該当の XML 文書が整形式になってないとのことなんですが、実際に見てみるとちゃんと整形式にはなっているということが多く、疑問に思っていたのですが、今日偶然僕のエントリがこれに当たったのでデバッグしてみました。
結果、エラーの原因はエントリに含まれた機種依存文字でした。調べてみると、XML ドキュメントの文字コードが EUC-JP の場合に機種依存文字が含まれていると、XML::Parser がパースに失敗することが分かりました。この問題に関しては dh's memoranda の 'Comment on Movable Type 2.6 Japanese Language Pack' でも話題に上っていて、間違いなさそうです。(実際には XML::Parser じゃなくて Expat が原因なのかな?)
根本的な対処方法が思いつかなかったので、とりあえず RssRolling は RSS のパース前に一度 UTF-8 に変換して、それを XML::RSS#parse() に食わせる形に改良してしのぐことにしました。機種依存文字は'〓'になっちゃうみたいですが、パースできないよりは遥かにマシということで。
ただ、XML::RSS か XML::Parser か分からないですけど、これらのモジュールは XML 宣言に書かれた encodings の値を見て文字コードを判別するようなので、UTF-8 への変換の後そこも置換してやる必要がありました。で、以下のようなサブルーチンを定義してなんとか事なきを得ています。
{
my %enc_map = (
sjis => "Shift_JIS",
euc => "EUC-JP",
utf8 => "UTF-8",
jis => "ISO-2022-JP"
);
# XML宣言の encoding とほんとの文字コードが違ってたら、宣言を直す
sub _modifyXmlDeclaration {
my $content = shift;
my $j = Jcode->new ( $content );
my $icode = $j->icode;
my $real_enc = $enc_map{ $icode };
# 日本語じゃないものとか分からない文字コードの場合はそのまま。
if (not defined $real_enc) {
return $content;
}
# XML宣言から encodings を読み取る
my ($declared_enc) = ($content =~ /<\?xml .*? encoding="(.*?)"\s*\?>/);
$declared_enc = "\U$declared_enc";
if (not $declared_enc) {
return $content;
}
if ( not $declared_enc eq $real_enc) {
# XML 宣言を実際の文字コードに合わせて変換
$content =~ s/(<\?xml .*? encoding=\").*?(\"\s*\?>)/$1$real_enc$2/;
}
return $content;
}
}
これを使って、
# $feed は RSS 文書が詰まったスカラー
eval {
my $r = new XML::RSS();
my $utf8_feed
= _modifyXmlDeclaration( Jcode->new( $feed )->utf8 );
$r->parse( $utf8_feed );
}; if ($@) {
die;
}
のような感じで処理しています。
もっと根本的な解決策をご存知の方がいらっしゃいましたら、是非教えてください。