@kyanny's blog

My thoughts, my life. Views/opinions are my own.

HTML::Element と HTML::TreeBuilder で盆栽

HTML::Element とか HTML::TreeBuilder で HTML 構文木をいじることを盆栽と呼ぶらしいですが、最近よく盆栽してていろいろ覚えたことがあるのでメモ。

as_HTML() で HTML エンティティに変換させない

HTML::Element#as_HTML を呼ぶと HTML 文字列が返るのだけど、これを引数なしで呼ぶと "all unsafe characters" が HTML エンティティに変換(エンコード)される。これは普通あまり望ましくないので(変換などせずそのまま出して欲しいことのほうが多い)、それを回避するには

my $html = $elem->as_HTML('');

と、空文字列を渡してやる。

HTML::TreeBuilder のインスタンスに対して as_HTML() を呼ぶと タグをつけられてしまう

これはサブクラスの HTML::TreeBuilder::XPath でも同じ。スクレイピングしてきた HTML 断片に対して HTML::TreeBuilder で盆栽したくなることはよくあるので、まさにそういうことをやってる Web::Scraper ではどうやって回避してるのかなーと思ってコードを読んでみたら __get_value の中で正規表現で置換していた。こうするしかないのか・・・と思っていたところ、もっとエレガントなやり方を発見。 ドメインパーキング に書いてあったのだけど、

use     HTML::TreeBuilder;

$tree->parse_file($filename);
$body = $tree->guts();                  #$treeをそのままas_HTMLで出力すると、implicitなtag(htmlなど)が挿入されてしまう。
					#そのため、最上位をHTML::Elementとして取得。elementifyでも上記と同様。

という風にすると、 タグを勝手につけられずに済む。

HTML::Element の属性を操作する

ちょっと葉が多いので切ってみよう的な。普通に属性値の中身を変更するのは $elem->attr('class' => 'klass') とかで良さそうだとわかったのだけど、属性そのものを消したいときはどうやんの?というと、 $elem->attr('class' => undef) でいける。


HTML の中から特定のタグだけ削除する(ただしツリー構造ごと削除はしない)

以下の例をみてもらうのが早いけど、 div の中に p と pre がたくさん入ってて pre だけ全部消したい(ただし pre の中にも p が入ってることがあってその p は消したくない)みたいな場合のやり方。

HTML::Element には子ノードを指定の条件で走査してノードのリストを返すメソッドがいくつかあるのだけど、 find* という名前のつくメソッドはいまは推奨されてないらしい。今後は look_down() と look_up() を使うようにと書いてあり、これの使い方を覚えると柔軟に対応できるのでこっちから覚えたほうがよさそう。 jQuery でいうところの $.ajax() みたいな感じだ。条件全部自分で指定して使う感じ。

あと、 HTML::Element#as_HTML は不思議なことに p タグの閉じタグを勝手に消したりする。元々の HTML には閉じタグがあるにもかかわらず。閉じタグもきっちり欲しい場合は as_XML() を使うと、より strict な HTML を返してくれる。

HTML::Element#content_list の返り値

$elem->content_list() は、そのエレメントが子ノードをもっているかどうかの判定に使える。スカラコンテキストの場合は子ノードの個数が、リストコンテキストの場合は子ノードのリストが返るのだけど、条件判定に使うならどっちにしろ偽が返ることになる。

これも例ママだけど、あるタグの中身が空っぽだったらタグごと捨てたい、みたいなときに使える。


他にももう少し手の込んだ盆栽テクニックとして、 HTML 中から特定のクラスをもったタグだけ取り除いて他の部分を残したい、みたいなケースもあり、そのときは HTML::TreeBuilder::XPath と HTML::Selector::XPath を組み合わせて、

$tree->find(selector_to_xpath('div.blah'))->delete();
$tree->guts()->as_XML('');

みたいにするとできたりする。以上、まとまりがないけど HTML 盆栽のテクニックをメモした。この二週間くらいずっとまとめを書いておきたかったのだけどやっと書けた!ここに書いておくとあとで検索しやすくて自分が助かるので、片付けられてよかった。奥歯にひっかかったものがとれたような気分。