Perl:从字符串中删除剩余的锚标记

Perl: Removing leftover anchor tags from a string

我编写了一个脚本来检查 HTML 文件中的所有 URL,并使用 HTTP::Tiny 删除它们并删除,但它在整个过程中留下了一堆不完整的锚标记,如 <a>text here</a>数据库。这些更改已经完成,脚本不需要再次 运行。

脚本从存储此信息的数据库中读取。

现在的目标是去除 <a>text here</a>,同时将缓冲区中的所有其他内容以及“此处的文本”文本保留下来。

我编写了另一个读取数据库并包含以下正则表达式的脚本:

$html_buffer =~ s/(.*?)<a>(.*?)<\/a>(.*?)//g;

但它不起作用,我不知道为什么。这是一个示例缓冲区:

This week, perhaps the most interesting articles include &quot;<a>Finding \r\n  that Windows is superior to Linux is biased</a>,&quot; &quot;<a href=\"http://www.example.com/content/view/118693\">How \r\n  to set up DNS for Linux VPNs</a>,&quot; and &quot;<a href=\"http://www.example.com/content/view/118664
\">Writing \r\n  an Incident Handling and Recovery Plan</a>.&quot;

当然我希望正则表达式在整个字符串中运行,但也匹配字符串中的多次出现。

这是最有效的方法吗?我怎样才能确保不像上面的例子那样删除字符串末尾的 </a>

原则上,始终建议使用合适的解析器。第二部分有一个例子


正则表达式需要 /s 修饰符,以便 . 也匹配换行符,然后它就可以工作了。没有它,模式 .*? 会在换行处停止。只是碰巧正则表达式仍然设法匹配一些字符串,看起来“有效但不正确”。

要点是,如果该字符串被分配为 double-quoted

,则其中包含 \ns 的字符串具有换行符
my $html_buffer = " ... ";   # or:  = qq(...);

因为 \n 被解释为换行符。如果该字符串用单引号给出,

my $html_buffer = ' ... ';   # or:  = q(...);

然后里面就没有换行符了,只是偶尔出现的字符 \n 一个接一个,而 .*? 按预期工作。

最后:替换运算符仅影响已匹配的字符串部分(或更少,如果模式被写成“删除”一些匹配项,例如 \K),所以没有理由前导和尾随 (.*?)。只需要

$html_buffer =~ s{<a>(.*?)</a>}{}sg;

为了安全起见,在所有这些模式中添加一点额外的空格,<\s*a\s*> 所以

$html_buffer =~ s{<\s*a\s*>(.*?)<\s*/a\s*>}{}sg;

请注意,我发现修复原始脚本的解决方案要好得多,这样它就不会留下意外的点点滴滴。我敢猜测,这是通过对 HTML 使用正则表达式发生的吗?令人惊讶的是,任何为解析 HTML/XML 而编写的主要库都会像那样削弱(我认为有效)HTML。

另一方面,现在也可以做到这一点。如果剩余的文本及其 not-links(没有 href 属性的 <a> 在技术上不是超链接)非常简单,那么使用正则表达式(手指交叉)可能会更简单;就这一次。

在所有其他情况下,这里有一个非常基本的 Mojo::DOM

use warnings;
use strict;
use feature 'say';

use Mojo::DOM; 

my $html = q(<p> a link: <a>no href</a>, not. <p> OK: <a href="#">hoho</a>);

my $dom = Mojo::DOM->new( $html );    
say $dom; 

foreach my $link ($dom->find("a")->each) { 

    if (not defined $link->attr->{href}) {
        # Replace this node with its text
        $link->replace( $link->text );
    }

}
say $dom;  # object stringifies to just its HTML

(或 $link->strip; 删除 HTML 元素但保留其内容,结果相同)

这会打印

<p> a link: <a>no href</a>, not. </p><p> OK: <a href="#">hoho</a></p>
<p> a link: no href, not. </p><p> OK: <a href="#">hoho</a></p>

我使用了较短的 made-up HTML 字符串,但我也检查了问题的示例。 Mojo 还有其他方法可以做到这一点,探索它们很有趣(也很有用)。

例如,仅处理未定义 href 属性的链接,方法是先过滤

$_->replace($_->text) for 
    $dom -> find("a")
         -> grep( sub { not defined $_->attr->{href} } )
         -> each;

或者,直接处理

$dom -> find("a")
     -> each( sub { 
            $_->replace($_->text) if not defined $_->attr->{href} 
        });