.Net正则平衡组使用

.Net中, 正则表达式匹配支持两种高级语法:

  1. 匹配到的指定组名(?<Group>...)
  2. 根据组名的几种操作方式(?<-Group>), (?(Group)yes|no)

指定组名

?<Group>?'Group'两种语法, 都可以为匹配到的组命名.

1
2
3
4
5
6
7
var reg = new Regex("name:(?'groupName'[a-zA-Z0-9_-]+)");
//var reg = new Regex("name:(?<groupName>[a-zA-Z0-9_-]+)");
var matches = reg.Matches("name:test1,name:test2");
foreach (Match m in matches)
{
var name = m.Groups["groupName"].Value;
}

组堆栈

在使用?<Group>语法时, .net会将匹配到的内容压入堆栈stack. 然后在之后的匹配中, 可以使用语法?<-Group>从堆栈顶部弹出名字为<Group>的命名组, 如果堆栈为空, 那么该组匹配失败.

1
2
3
4
5
6
var reg = new Regex("name:(?<nameMatch>(?'stack'{[^{}]+)(?'-stack'}))");
var matches = reg.Matches("name:{abc}");
foreach (Match m in matches)
{
var name = m.Groups["nameMatch"].Value;//{abc}
}

(?(Group)yes|no)的意义是, 如果堆栈上还存在名字为Group的组内容是, 执行yes语句, 否则执行no语句. 这时候可以使用(?!)零宽负向先行断言, 由于没有后缀表达式, 试图匹配总是失败. 即如果还存在Group时, 直接失败.

这几种语法结合起来, 就可以构造递归匹配的方法了. 如匹配类json格式字符串{l:{l1:{l2:3}}}.

1
2
3
4
5
6
7
var reg = new Regex(@"\{(?<match>.+)\}");
//匹配到内容 : "l:{l1:{l2:3}}"
//存在问题, 如果字符串为 "{l:{ {l1:{l2:3}}}" 也能匹配到全部
var reg = new Regex(@"\{(?<match>(?>[^{}]+|(?<stack>\{(?!\{))|(?<-stack>\}))*(?(stack)(?!)))\}");
// {l:{l1:{l2:3}}} => l:{l1:{l2:3}}
// {l:{ {l1:{l2:3}}} => l1:{l2:3}
// {l:{ {l1:{ {l2:3}}} => l2:3

解释: 找到第一个{开始, 命名为match匹配组, 使用(?>)非回溯(又名贪婪)匹配法则和固化分组进行内容匹配(对性能好), 匹配到 : 所有不是{}符号的内容[^{}]+ 或者
如果是{, 而且后面不为{的内容则压入stack, 如果是}, 则弹出stack. 一直匹配到最后(贪婪模式), 如果最后还有stack, 则匹配失败({}数量不正确的情况不会匹配到).

这种语法的帮助在于可以递归匹配内容, 而且判断内容是否正确. 比如做SQL动态(0 嵌套)解析, 即可使用正则平衡组匹配.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
\{\?                                #匹配括号开始 -- 有一个括号和问号
(?<DynamicClause> #分组别名DynamicClause
(?> #非回溯(也称为 贪婪 )子表达式。 即在贪婪条件下匹配过去满足条件的,
#如果碰见后面不满足的,则不会退回字符再查找

#用(?>…)实现固化分组(成功匹配后,回溯时不会考虑这个匹配的字符)

#!正常情况则是贪婪匹配,如果碰见不满足的可以退回字符再次查找满足的
[^\{\}]+ #除了{}的字符串
| #或者
\{\?(?<Clause>) #{[0-1个问号]push字符串到Clause -- 括号,可能有个问号,然后把Clause放进去
#命名捕获组,遇到正括弧Clause计数加1
| #或者
\}(?<-Clause>) #}pop字符串Clause -- 反括号,然后把Clause弹出
#狭义平衡组,遇到闭括弧Clause计数减1
)* #0-n个这种字符串
(?(Clause)(?!)) #如果还有Clause匹配失败
) #分组结束
\} #匹配括号结束

//使用语法
select * from table
where row = #row#
and IsDel = 0
{? and a = #AValue#}
{? and ({? b like '%' + #input# + '%'} {? or c = #input#})}
作者

Mosby

发布于

2017-04-28

许可协议

评论