Как я могу удалить внешние ссылки из HTML с помощью Perl?

Я пытаюсь удалить внешние ссылки из HTML-документа, но сохраняю привязки, но мне не очень везет. Следующее регулярное выражение

$html =~ s/<a href=".+?\.htm">(.+?)<\/a>/$1/sig;

будет соответствовать началу тега привязки и концу тега внешней ссылки, например.

<a HREF="#FN1" name="01">1</a>
some other html
<a href="155.htm">No. 155
</a> <!-- end tag not necessarily on the same line -->

так что я ничего не получаю вместо

<a HREF="#FN1" name="01">1</a>
some other html

Так уж получилось, что все привязки имеют свой атрибут href в верхнем регистре, поэтому я знаю, что могу выполнить сопоставление с учетом регистра, но я не хочу полагаться на то, что это всегда будет иметь место в будущем.

Могу ли я что-то изменить, чтобы он соответствовал только одному тегу a?


person Mark    schedule 21.10.2009    source источник
comment
О, как у меня болит мозг, когда я вижу другое. Как мне использовать регулярные выражения для синтаксического анализа HTML? вопрос. Посмотрите на stackoverflow.com/questions/701166/ и stackoverflow .com / questions / 773340 /stackoverflow.com/questions/487213/ в вашем случае), прежде чем продолжить.   -  person Chris Lutz    schedule 21.10.2009
comment
В общем случае да, регулярное выражение на самом деле не предназначено для синтаксического анализа XML / HTML. Тем не менее, если проблемное пространство ограничено, это может быть жизнеспособным вариантом.   -  person Amber    schedule 21.10.2009
comment
Также обратите внимание, что одна из самых больших проблем при попытке использовать регулярное выражение для синтаксического анализа XML / HTML заключается в том, что для этого обычно требуется рекурсивный синтаксический анализ; теги привязки - удобное исключение, поскольку привязки не могут быть вложенными.   -  person Amber    schedule 21.10.2009
comment
В этом случае это просто - обычно я бы высказал возражение по поводу вложенных скобок, но ни одно из приведенных регулярных выражений не сильно пострадает от этого. Я озвучу изменение требований, и ваше регулярное выражение может стать неподдерживаемым, если вы попытаетесь адаптировать его к возражению против новых требований, хотя просто для полноты.   -  person Chris Lutz    schedule 21.10.2009
comment
Это превращается в comp.lang.perl.misc. Я уверен, что все мы хотим продемонстрировать наш опыт, но не могли бы мы рассмотреть слабую возможность того, что кому-то просто нужно быстрое регулярное выражение для разового редактирования, вместо того, чтобы дать нам всем возможность продемонстрировать нашу в- глубокие знания?   -  person    schedule 21.10.2009
comment
Здесь есть отличная статья Марка Джейсона Доминуса: perl.plover.com/yak /12views/samples/notes.html Давайте не будем забывать о достоинствах Perl. Он хорош для взаимодействия с другими программами и для быстрого прототипирования. Давайте не будем беспокоить людей, когда они используют Perl так, как он был разработан.   -  person    schedule 21.10.2009
comment
@Kinopiko MJD говорит о вызове внешних программ через system: см. perl.plover.com /yak/12views/samples/slide003.html   -  person Sinan Ünür    schedule 21.10.2009
comment
Нет, он об этом говорит: моя претензия не столько к самой позиции ... сколько к догматизму и легкомыслию, с которым она провозглашается.   -  person    schedule 21.10.2009
comment
Догматизм и легкомыслие - вот все, что я здесь вижу.   -  person    schedule 21.10.2009
comment
@Kinopiko Вы не можете игнорировать то, что обсуждает MJD. Кроме того, MJD не защищает метод, который дает неправильные результаты. Кроме того, я полностью согласен с MJD относительно вызова внешних команд. Теперь люди на этой странице, которые возражают против синтаксического анализа HTML с помощью регулярных выражений, привели разумные, продуманные аргументы, показывающие, насколько легко этот метод дает сбой. Вы случайно игнорируете все эти аргументы, утверждая, что это догма. Ну что ж. books.google. ru /   -  person Sinan Ünür    schedule 21.10.2009
comment
@Kinopiko, Сигареты вызывают рак. Человеческая деятельность играет важную роль в изменении климата. У людей и обезьян есть общий предок. Многие утверждают, что эти факты - всего лишь догма. Но это не меняет того факта, что они верны. Анализировать HTML с помощью регулярных выражений - плохая идея. Иногда чем хуже, тем лучше, но в этом случае, полагаясь на регулярное выражение, возникают проблемы - упс, у нас была ссылка на файл .php, упс, вот ссылка на .cgi, ad naseum - так что регулярное выражение растет, еще более неуклюжий и всегда сломанный. Подход с использованием настоящего парсера проще написать (исправить код), легче поддерживать и легче понять.   -  person daotoad    schedule 21.10.2009
comment
Именно такие комментарии я имею в виду под догматизмом. По какой-то причине вы решили, что приведенный выше сценарий является своего рода мощным анализатором HTML, который необходимо поддерживать в далеком будущем и который должен быть достаточно надежным для обработки любого возможного типа ввода HTML. Может быть, это так, но, может быть, это всего лишь одноразовый продукт, который больше никогда не будет использоваться. Догматизм, в котором я вас обвиняю, предполагает первое и настаивает на полномасштабном решении, которое может быть совершенно неуместным.   -  person    schedule 27.10.2009


