Perl正则表达式只向前解析;不是最终开始

时间:2010-08-21 01:48:29

标签: html regex perl

使用正则表达式解析HTML是一个坏主意,但它似乎适合这种情况。

描述:给定.html文件,我必须解析内部链接,提取缩进级别,链接文本及其驻留的页码到外部.txt文件,然后传递给其他人。< / p>

所以给出了这个示例HTML:

<TR valign="bottom">
    <TD valign="top"><DIV style="margin-left:0px; text-indent:-0px"><A href="#101"><FONT style="font-variant:small-caps;">The &#147;Offering&#147;</FONT>
</A></DIV></TD>
    <TD>&nbsp;</TD>
    <TD nowrap align="right" valign="top">&nbsp;</TD>
    <TD align="right" valign="top">1</TD>
    <TD nowrap valign="top">&nbsp;</TD>
</TR>
<TR valign="bottom">
    <TD valign="top"><DIV style="margin-left:15px; text-indent:-0px"><A href="#102">Sales &#038; Property
</A></DIV></TD>
    <TD>&nbsp;</TD>
    <TD nowrap align="right" valign="top">&nbsp;</TD>
    <TD align="right" valign="top">2</TD>
    <TD nowrap valign="top">&nbsp;</TD>
</TR>

外部文件将产生:

0|The "Offering"|4
15|Sales & Property|5

(页码不同,因为它们是实际页码,而不是作品集参考号。)

除了1部分,当链接文本包含额外的HTML代码(例如第一个链接中的<Font>标记)时,我才会想到这一点。

这是我提取链接的正则表达式(注意$ string包含上面的html):

while ($string =~ m/<DIV style="margin-left:([0-9]+)px; text-indent:[-0-9]+px"><A href="#([0-9]+)">([a-zA-Z0-9\.,:;&#\s]+)<\/A>/gi) {
    push(@indents,$1);
    push(@linkIDs,$2);
    push(@names,escapeHTML($3));
};

这将正确地提取第二个,但不是第一个,因为&gt;&lt;和HTML代码中的其他符号。

如果我将最后一个捕获组更改为.+.*,我会获得整个HTML文件(在第一个<Div><A>和最后一个</A>之间。似乎模式从头开始,但从文件末尾向后匹配。

以下是在线正则表达式构建器的链接:http://regexr.com?2s0po
它正确地找到了我需要的东西,但是在Perl中我没有得到相同的结果(只是提到的整个文件)。

我似乎无法编写任何可以正确捕获每个组的内容 - 您会认为“光标”会向前移动并停在它从文件开头看到的第一个</A>

非常感谢任何帮助或意见或指导。 - 谢谢你。

3 个答案:

答案 0 :(得分:3)

解析HTML或类似结构时,必须小心使用正则表达式。你正在尝试的正则表达式有两个问题:

  1. 嵌套标签(第一个条目中的font-tag)
  2. 换行符(在第一个关闭锚标记之前)
  3. 这是一个处理这些问题的正则表达式:

    use HTML::Entities;
    while ($string =~ m/<DIV style="margin-left:([0-9]+)px; text-indent:[-0-9]+px"><A href="#([0-9]+)">(.*?)<\/A>/gis) {
        my $indent = $1;
        my $page = $2;
        (my $name = $3) =~ s/\s+$//;
        $name =~ s/^\s+//;
        $name =~ s/<.*?>//g;
        print $indent, '|', decode_entities($name), '|', $page, "\n";
    }
    

答案 1 :(得分:2)

我不会用正则表达式来做这件事。

例如,使用HTML::TreeBuilder,您可以使用

构建树
#! /usr/bin/perl

use warnings;
use strict;

use HTML::TreeBuilder;
use HTML::TreeBuilder::XPath;

my $root = HTML::TreeBuilder->new_from_content(<<'EOHTML');
<TR valign="bottom">
    <TD valign="top"><DIV style="margin-left:0px; text-indent:-0px"><A href="#101"><FONT style="font-variant:small-caps;">The &#147;Offering&#147;</FONT>
</A></DIV></TD>
    <TD>&nbsp;</TD>
    <TD nowrap align="right" valign="top">&nbsp;</TD>
    <TD align="right" valign="top">1</TD>
    <TD nowrap valign="top">&nbsp;</TD>
</TR>
<TR valign="bottom">
    <TD valign="top"><DIV style="margin-left:15px; text-indent:-0px"><A href="#102">Sales &#038; Property
</A></DIV></TD>
    <TD>&nbsp;</TD>
    <TD nowrap align="right" valign="top">&nbsp;</TD>
    <TD align="right" valign="top">2</TD>
    <TD nowrap valign="top">&nbsp;</TD>
</TR>
EOHTML

然后使用HTML::TreeBuilder::XPath提取链接和缩进:

sub all_text {
  my($root) = @_;

  ref $root
    ? join "" => map all_text($_) => $root->content_list
    : $root;
}

foreach my $div ($root->findnodes('/html/body//div[.//a]')) {
  my $indent =
    $div->attr('style') =~ /\bmargin-left:\s*(\d+)/ ? $1 : 0;

  foreach my $a ($div->findnodes('.//a')) {
    (my $text = all_text $a) =~ s/\s+\z//;
    print "$indent|$text|FIXME\n";
  }
}

输出:

0|The �Offering�|FIXME
15|Sales & Property|FIXME

答案 2 :(得分:1)

您可以尝试使用.+?.*?进行非贪婪的匹配,以防止其对文件的其余部分进行诽谤。