2010-08-21 12 views
1

用正則表達式解析HTML是一個壞主意,但它似乎適用於這種情況。Perl正則表達式僅向前解析;不是從頭開始

描述:給定一個.html文件,我必須解析內部鏈接,將縮進級別,鏈接的文本和它所在的頁碼提取到外部.txt文件,然後傳遞給其他人。

所以給這個樣本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

(因爲它們是實際的頁面數,而不是頁碼是不同對開頁參考)。

除了第一部分,當鏈接的文本包含額外的HTML代碼,如第一個鏈接中的<Font>標記時,我主要想到了這一點。

這裏是我的正則表達式來提取鏈接(注$字符串包含上面的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)); 
}; 

這將正確地提取第二個,但不是第一,因爲> <和其他符號的在HTML代碼中。

如果我改變,去年的捕獲組.+.*,我得到了完整的HTML文件(當然,第一<Div><A>和最後</A>之間,似乎圖案從頭開始,而是從終端匹配將文件的向後

這裏是一個鏈接到一個在線的正則表達式生成器:http://regexr.com?2s0po
它正確地找到我所需要的,但在Perl我沒有得到相同的結果(就像提到的整個文件)

我似乎無法寫出任何會捕捉每個組的東西p正確 - 你會認爲「光標」會向前移動,並停在從文件開頭看到的第一個</A>

任何幫助或意見或指導將不勝感激。 -謝謝。

+8

這是一個使用HTML解析器的完美場景。正則表達式完全是錯誤的工具。我不知道Perl HTML解析器的風景,但有人應該能夠向你推薦一些東西。 – 2010-08-21 02:01:19

回答

3

解析HTML或類似結構時,你必須小心正則表達式。沒有與你想要的正則表達式的兩個問題:

  1. 嵌套的標籤(在第一項的字體標籤)
  2. 換行符(第一關閉錨標記之前)

這裏有一個正則表達式處理那些:

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"; 
} 
+0

謝謝!這是一個完美的例子 - 由於缺少外部模塊,我必須使用我自己的html實體功能,否則這是現場! – WSkid 2010-08-21 06:32:06

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
+0

謝謝,不幸的是,由於軟件限制,我無法在生產機器上使用非核心模塊,但是我在開發機器上測試了這個解決方案,並且它在那些在正常環境中查看此問題的人員工作得非常好。 – WSkid 2010-08-21 06:33:25

+0

@WSkid不客氣。我很高興你能解決你遇到的問題。 – 2010-08-21 11:05:01

1

你可以嘗試使用進行非貪婪匹配3210或.*?,以防止誹謗文件的其餘部分。

+0

啊,謝謝 - 我知道我看起來這麼簡單! – WSkid 2010-08-21 06:31:24