2010-09-18 61 views
2

我試圖做在Perl匹配,使用以下正則表達式:如何使用Perl刪除所有隻有空白的字體標籤?

s/<font(.*?)>[\t\f ]*<\/font>//gi; 

我希望它做的是去除沒有任何東西里面所有的字體標籤。

不幸的是,它不會在<font在第一個>之後停止,直到從</font>之前的>

任何關於正則表達式錯誤的指針?

my $text1 = '<font color="#008080"><span style="background: #ffffff"></span></font>'; 
my $text2 = '<font color="#008080"> s</font>'; 
my $text2 = '<font></font>'; 
$text1 =~ s/<font(.*?)>[\t\f ]*<\/font>//gi; 
$text2 =~ s/<font(.*?)>[\t\f ]*<\/font>//gi; 
$text3 =~ s/<font(.*?)>[\t\f ]*<\/font>//gi; 
print "$text1\n$text2\n$text3\n"; 

將打印

  
<font>s</font> 
  
+3

使用正則表達式解析HTML的任何理由?您可能會使用Pearl的體面HTML解析器。來自Jamie Zawinski的引用:「有些人在遇到問題時想'我知道,我會用正則表達式'。現在他們有兩個問題。「 – 2010-09-18 11:26:21

+2

[朋友不讓朋友用正則表達式解析HTML。](http://stackoverflow.com/questions/1732348/regex-match-open-tags-except-xhtml-self-contained-tags/1732454#1732454) – Ether 2010-09-18 17:20:52

+0

嗯,它實際上不是html代碼,它是我想清理的wiki代碼。 – cristi 2010-09-19 18:26:56

回答

5

強制性警告:You shouldn't use regex to parse HTML


雖然.*?很懶,但這並不意味着它會避免一場比賽是成功的。在$文本1,

<font color="#008080"><span style="background: #ffffff"></span></font> 

可以通過具有.*?比賽" color="#008080"><span style="background: #ffffff"></span"匹配<font(.*?)>[\t\f ]*<\/font>。這是最短匹配這將導致匹配成功

如果你想停在第一>,使用

s|<font[^>]*>\s*</font>||gi 
#  ^^^^ 

這是假定>將不是<font>標籤內出現。 (例違規:<font onclick="return 1>2"></font>。)

+0

@downvoter:請解釋。 – kennytm 2010-09-18 11:45:57

+0

+1爲該問題的鏈接。 – 2010-09-18 12:14:06

11

如果使用XHTML,那麼這是很容易與XML::Twig

use XML::Twig; 

my $string = <<"HTML"; 
<?xml version="1.0"?> 
<html> 
<font color="#008080"><span style="background: #ffffff"></span></font> 
<font color="#008080"> s</font> 
<font></font> 
</html> 
HTML 

use XML::Twig; 
my $twig = XML::Twig->new( 
    pretty_print => 'nice', 
    twig_handlers => { 
     span => \&delete_empty, 
     font => \&delete_empty, 
     }, 
    ); 
$twig->parse($string); 

$twig->print; 

sub delete_empty { 
    my($twig, $element) = @_; 

    $element->delete unless $element->text =~ /\S/; 
    } 

您還可以使用HTML::Tree,但我沒有時間來寫一個例子權現在(現在我知道了,Greg Bacon has already done it)。我沒有告訴你如何在InformIT的Process HTML with a Perl Module文章中完成這項特定任務,但大部分代碼都在那裏。

2

我真的很喜歡HTML::TokeParser::Simple。因此,對於種類繁多,這裏是另一種方式:

#!/usr/bin/perl 

use strict; use warnings; 
use HTML::TokeParser::Simple; 

my $parser = HTML::TokeParser::Simple->new(\*DATA); 

while (my $stag = $parser->get_token) { 
    if ($stag->is_start_tag(qr/font|span/)) { 
     my $closer = '/' . $stag->get_tag; 
     my $text = $parser->get_text($closer); 
     my $etag = $parser->get_tag($closer); 

     if ($text =~ /\S/) { 
      $text =~ s/^\s+//; 
      $text =~ s/\s+\z//; 
      print $stag->as_is, $text, $etag->as_is; 
     } 
    } 
    else { 
     print $stag->as_is; 
    } 
} 


__DATA__ 
<h1>Test heading</h1> 
<p>Here is some <b>sample</b> <em>text</em>: <span>one</span> 
<font color="#008080"><span style="background: #ffffff"></span></font> 
<font color="#008080"> s</font> 
<font></font></p> 

<h2>A subtitle</h2> 
<p><q>this is a test</q>: ya ba da ba doo!</p> 
</body> 

輸出:

<h1>Test heading</h1> 
<p>Here is some <b>sample</b> <em>text</em>: <span>one</span> 

<font color="#008080">s</font> 
</p> 

<h2>A subtitle</h2> 
<p><q>this is a test</q>: ya ba da ba doo!</p> 
</body> 
4

下面的代碼使用HTML::TreeBuilder模塊,這是一個用於解析HTML適當的工具。正則表達式不是。

#! /usr/bin/perl 

use warnings; 
use strict; 

use HTML::TreeBuilder; 

測試用例從你的問題:

my @cases = (
    '<font color="#008080"><span style="background: #ffffff"></span></font>', 
    '<font color="#008080"> s</font>', 
    '<font></font>', 
); 

我們將使用is_empty的謂詞的HTML::Elementlook_down方法找到<font>元素,沒有有趣的內容。

sub is_empty { 
    my($font) = @_; 

    my $is_interesting = sub { 
    for ($_[0]->content_list) { 
     return 1 if !ref($_) && /\S/; 
    } 
    }; 

    !$font->look_down($is_interesting); 
} 

最後主循環。對於每個片段,我們創建一個新的HTML::TreeBuilder實例,刪除空的<font>元素,並修剪剩下的第一層文本內容。

foreach my $html (@cases) { 
    my $tree = HTML::TreeBuilder->new_from_content($html); 
    $_->detach for $tree->guts->look_down(_tag => "font", \&is_empty); 

    my $result = ""; 
    if ($tree->guts) { 
    foreach my $font ($tree->guts->look_down(_tag => "font")) { 
     $font->attr($_,undef) for $font->all_external_attr_names; 
     foreach my $text ($font->content_refs_list) { 
     next if ref $$text; 
     $$text =~ s/^\s+//; 
     $$text =~ s/\s+$//; 
     } 
    } 

    ($result = $tree->guts->as_HTML) =~ s/\s+$//; 
    } 

    print "$result\n"; 
} 

輸出:

  
<font>s</font> 

製作兩遍是馬虎。該代碼可以改進:

#! /usr/bin/perl 

use warnings; 
use strict; 

use HTML::TreeBuilder; 

my @cases = (
    '<font color="#008080"><span style="background: #ffffff"></span></font>', 
    '<font color="#008080"> s</font>', 
    '<font></font>', 
); 

foreach my $fragment (@cases) { 
    my $tree = HTML::TreeBuilder->new_from_content($fragment); 
    foreach my $font ($tree->guts->look_down(_tag => "font")) { 
    $font->detach, next 
     unless $font->look_down(sub { grep !ref && /\S/ => $_[0]->content_list }); 

    $font->attr($_,undef) for $font->all_external_attr_names; 
    foreach my $text ($font->content_refs_list) { 
     next if ref $$text; 
     $$text =~ s/^\s+//; 
     $$text =~ s/\s+$//; 
    } 
    } 

    (my $cleaned = $tree->guts ? $tree->guts->as_HTML : "") =~ s/\s+$//; 
    print $cleaned, "\n"; 
} 
+0

我需要知道:爲什麼要做$ font-> detach? – cristi 2010-10-14 20:15:56

+0

@cristi代碼使用它來刪除空的'font'元素。根據[HTML :: Element的文檔](http://search.cpan.org/~jfearn/HTML-Tree-4.0/lib/HTML/Element.pm),'$ h-> detach() ''「通過將其'parent'屬性設置爲'undef',並從其父項的內容列表中刪除它(如果它有一個),從其父項中斷開$ h'」 – 2010-10-15 00:49:07

+0

謝謝。另一個問題:爲什麼!ref在grep?這也將刪除 – cristi 2010-10-21 11:12:59

0
s/<font[^>]*>\s*<\/font>//gi; 

非貪婪.*?嘗試消耗字符的最小數目,但它會採取許多必要實現總體的比賽。如果將其替換爲[^>]*,則>必須與下一個>匹配,否則匹配嘗試失敗。

請注意,>出現在屬性值中是合法的,因此此解決方案不是100%保證。幸運的是,那些知道這個小漏洞的人也很明智,不會使用它;我從來沒有在野外的一個屬性值中看到一個尖括號。

+0

downvoter照顧解釋爲什麼? – 2010-09-19 15:46:34

相關問題