2010-01-26

Perl で HTML ファイルの内容を書き換える

ネットからダウンロードした Web ページの内容を少しだけエディタで書き換えることがよくある,これを Perl を使ってプログラムからやるにはどのようにすればよいだろうか.

HTML::TreeBuilder, HTML::Element あたりを使うことになるだろう.

HTML をパーズし,HTML に戻す

まずは単に HTML をパーズし,HTML に戻すプログラムを書いてみよう.

use HTML::TreeBuilder::XPath;
my $html = HTML::TreeBuilder::XPath->new_from_file('sample.html');
print $html->as_HTML('&<>');

as_HTML() の引数としてエンティティ化する文字のリストを明示的にを渡さないと,漢字などは全てエンティティにされてしまうので注意.

ここで問題となるのは $html->as_HTML('&<>') の文字コードは何なのか,そもそも sample.html の文字コードの扱いはどうなっているのかということである.HTML::TreeBuilder や HTML::Element には文字コードに関する記述は無い.

sample.html の文字コードを変えていろいろやってみたところ,どうやら入力をそのまま出力しているようである.当然 utf8::is_utf8($html->as_HTML('&<>')) は偽となる.

sample.html の文字コードを UTF-16 にするとうまくいかなかったので,結局タグが ASCII である文字コードのときにうまくいくだけのようだ.

あちこち検索などした結果,求める情報は man HTML::Parser の METHODS > Unicode にあることが判明した.これによると utf8 文字列なら扱えるとあるので,次に得られた文字列を utf8 文字列に変換する方法について書くことにする.

文字コードが不明の文字列を utf8 文字列に変換する

perl の utf8 文字列についての詳しい話は別の場所を参照してほしい.

ここでは Encode::Guess を用いて,文字コードが不明の文字列を utf8 文字列に変換する方法について書くことにする.

Encode::Guess の guess_encoding 関数を用いて以下のように書けばよい.

use Encode::Guess;

sub content_of_file {
    my ($fname) = @_;
    open my $fh, '<', $fname;
    local $/;
    my $content = <$fh>;
    close $fh;
    return $content;
}

while () {
    my $content = content_of_file($_);
    my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
    print "$_ => ", $decoder->name(), "\n";
    my $decoded = $decoder->decode($content);
}

HTML をパーズし,HTML に戻す (again)

これまで説明してきたことを合体すれば HTML をパーズし,HTML に元の文字コードのまま戻すコードを書くことができる.

use Encode::Guess;
use HTML::TreeBuilder::XPath;

sub content_of_file { ... }

my $content = content_of_file('sample.html');
my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
my $decoded = $decoder->decode($content);
my $html = HTML::TreeBuilder::XPath->new_from_content($decoded);
open my $fh, '>', "/tmp/sample.html";
print $fh $decoder->encode($html->as_HTML('&<>'));
close $fh;

HTML の書き換え

HTML を書き換えるには,delete_content(), push_content(), attr() などを用いる.

use utf8;

use Encode::Guess;
use HTML::TreeBuilder::XPath;

sub content_of_file { ... }

my $content = content_of_file('sample.html');
my $decoder = guess_encoding($content, qw{shift_jis euc-jp jis});
my $decoded = $decoder->decode($content);
my $html = HTML::TreeBuilder::XPath->new_from_content($decoded);

my $nodeset = $html->findnodes('//a[@href]');
for my $element (grep { $_->attr('href') =~ /^index\.html$/ &&
                        $_->look_down('_tag', 'img'); }
                 $nodeset->get_nodelist()) {
    my $href = $element->attr('href');
    $href =~ s/index\.html/default.html/;
    $element->attr('href', $href);
    $element->delete_content();
    $element->push_content('画像は削除されました.');
}

open my $fh, '>', "/tmp/sample.html";
print $fh $decoder->encode($html->as_HTML('&<>'));
close $fh;

情報源

0 件のコメント:

コメントを投稿