最新 追記

おこたの国


2009-06-23 遂に季刊になっちまった…

_ [COMP] XML::RSS の encode_cb と、utf8 フラグ

自前の antenna プログラムが、えらく重たくなっていることは前々から気づいてた。けど、今イチやる気が起きなくて延々放置してたのだが、この度、ようやく物理的にも重い腰を上げてみた。

計ると10分も処理にかかってる。その間、perl は CPU を食い荒らし、筐体内温度も上がってく。今回の修正で処理時間は20秒ほどになった。やれやれである。

HTML::Entities

XML::RSS は 1.12 から、実体参照の処理に HTML::Entities を利用するようになった。内部のデータは encode_entities_numeric で処理され、utf8フラグが付いているとか、そういう配慮無しに日本語はすべて数値参照になってしまった。問題の XML::RSS::Private::Output::Base の当該箇所はこうなっている。

   while ($text =~ s/(.*?)(\<\!\[CDATA\[.*?\]\]\>)//s) {
       # we use &named; entities here because it's HTML
       $encoded_text .= encode_entities($1) . $2;
   }
   # we use numeric entities here because it's XML
   $encoded_text .= encode_entities_numeric($text);

これだけだと分かり難いので、サンプルで。

$ cat a.pl
#!/usr/bin/perl

use HTML::Entities qw/:DEFAULT encode_entities_numeric/;
use encoding utf8;

my $in = '<テストだよ>';
print $in, "\n";
print encode_entities($in), "\n";
print encode_entities($in, '<>&"'), "\n";
print encode_entities_numeric($in), "\n";
print encode_entities_numeric($in, '<>&"'), "\n";
$ perl a.pl
<テストだよ>
&lt;&#x30C6;&#x30B9;&#x30C8;&#x3060;&#x3088;&gt;
&lt;テストだよ&gt;
&#x3C;&#x30C6;&#x30B9;&#x30C8;&#x3060;&#x3088;&#x3E;
&#x3C;テストだよ&#x3E;

こちとら、2行目の「&lt;テストだよ&gt;」のようにして欲しいわけだが、みな、3行目のようになり、何が書いてあるのかデコードしないとわからなくなってしまった。まぁ、RSS なんだからプラウザで読めればいいという話ではあるのだが。

ま、とにもかくも、HTML::Entities の使い方は日本では不評であった。これに対抗するにはどうすりゃいいか、なんとか encode 部分を差し替えられんかと、あれこれ考えていた。XML::RSS をバージョンアップせず、1.10 を使い続けるというのも一手。もしくは改造しちゃう。最初の頃はそうしてた。あるときから面倒になって、とりあえず、encode された状態を出力させ、後で decode するというのに切り替えた。XML::RSS は、FreeBSD の ports で導入してるので、バージョンアップしないでいるのも面倒だから。

「XML::RSS 字化け」でググると、encode_output => 0 をしてるページが沢山ヒットしてしまう。それはどうよ。XML パーサーにかけるとエラーになる RSS が生成されっぞ。まぁ,他人事ではあるが。

encode_cb

さて、今回の件を調べていて、CPU 喰っているのは実体参照の encode をしている部分であることが分かった。なんかできんかと、XML::RSS の Pod を読み返していて、new Method のところに、encode_cb なるパラメータが増えていることを知った。まさに、encode 部分の差し替え機能である。エンコード部分を自前で用意して reference で渡すだけでいい。こんな感じになる。

sub my_encode {
    my ($self, $text) = @_;

    #return "" unless defined $text;
    if (!defined($text)) {
        confess "\$text is undefined in XML::RSS::_encode(). We don't know how "
. "to handle it!";
    }

    return $text if (!$self->_main->_encode_output);

    my $encoded_text = '';

    if ($text =~ /\[CDATA\[/) {
        while ($text =~ s/(.*?)(\<\!\[CDATA\[.*?\]\]\>)//s) {
            # we use &named; entities here because it's HTML
            $encoded_text .= encode_entities($1, '<>&"') . $2;
        }
    }
    # we use numeric entities here because it's XML
    $encoded_text .= encode_entities($text, '<>&"');

    return $encoded_text;
}
...
my $rss = new XML::RSS(encode_cb => \&my_encode);

XML::RSS::Private::Output::Base の _default_encode から、二箇所を変更している。

  • encode_entities_numeric は使わない。
  • unsafe_chars を明示指定して、余計なエンコードを抑制

これで XML::RSS 1.10 相当に戻る。

utf8 フラグ

日本語文字列が数値参照になってしまうのは、これで防げたが、CPU喰いまくりなのは直らない。何が? これだ。

while ($text =~ s/(.*?)(\<\!\[CDATA\[.*?\]\]\>)//s) { }

CDATAセクション内を実体参照しないための処置なのだが、このパターンマッチを utf8 フラグの立ったデータに行うと時間がかかる。マッチだけで 10秒近く。なにやら perl 5.8.9 からのような気がする。とりあえず、上のように、CDATA が無ければマッチに行かないように変更して逃げてるんだけど、いっそ perl を 5.10 にしてしまった方がいいのかも。


2002|10|
2003|10|12|
2004|01|06|07|08|09|10|11|12|
2005|01|02|03|04|05|06|07|08|09|10|11|12|
2006|01|02|03|04|05|06|07|08|09|10|11|
2007|01|02|03|04|05|06|07|08|09|10|11|12|
2008|01|02|03|05|07|08|09|10|11|12|
2009|02|06|08|09|
2010|02|08|
2011|08|
2012|07|
2013|01|06|09|10|
2019|07|10|
2020|07|