Ответы (5)


Повторяя комментарий Криса Лутца, я надеюсь, что следующее показывает, что действительно просто использовать синтаксический анализатор (особенно если вы хотите иметь возможность иметь дело с вводом, который вы еще не видели, например <a class="external" href="...">), вместо того, чтобы собирать хрупкие решения с использованием s///.

Если вы собираетесь пойти по пути s///, по крайней мере, будьте честны, полагайтесь на href атрибуты, написанные полностью в верхнем регистре, а не создавайте иллюзию гибкости.

Изменить: По многочисленным просьбам ;-), вот версия с использованием HTML :: TokeParser :: Simple. Просматривайте историю изменений для версии, используя только HTML :: TokeParser.

#!/usr/bin/perl

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

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

while ( my $token = $parser->get_token ) {
    if ($token->is_start_tag('a')) {
        my $href = $token->get_attr('href');
        if (defined $href and $href !~ /^#/) {
            print $parser->get_trimmed_text('/a');
            $parser->get_token; # discard </a>
            next;
        }
    }
    print $token->as_is;
}

__DATA__
<a HREF="#FN1" name="01">1</a>
some other html
<a href="155.htm">No. 155
</a> <!-- end tag not necessarily on the same line -->
<a class="external" href="http://example.com">An example you
might not have considered</a>

<p>Maybe you did not consider <a
href="test.html">click here >>></a>
either</p>

Вывод:

C:\Temp> hjk
<a HREF="#FN1" name="01">1</a>
some other html
No. 155 <!-- end tag not necessarily on the same line -->
An example you might not have considered

<p>Maybe you did not consider click here >>>
either</p>

NB: решение на основе регулярных выражений, которое вы отметили как "правильное", не работает, если файлы, которые связаны, имеют расширение .html, а не .htm. Учитывая это, я считаю, что ваша забота о том, чтобы не полагаться на верхний регистр HREF атрибутов, неоправданна. Если вы действительно хотите быстро и грязно, вам не следует беспокоиться ни о чем другом, а следует полагаться на все заглавные буквы HREF и покончить с этим. Однако, если вы хотите, чтобы ваш код работал с гораздо большим количеством документов и дольше, вам следует использовать соответствующий синтаксический анализатор.

person Sinan Ünür    schedule 21.10.2009
comment
+1 за пример, который он, возможно, не рассматривал. Это то, что должно было быть моим аргументом против регулярных выражений в данном случае. - person Chris Lutz; 21.10.2009
comment
Я не понимаю, насколько это просто. У вас есть код, который во многом зависит от знания справочной системы Perl, и в нем есть несколько магических чисел без каких-либо объяснений. Кроме того, он полагается на другой модуль, а модули CPAN часто не имеют четкой документации. Ваше решение компактно, но далеко не так просто, если только вы не являетесь экспертом по Perl. - person ; 21.10.2009
comment
@Kinopiko: 1. Это правильно, в отличие от вашего решения, которое ломается в любом количестве ситуаций. 2. Код должен быть доступен для чтения кем-то компетентным. Ссылки не являются препятствием для входа. Полное понимание ссылок намного проще для новичка, чем полное понимание регулярных выражений. 3. Я бы предпочел HTML::TokeParser::Simple из-за его более удобочитаемого интерфейса, но если вы не можете потратить немного времени на просмотр документации, вы снова проиграете. - person hobbs; 21.10.2009
comment
И 4. Модуль используется, потому что еще раз это нетривиальная проблема. Если вы относитесь к этому как к тривиальной проблеме, вы получаете решение, которое является неправильным, как на исходном плакате, так и на вашем. Модуль, подходящий для этой задачи, почти гарантированно будет менее глючным. - person hobbs; 21.10.2009
comment
Я согласен с тем, что магические числа сбивают с толку. Это свойство HTML :: TokeParser, а не общее свойство не анализировать с помощью регулярных выражений. Использование XML :: LibXML реализации модели W3C DOM было бы более ясным, но более подробным. - person jrockway; 21.10.2009
comment
Моим первым инстинктом обычно является HTML :: TreeBuilder, но я ценю использование потокового парсера, а не DOM (или псевдо-DOM) там, где это позволяет проблема. Еще одна приятная вещь в TokeParser :: Simple - это то, что он значительно упрощает круговое переключение :) - person hobbs; 21.10.2009
comment
@jrockway С другой стороны, в документации очень четко указано, что это за числа search.cpan.org/perldoc/HTML::TokeParser::Simple#DESCRIPTION - person Sinan Ünür; 21.10.2009
comment
Модули CPAN часто не имеют четкой документации. И поэтому вам никогда не следует рассматривать использование модуля, если вы умеете быстро работать с регулярным выражением? Вау! - person innaM; 21.10.2009
comment
Кого волнует, что модули CPAN часто что-то делают? Имеет значение только то, что вам нужен модуль CPAN. - person brian d foy; 21.10.2009

