如果我有这样的HTML:
<b>1<i>2</i>3</b>
Run Code Online (Sandbox Code Playgroud)
以下正则表达式:
\<[^\>\/]+\>(.*?)\<\/[^\>]+\>
Run Code Online (Sandbox Code Playgroud)
然后它将匹配:
<b>1<i>2</i>
Run Code Online (Sandbox Code Playgroud)
我希望它只匹配开始和结束标记相同的HTML.有没有办法做到这一点?
谢谢,
乔
有没有办法做到这一点?
是的,当然了.忽略那些轻率的非答案,告诉你它无法完成.它当然可以.您可能不希望这样做,正如我在下面解释的那样.
假装HTML <i>和<b>标签总是否定属性的nonce,而且,既不重叠也不嵌套,我们有这个简单的解决方案:
#!/usr/bin/env perl
#
# solution A: numbered captures
#
use v5.10;
while (<>) {
say "$1: $2" while m{
< ( [ib] ) >
(
(?:
(?! < /? \1 > ) .
) *
)
</ \1 >
}gsix;
}
Run Code Online (Sandbox Code Playgroud)
哪个在运行时产生这个:
$ echo 'i got <i>foo</i> and <b>bar</b> bits go here' | perl solution-A
i: foo
b: bar
Run Code Online (Sandbox Code Playgroud)
最好使用命名捕获,这导致了这种等效的解决方案:
#!/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;
}
Run Code Online (Sandbox Code Playgroud)
当然,假设这样的标签既不重叠也不嵌套是不合理的.由于这是递归数据,因此需要递归模式来解决.记住用于递归地解析嵌套parens的trival模式很简单:
( \( (?: [^()]++ | (?-1) )*+ \) )
Run Code Online (Sandbox Code Playgroud)
我将在前面的解决方案中构建这种递归匹配,并且我将进一步抛出一些迭代处理来解开内部位.
#!/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};
}
}
}
Run Code Online (Sandbox Code Playgroud)
哪个demo会产生这个:
$ 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
Run Code Online (Sandbox Code Playgroud)
这仍然相当简单,所以如果它适用于你的数据,那就去吧.
但是,它实际上并不知道正确的HTML语法,它允许标记属性为<i>和<b>.
正如在这个答案中所解释的那样,当然可以使用正则表达式来解析标记语言,只要注意它.
例如,这知道与<i>(或<b>)标签密切相关的属性.在这里,我们定义了用于构建语法正则表达式的正则表达式子例程.这些只是定义,就像定义常规潜艇一样,但现在用于正则表达式:
(?(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> / > )
)
Run Code Online (Sandbox Code Playgroud)
一旦你掌握了语法部分,你就可以将这些定义纳入已经给出的递归解决方案中,以便做得更好.
但是,仍然有一些事情没有被考虑过,而且在更一般的情况下必须是这样.这些已在已提供的较长解决方案中得到证明.
我只想到三个可能的原因,为什么你可能不关心使用正则表达式来解析一般HTML:
其中任何一个或多个都可能适用.在这种情况下,不要这样做.
对于简单的固定示例,这条路线很容易.你希望它能够在你以前从未见过的事物上运行得越强大,这条路线就越难.
当然,如果你使用像Python这样的语言,甚至更糟糕的Javascript,使用劣质,贫困的模式匹配,你就无法做到.那些几乎没有比Unix grep程序好,在某些方面,甚至更糟.不,你需要一个现代模式匹配引擎,如Perl或PHP中的引擎,甚至可以从这条路开始.
但老实说,让别人为你做这件事可能更容易,我的意思是你应该使用一个已经编写过的解析模块.
仍然,理解为什么不打扰这些基于正则表达式的方法(至少,不超过一次)要求您首先使用正则表达式正确实现正确的HTML解析.你需要了解它的全部意义.因此,像这样的小练习对于提高对问题空间和现代模式匹配的整体理解是有用的.
这个论坛并不是用于解释现代模式匹配所有这些事情的正确格式.然而,有些书籍表现得非常好.
| 归档时间: |
|
| 查看次数: |
2006 次 |
| 最近记录: |