ahtpaap2016-03-01T18:30:41+09:00

Perl の HTML::TreeBuilder で元の HTML を出力する

このサイトは Perl と XSLT と Ruby と GoLive で構築している。長年やっているので、今時の CMS のようなものは導入し損ね、徐々にお手製の静的サイトジェネレータとも呼ぶべきツール群がごっそり溜まってしまっている。

もともと、Perl でそれらのツール群を書いていた。Mac OS 9 の頃は Ruby なんて使えなかったし、Mac OS X になってもしばらくはライブラリが充実しておらず Perl をずっと使い続けてきた。Ruby に乗り換えたいと思って、少し浮気したが、過去の蓄積が有りすぎて Perl を捨てるなんて想像しがたい。もう、覚悟を決めて Perl を使い倒そうと思っている。

Perl で HTML を処理するときは、HTML::Parser と HTML::TreeBuilder と仲良くすることになる。HTML::TreeBuilder は、HTML を読み込んで、木構造に変換してくれるので、HTML ファイルから一部の情報を取り出すのに大変便利。しかし、その木構造を再度 HTML ファイルに書き出そうとすると、余計なものが付いたり、大事な物が削られたり、順番が変わったり、とにかく余計な事をいろいろしてくれるので、HTML ファイルの書き出しには使えなかった。

とてもたくさんあるオプション試行錯誤して、ようやく手なずける事ができ、HTML ファイルの書き出しに使える気がしてきた。はまりそうな、ポイントをいくつかご紹介する。

ちなみに、XHTML を処理する事を前提にしている。

implicit_tags をオフにして、processing instruction の後ろに行かないようにする

これを オフにしないと、XHMTL の最初に有る processing instruction が一番最後に出力されてしまう。<html> タグ直下にあるコメントも一番後ろに回される。何故こんなことになるのか分からないが、マニュアルによれば、「try to deduce implicit elements and implicit end tags」 とのことなので、いろいろ気を利かせてくれて、余計なことをするようだ。

終了タグの省略をやめさせる

何を血迷っているのか、デフォルトの動作では </p> </li> などの終了タグが出力されない。 「%HTML::Element::optionalEndTag = (); 」として省略していい終了タグをないことにする。ちなみに、as_HTML メソッドの第三引数に空のハッシュリファレンスを設定しても同じ効果があるとの事。多分、こちらが正式なやり方。でも、as_HTML の第三引数をいちいち設定するのは面倒に思えたので、パッケージ変数を変えて、グローバルな動作を変更してしまうことにした。

guts を取得してから、as_HTML する

HTML::TreeBuilder のインスタンスに対して as_HTML すると、なぜか <html> タグが追加されて二重になったりする。guts メソッドで生の中身を取り出してから、as_HTML すると余計なタグがつかなくなる。

guts なんてマニュアルを斜め読みではなかなか目に留まらない。

ここが参考になった。

感謝。

as_HTML の第二引数を指定して適切に改行させる

as_HTML の第二引数にはインデント用の文字を設定することになっている。インデントぐらい無くてもいいか〜と設定する事をさぼると、インデントどころか改行までごっそり無くなる。そもそも、こういう設定はメソッドの引数ではなく、HTML::TreeBuilder のインスタンスか、パッケージ変数で指定できないものか。いちいち as_HTML で設定するのは冗長に感じる。

追記 : 以下のコードでは、DOCTYPE 宣言を出力しないという不具合が有ります。修正版を考えました。

#!/usr/bin/env perl
use strict;
use warnings;
use HTML::TreeBuilder;
use HTML::Element;

my $src = 'path/to/file.xhtml';
my $tree = HTML::TreeBuilder->new;
my %opts = ('store_comments'=>1 ,
            'store_pis'=>1 ,
            'no_expand_entities'=>1 ,
            'ignore_unknown'=>0 ,
            'no_space_compacting'=>1 ,
            # pis, <html> 直下のコメントの順番維持
            'implicit_tags'=>0,
            'ignore_ignorable_whitespace'=>0);
while (my ($k, $v) = each(%opts)) {
    $tree->$k($v);
}

$tree->parse_file($src);
$tree->eof();
%HTML::Element::optionalEndTag = (); # end tag 省略しない。

# 余計な <html> タグが追加されるので guts を使う。
# print $tree->as_HTML('&<>', '  ', {});
foreach my $c ($tree->guts()) {
    print ref $c ? $c->as_HTML('', '  ') : $c;
    # 2番目の引数をしてないと、改行が無い HTML が出力される。
}