我需要使用 Perl 中的正则表达式匹配和删除所有标签。我有以下内容:
<\\??(?!p).+?>
但这仍然与结束</p>
标签匹配。关于如何与结束标签匹配的任何提示?
请注意,这是在 xhtml 上执行的。
如果你坚持使用正则表达式,在大多数情况下这样的事情会起作用:
# Remove all HTML except "p" tags
$html =~ s{<(?>/?)(?:[^pP]|[pP][^\s>/])[^>]*>}{}g;
解释:
s{
< # opening angled bracket
(?>/?) # ratchet past optional /
(?:
[^pP] # non-p tag
| # ...or...
[pP][^\s>/] # longer tag that begins with p (e.g., <pre>)
)
[^>]* # everything until closing angled bracket
> # closing angled bracket
}{}gx; # replace with nothing, globally
但实际上,省去一些麻烦,改用解析器。CPAN 有几个合适的模块。这是一个使用HTML::TokeParser模块的示例,该模块附带功能强大的HTML::Parser CPAN 发行版:
use strict;
use HTML::TokeParser;
my $parser = HTML::TokeParser->new('/some/file.html')
or die "Could not open /some/file.html - $!";
while(my $t = $parser->get_token)
{
# Skip start or end tags that are not "p" tags
next if(($t->[0] eq 'S' || $t->[0] eq 'E') && lc $t->[1] ne 'p');
# Print everything else normally (see HTML::TokeParser docs for explanation)
if($t->[0] eq 'T')
{
print $t->[1];
}
else
{
print $t->[-1];
}
}
HTML::Parser接受文件名、打开文件句柄或字符串形式的输入。将上面的代码包装在库中并使目标可配置(即,不只是print
像上面那样)并不难。与尝试使用正则表达式相比,结果将更加可靠、可维护并且可能更快(HTML::Parser 使用基于 C 的后端)。
在我看来,试图用 HTML 解析器以外的任何东西来解析 HTML 只是在寻找一个痛苦的世界。HTML 是一种非常复杂的语言(这是创建 XHTML 的主要原因之一,它比 HTML 简单得多)。
例如,这个:
<HTML /
<HEAD /
<TITLE / > /
<P / >
是一个完整的、100% 格式正确、100% 有效的 HTML 文档。(嗯,它缺少 DOCTYPE 声明,但除此之外......)
它在语义上等价于
<html>
<head>
<title>
>
</title>
</head>
<body>
<p>
>
</p>
</body>
</html>
但它仍然是您必须处理的有效 HTML。当然,您可以设计一个正则表达式来解析它,但是,正如其他人已经建议的那样,使用实际的 HTML 解析器要容易得多。
我想出了这个:
<(?!\/?p(?=>|\s.*>))\/?.*?>
x/
< # Match open angle bracket
(?! # Negative lookahead (Not matching and not consuming)
\/? # 0 or 1 /
p # p
(?= # Positive lookahead (Matching and not consuming)
> # > - No attributes
| # or
\s # whitespace
.* # anything up to
> # close angle brackets - with attributes
) # close positive lookahead
) # close negative lookahead
# if we have got this far then we don't match
# a p tag or closing p tag
# with or without attributes
\/? # optional close tag symbol (/)
.*? # and anything up to
> # first closing tag
/
这现在将处理带有或不带有属性的 p 标签和关闭 p 标签,但将匹配带有或不带有属性的前标签和类似标签。
它不会去除属性,但我的源数据没有将它们放入。我可能稍后会更改它以执行此操作,但现在就足够了。
不知道你为什么要这样做 - 用于 HTML 清理的正则表达式并不总是最好的方法(你需要记住清理属性等,删除 javascript: hrefs 等)......但是,一个匹配 HTML 的正则表达式不是的标签<p></p>
:
(<[^pP].*?>|</[^pP]>)
详细:
(
< # < opening tag
[^pP].*? # p non-p character, then non-greedy anything
> # > closing tag
| # ....or....
</ # </
[^pP] # a non-p tag
> # >
)
我使用了 Xetius 正则表达式,它工作正常。除了一些 flex 生成的标签,它们可以是 :
里面没有空格。我试着用一个简单的方法来修复它?在\s之后,看起来它正在工作:
<(?!\/?p(?=>|\s?.*>))\/?.*?>
我用它来清除 flex 生成的 html 文本中的标签,所以我还添加了更多例外标签:
<(?!\/?(p|a|b|i|u|br)(?=>|\s?.*>))\/?.*?>
Xetius,复活了这个古老的问题,因为它有一个没有提到的简单解决方案。(在对正则表达式赏金任务进行一些研究时发现了您的问题。)
关于使用正则表达式解析 html 的所有免责声明,这里有一个简单的方法。
#!/usr/bin/perl
$regex = '(<\/?p[^>]*>)|<[^>]*>';
$subject = 'Bad html <a> </I> <p>My paragraph</p> <i>Italics</i> <p class="blue">second</p>';
($replaced = $subject) =~ s/$regex/$1/eg;
print $replaced . "\n";
看这个现场演示
参考
由于 HTML 不是常规语言,我不希望正则表达式在匹配它方面做得很好。他们可能能够胜任这项任务(尽管我不相信),但我会考虑寻找其他地方;我确信 perl 必须有一些现成的库来操作 HTML。
无论如何,我认为你想要匹配的是 </?(p.+|.*)(\s*.*)> 非贪婪(我不知道 perl 的正则表达式语法的变幻莫测,所以我无能为力更远)。我假设 \s 表示空格。也许不是。无论哪种方式,您都需要一些与标签名称偏移的属性相匹配的东西。但这比这更困难,因为人们经常将未转义的尖括号放在脚本和注释中,甚至可能是引用的属性值,而您不想匹配这些值。
所以正如我所说,我真的不认为正则表达式是适合这项工作的工具。
由于 HTML 不是常规语言
HTML 不是,但 HTML 标签是,它们可以用正则表达式充分描述。
假设这将在 PERL 中像在声称使用 PERL 兼容语法的语言中一样工作:
/<\/?[^p][^>]*>/
编辑:
但不幸的是,这与 a <pre>
or<param>
标签不匹配。
这,也许?
/<\/?(?!p>|p )[^>]+>/
这也应该涵盖<p>
具有属性的标签。
您可能还希望在 p 标签中的“p”之前允许空格。不知道你多久会遇到这种情况,但 < p> 是完全有效的 HTML。
只需很少的努力就可以使原始的正则表达式工作:
<(?>/?)(?!p).+?>
问题是 /? (或 \?)在断言失败后放弃匹配的内容。在它周围使用非回溯组 (?>...) 会注意它永远不会释放匹配的斜杠,因此 (?!p) 断言始终锚定到标记文本的开头。
(也就是说,我同意通常用正则表达式解析 HTML 不是可行的方法)。
试试这个,它应该工作:
/<\/?([^p](\s.+?)?|..+?)>/
解释:它匹配除“p”之外的单个字母,后跟可选的空格和更多字符,或多个字母(至少两个)。
/编辑:我添加了处理p
标签中的属性的能力。
这对我有用,因为上述所有解决方案对于以 p 开头的其他 html 标记(例如 param pre progress 等)都失败了。它也处理了 html 属性。
~(<\/?[^>]*(?<!<\/p|p)>)~ig
您可能还应该删除 <p> 标记上的任何属性,因为坏人可能会执行以下操作:
<p onclick="document.location.href='http://www.evil.com'">Clickable text</p>
最简单的方法是使用人们在此处建议的正则表达式来搜索具有属性的 <p> 标记,并将它们替换为没有属性的 <p> 标记。只是为了安全起见。