印刷用表示へ切り替え 通常表示へ切り替え 更新履歴を表示 更新履歴を隠す
Perl HTML::TreeBuilder のメソッド名がグロい

HTML::TreeBuilder で HTML 文書の断片を構文解析して元に戻すと,余計な html タグなどをつけてくれる。回避する方法はないか調べてみると,一風変わったな名前のメソッドが見つかった。

■ ■ ■

HTML::TreeBuilder の話の続き。先の記事は,HTML::TreeBuilder の出力が strict になる方法についてだった (nlog(n): HTML::TreeBuilder で構築した木が元の HTML に戻らない)。もちろん,これはオリジナルのドキュメントにちゃんと書いてある。そんなものを「再発見」してどうするのだという気もするが,嵌{はま}りやすいことでもあるので,強調しておくことに多少の意味はあるだろうということで,深く考えないことにする。さて,今回は,HTML 文書の一部 (断片) を構文解析して元に戻すと,「戻りすぎる」という話である。これもドキュメントにちゃんと書いてある記述ではあるのだが (HTML::TreeBuilder)。

HTML::TreeBuilder は,デフォルトでは HTML 文書全体を解析の対象としている。<html> タグで始まり,</html> で終わる文書である (加えて,<?xml?> や <!DOCTYPE> にも対応している)。したがって,逆に,HTML 文書の一部であると,困ることがある。例えば,次のようなものである。

<li>I like pie!

これを構文解析して元に戻すと,次の出力になる。

<html>
  <head>
  </head>
  <body>
    <ul>
      <li>I like pie </li>
    </ul>
  </body>
</html>

<li> に不足している </li> や ul タグなどを追加してくれるのは嬉しいのだが,余分なタグを追加してくれてしまうのである。html, head, body は元々書いてないので困るのだ。このテストに使ったプログラムは次の通り (前回と同じ)。

#!/usr/bin/perl

use strict;
use HTML::TreeBuilder;

my $html = <<'END';
<li>I like pie!
END

my $root = HTML::TreeBuilder->new;

$root->p_strict(1);
$root->parse($html);
$root->eof;
$html = $root->as_HTML('<>&', '  ', {});
$root->delete;

print $html;

当然,この問題は著者も認識していて,その回避の方法も,ドキュメントに書かれている (HTML::TreeBuilder)。「HTML 文書の一部が対象の場合は,guts() メソッドを使え」。guts() は,余計な要素を付け加えないからである。動作をまとめると次のようになる。

@nodes = $root->guts()
$parent_for_nodes = $root->guts()

  • リストコンテキストの場合,(暗黙でない) ノードのリストを返す
  • スカラーコンテキストの場合,ノードの数により返す値が異なる
    • ノードが0個の場合,undef を返す
    • ノードが1個の場合,そのノードを返す
    • ノードが2個以上の場合,親のノードを返す (親のノードを div 要素と仮定する)

このことを考えると,プログラムとしてはリストコンテキストで受け取るのがいいということになる。ノードの数によって返す値が変わらないからである。逆に言うと,スカラーコンテキストだと「div」がついてしまうのが痛いのだ。

したがって,html, head, body などの暗黙の要素を付け加えたくない場合は,木を HTML に変換する次の部分を,

$html = $root->as_HTML('<>&', '  ', {});

以下のように書けばいいということになる。

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

guts() が返すリストの要素 $c がリファレンスのときは HTML 要素なので,as_HTML メソッドで変換する。$c がリファレンスでないときは,単なるテキストなので,変換しない。

「guts」は「内臓」の意味である。ギターやバイオリン族の「ガット弦」やテニスラケットの「ガット」もそうだ (実際は腸)。TreeBuilderの guts は,外の「皮」を付けないで,内臓を取り出すという意味だろうか。これを他の言葉で「get_contents」などと表現しても,ピンとこない。「content_list」メソッドは実装されているので,混乱の元にもなる。なかなかグロい (grotesque な) 表現であるが,的確であると言える。

TreeBuilder には,他にも「disembowel」というメソッドがある。これは「腸を取出す」という意味である (dis・em・bow・el - goo 辞書)。disembowel() メソッドは,guts() と同様な値を返すが,中身を取り除いてしまうという点が異なる。こちらは,グロを超えて,むしろホラーであると言えるのではなかろうか。好きだけど。

2008年4月14日追記:
文字参照が含まれている場合を考えると,次のように書いた方がいいかも知れません (nlog(n): HTML::TreeBuilder で日本語と文字参照を扱う場合の手順)。

foreach my $c ($tree->guts()) {
  $html .= ref $c ? $c->as_HTML('<>&', '', {}) : encode_entities($c);
}
Posted by n at 2008-03-13 23:43 | Edit | Comments (0) | Trackback(0)
Trackbacks

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


(必須, 表示されます)


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


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


Confirmation Code (必須)


Remember info (R)?