読者です 読者をやめる 読者になる 読者になる

$shibayu36->blog;

株式会社はてなでエンジニアをしています。プログラミングや読書のことなどについて書いています。

HTML::Parserを使って特定タグの属性を置き換える

perl tech

最近、imgタグのsrcだけ書き換えたいという事例が発生したので、HTML::Parserを使ってやってみた。

サンプル

例えばサンプルとして以下のコード。

package HTML::Converter;

use utf8;
use strict;
use warnings;

use parent qw(HTML::Parser);

# HTMLのimg srcを変換する
# my $converter = HTML::Converter->new;
# my $result    = $converter->convert($html);

sub new {
    my ($class, $opts) = @_;
    my $self = $class->SUPER::new(
        api_version => 3,
        start_h     => [ \&_start_tag, "self,tagname,attr,text" ],
        default_h   => [ \&_default,   "self,text" ],
    );

    $self->{converted_html} = '';
    $self->{prefix_url} = $opts->{prefix_url};

    return $self;
}

sub convert {
    my ($self, $html) = @_;

    $self->{converted_html} = '';
    $self->parse($html);

    return $self->{converted_html};
}

sub prefix_url {
    my ($self) = @_;
    return $self->{prefix_url};
}

sub _start_tag {
    my ($self, $tagname, $attr, $text) = @_;

    # img以外はそのまま通す
    if ($tagname ne 'img') {
        $self->{converted_html} .= $text;
        return;
    }

    $self->{converted_html} .= "<$tagname";

    if ($attr->{src}) {
        $attr->{src} = $self->prefix_url . $attr->{src};
    }
    for my $attr_name (keys %$attr) {
        next if $attr_name eq '/';

        my $attr_val = $attr->{$attr_name};
        $self->{converted_html} .= qq{ $attr_name="$attr_val"};
    }

    $self->{converted_html} .= ' /' if $attr->{'/'};
    $self->{converted_html} .= '>';
}

sub _default {
    my ($self, $text) = @_;
    $self->{converted_html} .= $text;
}

1;


以下のように使う。

my $converter = HTML::Converter->new({
    prefix_url => 'http://example.com',
});
my $html = $converter->convert(q[
<a href="">
  <img src="/example.png" />
  example
</a>]);

warn $html;

そうするとimgのsrcがhttp://example.com/example.pngのようになる。

やっていること

HTML::Parserはタグが開始した時というイベントで、その時パースしているタグの情報を取得できるので、それを使えば特定のタグの中身だけ書き換えたHTMLを作ることが出来る。

以下のようにstart_hで指定することでタグが開始した時に実行する関数を定義できる。

my $parser = HTML::Parser->new(
    api_version => 3,
    start_h     => [ sub { ... }, "self,tagname,attr,text" ],
);

start_hのリストの一番目に実行したい関数を渡して、二番目にどういう引数を渡して欲しいか書く。今回はタグ名とか属性のハッシュとか、生のhtmlを渡してもらうようにする。

そして以下のように関数を定義すれば、それらを使ってごちゃごちゃ出来る。

sub _start_tag {
    my ($self, $tagname, $attr, $text) = @_;

    warn $tagname;
    # ...
}

あとはサンプルのようにタグ名や属性を使ってHTMLを書き換えて行ったら良い。

まとめ

今回はHTML::Parserを使ったHTMLの変換の話について書きました。たまにこういうのが必要になるので勉強になりました。