Как я могу удалить все теги шрифтов, которые содержат только пробелы, используя Perl? - PullRequest
2 голосов
/ 18 сентября 2010

Я пытаюсь найти соответствие в 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>
 

Ответы [ 5 ]

11 голосов
/ 18 сентября 2010

Если вы используете 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 , но у меня нет времени, чтобы написать пример прямо сейчас (и теперь, когда я это делаю, Грег Бэкон уже сделал это ). Я не показываю вам, как выполнить эту конкретную задачу в моей Обработка HTML с помощью статьи Perl Module для InformIT, но большинство из них есть.

5 голосов
/ 18 сентября 2010

Обязательное предупреждение: Вы не должны использовать регулярное выражение для разбора HTML .


Хотя .*? ленив, это не означает, что совпадение не будет успешным,В $ text1,

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

можно сопоставить <font(.*?)>[\t\f ]*<\/font>, если .*? соответствует " color="#008080"><span style="background: #ffffff"></span".Это самое короткое совпадение , которое приведет к совпадению успешно .

Если вы хотите остановиться на первом >, используйте

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

Предполагается, что > не появится внутри тега <font>.(Пример нарушения: <font onclick="return 1>2"></font>.)

4 голосов
/ 18 сентября 2010

В приведенном ниже коде используется модуль 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 в качестве предиката для look_down метода HTML :: Element , чтобы найти <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";
}
2 голосов
/ 18 сентября 2010

Мне действительно нравится 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>
0 голосов
/ 18 сентября 2010
s/<font[^>]*>\s*<\/font>//gi;

Нежадный .*? пытается использовать минимальное количество символов, но для достижения общего соответствия потребуется столько же, сколько необходимо. Если вы замените его на [^>]*, > должен соответствовать самому следующему >, или попытка совпадения не удалась.

Имейте в виду, что > допускается в значениях атрибутов, поэтому это решение не гарантируется на 100%. К счастью, люди, которые знают об этой маленькой лазейке, также достаточно разумны, чтобы не использовать ее; Я никогда не видел угловую скобку в значении атрибута в дикой природе.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...