我们通常会碰到对字符串中的被tag包围的内容进行匹配的情况,比如
abc "Hello, world"
我们需要匹配""中的字符串,那么可以使用如下的regex
"[^"]*"
这里我们使用贪婪匹配来匹配字符串,为了防止过度匹配的情况发生,我们使用了排除型字符组,即[^"]来匹配引号中的内容。这里我们还可以用忽略优先来匹配
".*?"
因为忽略优先的特性,所以不会出现过度匹配的情况,即不会匹配
abc "hello, world" "hello, boy"
当我们要匹配不是简单的引号中的内容时,即如果一个tag包含多个字符,这种情况,使用简单的排除型字符组就不奏效了。比如HTML/XML中,所有element都是多字符的。
<b>hello</b>
这里,如果我们简单地用
<b>[^</b>]*</b>
是不行的,因为这里[^</b>]不是说不匹配</b>这个tag,而是不匹配<,/,b,>这些字符,所以以下合法的内容将不能匹配
<b>/abc</b>
这里我们需要检查的是,当一个字符不是</b>时,才会去匹配,也就是跟单字符tag匹配时一样,这次需要排除</b>而已。我们可以用Lookaround来达到这个目的。
<b>((?!</b>).)*</b> (1)
Lookaround会迫使正则引擎检查当前位置是否是</b>,如果是,则不匹配,于是会尝试匹配</b>。这里我们需要注意,不能写成
<b>(.(?!</b>))*</b>
这个表达式意义同上面那个不一样,这里会先匹配任意字符,之后匹配后,检查当前位置是否是</b>。
同单字符tag匹配一样,我们也可以使用忽略优先来匹配:
<b>.*?</b> (2)
这里不需要排除</b>,理由同单字符tag匹配时一样。
上面的方案无法解决有嵌套tag的情况,比如
<b>hello<b>world</b>
如果采用(1),那么很显然,我们这里会匹配整个字符串,但是<b>hello通常不是我们的期望匹配。而(2)同样也无法解决,还是会过度匹配。
这里其实我们需要匹配的是除了所有starting tag,和ending tag之外的字符,也就是说我们需要
start(except start, end)*end
对于贪婪匹配,我们必须同时排除starting和ending tag,
<b>((?!<b>|</b>).)*</b> => <b>((?!</?b>).)*</b>
对于非贪婪匹配,我们只需排除starting tag即可,因为我们总是不会过度匹配</b>
<b>((?!<b>).)*?</b>
对于tag匹配的问题,我们得到了两种解决方案
对于single-character tag,我们只需用简单的排除型字符组替换Lookaround即可,得到
Note
Python中的正则表达式不支持在Lookaround中使用非固定长度的表达式,需要注意tag的构造。
Reference
[1] Mastering Regular Expression