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 <テストだよ> <テストだよ> <テストだよ> <テストだよ> <テストだよ>
こちとら、2行目の「<テストだよ>」のようにして欲しいわけだが、みな、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 にしてしまった方がいいのかも。