HTML::TreeBuilder

お仕事的な都合で、Static な HTML のドキュメントの中身を変換して某 wiki に移行するという必要性が出てきた。html2wiki 的なツールがないものかと思ったけど、これといってめぼしいものが見つからなかったので少し考えてみる。

とりあえずは parser 的なものを探してみた。最初は HTML::TokeParser を使ってみた。これでもやれなさそうなことはなかったのだけど、日本語処理がいまひとつうまくいかなくて、一部文字化け、あるいは途中から分が表示されないなどの現象が見えたので面倒になって放棄。

HTML::TagParser が使えそうかなと思ったりもしたが、今回は特定の要素や情報だけを抜き出したいというわけではなくて、HTML ドキュメント全体の変換が目的なのだ。だから、ドキュメントの構造情報が見えてこないといかんのですよ。あまり詳しく調べてないけど、要素抽出とか情報抽出には良さそうだけど全体の構文情報とか、あるいは next 系の iterator 的な動作をするようなメソッドが見えなかったのでこれも放置。日本語対応とかちゃんしてくれてそうなので良さそうではあったのだが。

で、結局 HTML::TreeBuilder を使ってみた。木構造を作ってくれるのなら再帰でドキュメント全体のスキャニングが出来るはずだし。まずは問題の日本語処理…と思って日本語読ませてみたけど、入力を utf8 にしておかないといけないのか…。まあこれはフロントエンドを入れるか何かすれば対応出来るから良いでしょう。

ということでサンプルを書いてみる。

  • 入力(HTML)
<html>
 <head>
  <title>タイトル</title>
 </head>
</head>

 <body>
     <h1>見出し1</h1>
     <p>
      sample
      <dl>
       <dt>term1
       <dd>def1
       <dt>term2
       <dd>def2
      </dl>
      aiueo
      <pre>
       abcdefg = @@@;
       fhfhfhfhfhfhfhfhfhf
      </pre>
     </p>
     <h2>見出し2</h2>
     <p>
      aaaa <img src="./foo.png"> bbbbb
      cccccccdd ee <strong>ffffff g</strong> gg
      <ul>
        <li>list1</li>
        <li>list2</li>
        <ol>
          <li>olist1</li>
          <li>olist2</li>
        </ol>
      </ul>
      link to <a href="./hoge">hoge</a> aiueokakikukeko
      sasisuseso tatituteto
     </p>
 </body>

</html>
use strict;
use warnings;
use HTML::TreeBuilder;
use Data::Dumper;

sub itr_conv {
    my $content_ref = shift;
    my $cnt = shift || 1;

    my $type = ref $content_ref;

    if ( $type eq "HTML::Element" ) {

        my $fmt = '%' . $cnt * 2 . "s<%s>\n";
        printf $fmt, " ", $content_ref->tag;

        my $child_content_list_ref = $content_ref->content;
        foreach my $c (@$child_content_list_ref) {
            itr_conv( $c, $cnt + 1 );
        }

    }
    else {
        my $fmt = '%' . $cnt * 2 . "s%s\n";
        printf $fmt, " ", $content_ref;
    }
}

sub html2pkwk {
    my $tree = shift;

    my ($body) = $tree->find("body");
    itr_conv($body);
}

#####

my $file = shift;
my $tree = HTML::TreeBuilder->new;

$tree->parse_file($file);
print "--------------\n";
$tree->dump;
print "--------------\n";
html2pkwk($tree);
print "--------------\n";

$tree->delete;
  • 実行結果
--------------
<html> @0
  <head> @0.0
    <title> @0.0.0
      "タイトル"
  <body> @0.1
    <h1> @0.1.0
      "見出し1"
    <p> @0.1.1
      " sample "
      <dl> @0.1.1.1
        <dt> @0.1.1.1.0
          "term1 "
        <dd> @0.1.1.1.1
          "def1 "
        <dt> @0.1.1.1.2
          "term2 "
        <dd> @0.1.1.1.3
          "def2 "
      " aiueo "
      <pre> @0.1.1.3
        "\x0d\x0a       abcdefg = @@@;\x0d\x0a       fhfhfhfhfhfhfhfhfhf\x0d\x0a      "
    <h2> @0.1.2
      "見出し2"
    <p> @0.1.3
      " aaaa "
      <img src="./foo.png" /> @0.1.3.1
      " bbbbb cccccccdd ee "
      <strong> @0.1.3.3
        "ffffff g"
      " gg "
      <ul> @0.1.3.5
        <li> @0.1.3.5.0
          "list1"
        <li> @0.1.3.5.1
          "list2"
        <ol> @0.1.3.5.2
          <li> @0.1.3.5.2.0
            "olist1"
          <li> @0.1.3.5.2.1
            "olist2"
      " link to "
      <a href="./hoge"> @0.1.3.7
        "hoge"
      " aiueokakikukeko sasisuseso tatituteto "
--------------
  <body>
    <h1>
      見出し1
    <p>
       sample 
      <dl>
        <dt>
          term1 
        <dd>
          def1 
        <dt>
          term2 
        <dd>
          def2 
       aiueo 
      <pre>
        
       abcdefg = @@@;
       fhfhfhfhfhfhfhfhfhf
      
    <h2>
      見出し2
    <p>
       aaaa 
      <img>
       bbbbb cccccccdd ee 
      <strong>
        ffffff g
       gg 
      <ul>
        <li>
          list1
        <li>
          list2
        <ol>
          <li>
            olist1
          <li>
            olist2
       link to 
      <a>
        hoge
       aiueokakikukeko sasisuseso tatituteto 
--------------

注意点を挙げるとすると

  • HTML::Elementのドキュメントを読むこと。機能のかなりの部分はそこから継承している。
  • HTML::Element::contents の動作に注意。ある Element object の content (子要素)は、(a)Element object (b)文字列 のいずれかで、contents系のメソッドを呼ぶと子要素のリスト(のリファレンス)が返される。
  • pre とか、改行混じりでシリアライズされてるみたいなので、pre ブロック内のデータ処理は要注意。

pod以外の参考情報