Tag matching using Regex

 

Single-character tag matching

我们通常会碰到对字符串中的被tag包围的内容进行匹配的情况,比如

abc "Hello, world"

我们需要匹配""中的字符串,那么可以使用如下的regex

    "[^"]*"

这里我们使用贪婪匹配来匹配字符串,为了防止过度匹配的情况发生,我们使用了排除型字符组,即[^"]来匹配引号中的内容。这里我们还可以用忽略优先来匹配

    ".*?"

因为忽略优先的特性,所以不会出现过度匹配的情况,即不会匹配

 abc "hello, world" "hello, boy"

Multi-characters tag matching

当我们要匹配不是简单的引号中的内容时,即如果一个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匹配时一样。

 

Handling Nested Tags

上面的方案无法解决有嵌套tag的情况,比如

<b>hello<b>world</b>

如果采用(1),那么很显然,我们这里会匹配整个字符串,但是<b>hello通常不是我们的期望匹配。而(2)同样也无法解决,还是会过度匹配。

这里其实我们需要匹配的是除了所有starting tag,和ending tag之外的字符,也就是说我们需要

    start(except start, end)*end

对于贪婪匹配,我们必须同时排除startingending tag

  • 排除starting tag是为了防止匹配嵌套的tag
  • 排除ending tag是为了防止过度匹配. e.g. <b>blahblah</b>foo</b>
    <b>((?!<b>|</b>).)*</b> => <b>((?!</?b>).)*</b>

对于非贪婪匹配,我们只需排除starting tag即可,因为我们总是不会过度匹配</b>

    <b>((?!<b>).)*?</b>


Optimization

匹配时我们已经可以使用忽略优先来匹配tags了,但是对于某些tag,我们还可以做得更好,比如C comment。C的注释形如/*...*/,其中/**/不能嵌套,我们当然可以用忽略优先来构造,得到:

/\*((?!/\*).)*?\*/

但其实,我们也可以更直接,使用:

/\*.*?\*/

这里能够这样简单地使用忽略优先来完成匹配的原因是:/**/不能嵌套,这就保证了,里面不会出现/*,所以我们可以不用排除/*。

用python做性能测试结果表明,第二个表达式的效率比第一个表达式提升约69%。

Conclusion

对于tag匹配的问题,我们得到了两种解决方案

  • 使用贪婪匹配
    start((?!start|end).)*end
  • 使用非贪婪匹配,且start和end之间可能存在嵌套
    start((?!start).)*?end
  • 使用非贪婪匹配,且start和end之间不会存在嵌套
    start.*?end

对于single-character tag,我们只需用简单的排除型字符组替换Lookaround即可,得到

  • 使用贪婪匹配
    s[^se]*e
  • 使用非贪婪匹配
    s[^s]*?e

 Note

Python中的正则表达式不支持在Lookaround中使用非固定长度的表达式,需要注意tag的构造。

 Reference

[1] Mastering Regular Expression

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值