29 December 2005
This post may be outdated due to it was written on 2005. The links may be broken. The code may be not working anymore. Leave comments if needed.
HTML::Parser 是一个非常强大的用于处理 html 解析的模块。

HTML::Parser 的文档没有一个完整的例子。所以我就把下面的我写在 ShellWeb 里的例子摘取出来,并简单的解释一下。

use HTML::Parser;

my $parser = HTML::Parser->new( api_version => 3,
   start_h => [\&start, "self, tagname, attr, text"],
   text_h => [\&text, "self, dtext"],
   end_h => [\&end, "self, tagname"],
   ignore_elements => [qw(script style)],
);

$parser->parse($content);

new 用于建立实例,api_version => 3 用于指定版本,因为 HTML::Parser 还有老的版本 2,可能以后还会有新的版本。而不同的版本有不同的 API, 所以指定这个是必须的。
start_h, text_h, end_h 是三个事件处理过程。分别对应标签的开始,文字(就是没有标签的地方但不包括注释等),标签的关闭。

其实完整的事件处理包括 text, start, end, declaration, comment, process, start_document, end_document 和 default.
一般用到的只有 text start end.

    指定事件所对应的处理方法和参数有如下几种方法:
  • 第一种就是如上面所看到的,在事件后面加上 _h, 如 text_h, start_h 这样。
  • 第二种也是在 new 里做为参数。
    $p = HTML::Parser->new(api_version => 3,
    handlers => { text => [\@array, "event,text"],
    comment => [\@array, "event,text"],
    });
    用 handlers 来定义。
  • 第三种是在 new 里不定义,在后面用 handler 来定义。
    $p = HTML::Parser->new(...);
    $p->handler(start => \&start, 'attr, attrseq, text' );
这三者接受参数的方法是一样的。第一个为处理方法,第二个为参数。
完整的参数参考 perldoc HTML::Parser
最最常用的为 self, tagname, attr, text
不过注意的是不同的事件有不同的参数限制。如果 start 事件可以有 attr 但是 end 事件就没有 attr 了。(因为结束标签里是不会有属性的。)

如最开始的例子中我们使用了 start_h => [\&start, "self, tagname, attr, text"],
那么接下来我们就定义我们自己的 start 子程序:

sub start {
   my ($self, $tag, $attr, $text) = @_;
   if ($tag eq 'img') {
       $buffer->insert($iter, "[IMG - $attr->{src}]");
   } elsif ($tag eq 'pre') {
       $self->{'_pre'} = 1;
   } elsif ($tag eq 'title') {
       $self->{'_tilte'} = 1;
   } else {
       $buffer->insert($iter, $text);
   }
}
start 子程序的第一行就是 start_h => [\&start, "self, tagname, attr, text"], 后面所对应的参数。分别对照为 self, 标签名(如 a/img 等),属性(这是一个散列,比如 img 必定有 $attr->{src}),text 指的是整个标签的完整原始内容。
看起来就是这么简单。接下来定义 text end 子程序:
sub text {
   my ($self, $text) = @_;
   $text =~ s/\r\n//sg unless ( defined($self->{'_pre'}) );
   if ( defined($self->{'_tilte'}) ) {
       $window->set_title("$text - ShellWeb");
   } else {
       $buffer->insert($iter, $text);
   }
}

sub end {
   my ($self, $tag) = @_;
   if ($tag eq 'li') {
       $buffer->insert($iter, "\n");
   } elsif ($tag eq 'p') {
       $buffer->insert($iter, "\n");
   }
   $self->{'_tilte'} = undef if ($tag eq 'title');
   $self->{'_pre'} = undef if ($tag eq 'pre');
}

这个完整的程序中间我们加了一点点小小的技巧。因为 text 事件是不知道这个文本外面的标签名的(也就是 text_h 没有 tagname 参数)。
这样我们必须在 title/pre 标签的开始时用 $self->{'_title'} 保存它开始了,在 end 时将它释放。
这样我们就在 text 事件中知道刚文本是不是包围在 title 或 pre 里面。

上面就是大致的 HTML::Parser 分析过程。最重要的是实践,自己设定个结果,然后用 HTML::Parser 实现它。
实现不了出了问题看 perldoc HTML::Parser 或者给我发 mail. ;-)



blog comments powered by Disqus