印刷用表示へ切り替え 通常表示へ切り替え 更新履歴を表示 更新履歴を隠す
Perl HTML::TreeBuilder で日本語と文字参照を扱う場合の手順

TreeBuilder を使って,日本語と文字参照を含む文書を処理する場合の手順について,はまったのでまとめておく。

■ ■ ■

HTML::TreeBuilder が文字参照を勝手にデコードしてしまうので困った。便利なのだが,扱いが難しいのだ (HTML::TreeBuilder)。文字参照というのは,「&」が頭についている文字のこと (文字参照 - Wikipedia)。日本語と文字参照を含む文書を処理する場合の手順についてまとめる。

参考サイト

日本語処理

Encodeモジュールを使って、HTMLテキストを事前にdecodeメソッドで内部表現(UTFフラグありのUTF-8)に変更、処理後にencodeメソッドで元の文字コードに戻すとよいようです。

Perlメモ/HTML::TreeBuilderモジュール - Walrus, Digit.

日本語処理の基本はこれで。

文字参照処理

$tree->parse( encode_entities($html, '&') ); とか、&を余分にエスケープしてからパースさせるようにして decode_entities( $tree->as_XML ); とかして取り出している。

unknownplace.org - HTML::TreeBuilderいいんだけど。。

そうなのよ。私も TreeBuilder のエスケープ関係は微妙だと思う。エスケープしたりしなかったりするし,テキスト部分の自動デコードを止めさせられない。属性の自動デコード抑制はできるのに。

decode_entities

decode_entitiesした時点でUnicodeになっているので、あとは utf-8 で出力したければ、encode('utf-8', decode_entities('格安')) したらよいようです。ちなみに euc-jp にしたけりゃ encode('eucjp', decode_entities('格安')); です。

Engadget Japaneseで使われている日本語のエンティティの変換 (Clouder::Blogger)

Unicode と UTF-8 は違う模様。

まとめ

まとめると次のようになる。日本語を文字化けしないようにして,文字参照を元の HTML のままにする手順。

  1. encode_entitiesで&を余分にエスケープ
  2. decodeで内部表現(UTFフラグありのUTF-8)に変更
  3. TreeBuilderでパース
  4. decode_entitiesでエスケープした文字参照を元に戻す
  5. encodeで元の文字コードに変更

エンコードしてデコードして処理,デコードしてエンコードして終了ということになっていて,ややこしい。encode_entities と encode の順序が構造的になっていないのが気になるが,TreeBuilder の直前で UTF フラグを立てたくて,一番最後に目的の文字コードに変更したいことを優先すると,こうなってしまう。

サンプルコード

HTML 文書の文字コードを EUC-JP だとする (日本語は使ってないけど)。

#!/usr/bin/perl

use strict;
use HTML::TreeBuilder;
use HTML::Entities qw(encode_entities decode_entities);
use Encode qw(encode decode);

my $html = <<'END';
<p><img alt="&quot;" />&gt;</p>
&copy;
END

my $charset = 'euc-jp';
my $tree = HTML::TreeBuilder->new();
$html = encode_entities($html, '&');
$html = decode($charset, $html);
$tree->parse($html);
$tree->eof();

$html = '';
foreach my $c ($tree->guts()) {
  $html .= ref $c ? $c->as_HTML('<>&', '', {}) : $c;
}

# $html = decode_entities($html);
$html = encode($charset, $html);
print $html;

動作を確認するため,最後の decode_entities は意識的にコメントアウトしてある (実際は入れること)。guts を使っているのは,インプリシットに挿入されてしまうタグを表示したくないから (nlog(n): HTML::TreeBuilder のメソッド名がグロい)。実行結果は次の通り。

<p><img alt="&amp;quot;" />&amp;gt;</p>
 &copy;

この結果を見ると,あれれ? である。p の外側にある文字参照「&copy;」のエスケープが足りない。このまま decode_entities すると,「&copy;」が元に戻らなくなってしまう。ということは,as_HTML メソッドでエンコードしているということか。

foreach my $c ($tree->guts()) {
  $html .= ref $c ? $c->as_HTML('<>&', '', {}) : encode_entities($c, '&');
}

とすれば「&amp;copy;」になる。最初のサンプルソースの一部を,これで置き換えればよい。encode_entities して encode_entities することになっているというのが変な感じではある。

最初の方にある「$html = encode_entities($html, '&');」の部分は次のように書き換えることもできる。

$tree->handler(text =>
                    sub {
                      encode_entities($_[1], '&');
                      $_[0]->text($_[1]);
                    }, 'self, text');
$tree->attr_encoded(1);

テキストノードと属性のエンコードを別々に処理するというもの。テキストノード (上のHTMLでは「&gt;」の部分) のときだけに発生するイベントを捕まえて,encode_entities している。ハンドラの使い方については,HTML::Parser のドキュメントに記述がある (HTML::Parser)。属性については,テキストではないので,別に attr_encoded する必要がある。しかし,これだと,encode_entities と decode_entities の対がぱっと見分かりづらい。

最初,このハンドラ設定を見つけたときに,「文字参照の問題はこれで解決!」と思って,次のように書いてみた。

$tree->handler(text =>
                    sub {
                      $_[0]->text($_[1]);
                    }, 'self, text');

そうしたら,何も変わらなかった。テキストを引っ張り出してきて,元のところに入れるだけなので,何もやっていないのと等価だったのだ。

Posted by n at 2008-04-02 00:58 | Edit | Comments (0) | Trackback(0)
Trackbacks

  • 「手違いで複数トラックバックを送ってしまった!」という場合でも気にしないでください (重複分はこちらで勝手に削除させていただきます)
  • タイムアウトエラーは,こちらのサーバの処理能力不足が原因です (詳細は トラックバック送信時のエラー をご覧ください)
  • トラックバックする記事には,この記事へのリンクを含めてください(詳細は 迷惑トラックバック対策 をご覧ください)
Comments
Post a comment
  • 電子メールアドレスは必須ですが,表示されません (気になる場合は「メールアドレスのような」文字列でもOKです)
  • URL を入力した場合はリンクが張られます
  • コメント欄内ではタグは使えません
  • コメント欄内に URL を記入した場合は自動的にリンクに変換されます
  • コメント欄内の改行はそのまま改行となります
  • 「Confirmation Code」に表示されている数字を入力してください (迷惑コメント対策です)


(必須, 表示されます)


(必須, 表示されません)


(任意, リンクされます)


Confirmation Code (必須)


Remember info (R)?