2009-06-23

XML::RSSencode_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にしてしまった方がいいのかも。

0 件のコメント:

コメントを投稿