有没有办法做到这一点?
是的,当然。忽略那些告诉您无法完成的轻率的非答案。它肯定可以。正如我在下面解释的那样,您可能不希望这样做。
编号捕获
假装HTML的<i>和<b>标签总是剥离属性,而且既不重叠也不嵌套,我们有这个简单的解决方案:
#!/usr/bin/env perl
#
# solution A: numbered captures
#
use v5.10;
while (<>) {
say "$1: $2" while m{
< ( [ib] ) >
(
(?:
(?! < /? \1 > ) .
) *
)
</ \1 >
}gsix;
}
在运行时会产生这个:
$ echo 'i got <i>foo</i> and <b>bar</b> bits go here' | perl solution-A
i: foo
b: bar
命名捕获
使用命名捕获会更好,这会导致这种等效的解决方案:
#!/usr/bin/env perl
#
# Solution B: named captures
#
use v5.10;
while (<>) {
say "$+{name}: $+{contents}" while m{
< (?<name> [ib] ) >
(?<contents>
(?:
(?! < /? \k<name> > ) .
) *
)
</ \k<name> >
}gsix;
}
递归捕获
当然,假设这些标签既不重叠也不嵌套是不合理的。由于这是递归数据,因此需要递归模式来解决。记住递归解析嵌套括号的繁琐模式很简单:
( \( (?: [^()]++ | (?-1) )*+ \) )
我将在前面的解决方案中构建这种递归匹配,并且我将进一步投入一些交互处理来解开内部位。
#!/usr/bin/perl
use v5.10;
# Solution C: recursive captures, plus bonus iteration
while (my $line = <>) {
my @input = ( $line );
while (@input) {
my $cur = shift @input;
while ($cur =~ m{
< (?<name> [ib] ) >
(?<contents>
(?:
[^<]++
| (?0)
| (?! </ \k<name> > )
.
) *+
)
</ \k<name> >
}gsix)
{
say "$+{name}: $+{contents}";
push @input, $+{contents};
}
}
}
当演示产生这个时:
$ echo 'i got <i>foo <i>nested</i> and <b>bar</b> bits</i> go here' | perl Solution-C
i: foo <i>nested</i> and <b>bar</b> bits
i: nested
b: bar
这仍然相当简单,所以如果它适用于您的数据,那就去吧。
语法模式
然而,它实际上并不知道正确的 HTML 语法,它承认标签属性为 <i> 和 <b> 之类的东西。
正如this answer 中所解释的,当然可以使用正则表达式来解析标记语言,前提是要小心。
例如,它知道与<i>(或<b>)标签密切相关的属性。在这里,我们定义了用于构建语法正则表达式的正则表达式子例程。这些只是定义,就像定义常规 subs 但现在用于正则表达式:
(?(DEFINE) # begin regex subroutine defs for grammatical regex
(?<i_tag_end> < / i > )
(?<i_tag_start> < i (?&attributes) > )
(?<attributes> (?: \s* (?&one_attribute) ) *)
(?<one_attribute>
\b
(?&legal_attribute)
\s* = \s*
(?:
(?"ed_value)
| (?&unquoted_value)
)
)
(?<legal_attribute>
(?&standard_attribute)
| (?&event_attribute)
)
(?<standard_attribute>
class
| dir
| ltr
| id
| lang
| style
| title
| xml:lang
)
# NB: The white space in string literals
# below DOES NOT COUNT! It's just
# there for legibility.
(?<event_attribute>
on click
| on dbl click
| on mouse down
| on mouse move
| on mouse out
| on mouse over
| on mouse up
| on key down
| on key press
| on key up
)
(?<nv_pair> (?&name) (?&equals) (?&value) )
(?<name> \b (?= \pL ) [\w\-] + (?<= \pL ) \b )
(?<equals> (?&might_white) = (?&might_white) )
(?<value> (?"ed_value) | (?&unquoted_value) )
(?<unwhite_chunk> (?: (?! > ) \S ) + )
(?<unquoted_value> [\w\-] * )
(?<might_white> \s * )
(?<quoted_value>
(?<quote> ["'] )
(?: (?! \k<quote> ) . ) *
\k<quote>
)
(?<start_tag> < (?&might_white) )
(?<end_tag>
(?&might_white)
(?: (?&html_end_tag)
| (?&xhtml_end_tag)
)
)
(?<html_end_tag> > )
(?<xhtml_end_tag> / > )
)
组装好语法片段后,您可以将这些定义合并到已经给出的递归解决方案中,以便做得更好。
但是,仍有一些事情没有考虑到,而且在更一般的情况下必须考虑。这些已在the longer solution 中进行了演示。
总结
我只能想到三个您可能不关心使用正则表达式来解析一般 HTML 的可能原因:
- 您使用的是一种贫乏的正则表达式语言,而不是现代语言,因此您必须求助于递归匹配或语法模式等基本的现代便利。
- 您可能会因为递归和语法模式等概念过于复杂而难以理解。
- 您希望其他人为您完成所有繁重的工作,包括繁重的测试,因此您宁愿使用单独的 HTML 解析模块,而不是自己滚动。
其中任何一项或多项都可能适用。在这种情况下,不要这样做。
对于简单的罐头示例,这条路线很简单。你越希望它在你以前从未见过的东西上工作,这条路线就越难。
当然,如果您使用的劣质、贫乏的模式匹配被固定在诸如 Python 或更糟糕的 Javascript 之类的语言的一边,那么您肯定无法做到这一点。这些几乎不比 Unix grep 程序好,在某些方面甚至更糟。不,你需要一个现代的模式匹配引擎,比如在 Perl 或 PHP 中找到的,才能开始这条路。
但老实说,让其他人为你做这件事可能更容易,我的意思是你可能应该使用已经编写好的解析模块。
不过,要理解为什么不用这些基于正则表达式的方法(至少,不超过一次),您首先需要使用正则表达式正确实现正确的 HTML 解析。您需要了解它的全部内容。因此,像这样的小练习有助于提高您对问题空间以及一般现代模式匹配的整体理解。
这个论坛的格式并不适合解释所有这些关于现代模式匹配的事情。不过,有些书做得相当好。