Парсер типа SAX немного больше похож на HTML::Parser:

use strict;
use warnings;

use English qw<$OS_ERROR>;
use HTML::Parser;
use List::Util qw<first>;

my $omitted;

sub tag_handler { 
    my ( $self, $tag_name, $text, $attr_hashref ) = @_;
    if ( $tag_name eq 'a' ) { 
        my $href = first {; defined } @$attr_hashref{ qw<href HREF> };
        $omitted = substr( $href, 0, 7 ) eq 'http://';
        return if $omitted;
    }
    print $text;
}

sub end_handler { 
    my $tag_name = shift;
    if ( $tag_name eq 'a' && $omitted ) { 
        $omitted = false;
        return;
    }
    print shift;
}

my $parser
    = HTML::Parser->new( api_version => 3
                       , default_h   => [ sub { print shift; }, 'text' ]
                       , start_h     => [ \&tag_handler, 'self,tagname,text,attr' ]
                       , end_h       => [ \&end_handler, 'tagname,text' ]
                       );
$parser->parse_file( $path_to_file ) or die $OS_ERROR;
person Axeman    schedule 21.10.2009
comment
+1 Кстати, см. perlfoundation.org/perl5/ на Smart::Comments. Я не уверен, что чувствую это сильно, но в целом я не поклонник исходных фильтров. - person Sinan Ünür; 21.10.2009
comment
@Sinan Ünür: обычно я удаляю код отладки из своих готовых ответов. Вот где блестит Smart :: Comments - отладочный код. - person Axeman; 21.10.2009
comment
Это неплохо, но в конечном итоге полагается на другое регулярное выражение. Однако HTML :: Parser предоставит вам атрибуты и их значения, если вы вежливо попросите. - person innaM; 21.10.2009
comment
@Manni: Согласитесь - и я знал, что это так, но я не хотел писать сложный канал, когда менял тег - но это лучшее решение, если я ничего не пишу. Я собираюсь это изменить. - person Axeman; 21.10.2009

Еще одно решение. Мне нравится HTML :: TreeBuilder и семья.

#!/usr/bin/perl
use strict;
use warnings;
use HTML::TreeBuilder;

my $root = HTML::TreeBuilder->new_from_file(\*DATA);
foreach my $a ($root->find_by_tag_name('a')) {
    if ($a->attr('href') !~ /^#/) {
        $a->replace_with_content($a->as_text);
    }
}
print $root->as_HTML(undef, "\t");

__DATA__
<a HREF="#FN1" name="01">1</a>
some other html
<a href="155.htm">No. 155
</a> <!-- end tag not necessarily on the same line -->
<a class="external" href="http://example.com">An example you
might not have considered</a>

<p>Maybe you did not consider <a
href="test.html">click here >>></a>
either</p>
person Leonardo Herrera    schedule 22.10.2009
comment
Документация для TreeBuilder и друзей могла бы быть немного больше ... вместе, но я согласен, это очень хороший способ работы с HTML, когда вы к нему привыкнете. - person r0berts; 25.12.2015

Почему бы просто не удалить только те ссылки, для которых атрибут href не начинается со знака решетки? Что-то вроде этого:

html =~ s/<a href="[^#][^"]*?">(.+?)<\/a>/$1/sig;
person Amber    schedule 21.10.2009
comment
Не обрабатывает голые ссылки - я знаю, что голые ссылки грубые, и вы никогда не найдете их в HTML, который я пишу или для которого пишу генератор, но они и атрибуты в одинарных кавычках соответствуют спецификации. - person Axeman; 22.10.2009

Еще проще, если вам не нужны атрибуты тегов:

$html =~ s/<a[^>]+>(.+?)<\/a>/$1/sig;
person Павел П    schedule 24.11.2016