我刚刚读了一个有关如何在双花括号内获取数据的问题(此问题),然后有人提出了平衡组。我仍然不太清楚它们是什么以及如何使用它们。
我通读了Balancing Group Definition,但是很难理解,我对我提到的问题仍然很困惑。
有人可以简单地解释一下平衡组是什么以及它们如何发挥作用吗?
据我所知,平衡组是.NET正则表达式风格所独有的。
首先,您需要知道.NET(据我所知)是唯一的正则表达式,可让您访问单个捕获组的多个捕获(不在反向引用中,而是在匹配完成之后)。
为了举例说明,请考虑模式
(.)+
和字符串"abcd"。
"abcd"
在所有其他正则表达式中,捕获组1将仅产生一个结果:(d请注意,完全匹配当然会abcd如预期的那样)。这是因为捕获组的每次新使用都会覆盖先前的捕获。
1
d
abcd
另一方面,.NET会记住它们。它是一堆的。匹配上面的正则表达式之后
Match m = new Regex(@"(.)+").Match("abcd");
你会发现
m.Groups[1].Captures
是,CaptureCollection其元素对应于四个捕获
CaptureCollection
0: "a" 1: "b" 2: "c" 3: "d"
其中的数字是的索引CaptureCollection。因此,基本上每次再次使用该组时,都会将新的捕获推入堆栈。
如果我们使用命名捕获组,它将变得更加有趣。由于.NET允许重复使用相同的名称,因此我们可以编写如下正则表达式
(?<word>\w+)\W+(?<word>\w+)
将两个单词捕获到同一组中。同样,每次遇到具有特定名称的组时,都会将捕获推送到其堆栈中。因此,将此正则表达式应用于输入"foo bar"和检查
"foo bar"
m.Groups["word"].Captures
我们发现两个捕获
0: "foo" 1: "bar"
这使我们甚至可以将表达式的不同部分将内容推入单个堆栈中。但是,这只是.NET的功能,它能够跟踪此列表中列出的多个捕获CaptureCollection。但是我说过,这个集合是一个 堆栈 。那么我们可以从中 弹出 东西吗?
事实证明我们可以。如果我们使用如的组(?<-word>...),则word如果子表达式...匹配,则从堆栈中弹出最后一个捕获。因此,如果我们将之前的表达式更改为
(?<-word>...)
word
...
(?<word>\w+)\W+(?<-word>\w+)
然后,第二组将弹出第一组的捕获,最后我们将收到一个空白CaptureCollection。当然,这个例子是毫无用处的。
但是,减号语法还有一个细节:如果堆栈已经为空,则该组将失败(无论其子模式如何)。我们可以利用这种行为来计算嵌套级别- 这就是名称平衡组的来源(以及有趣之处)。假设我们要匹配正确括号内的字符串。我们将每个左括号插入堆栈,然后为每个右括号弹出一个捕获。如果遇到太多的右括号,它将尝试弹出一个空堆栈并导致模式失败:
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*$
因此,我们在重复中有三种选择。第一种选择消耗所有非括号的内容。第二种选择将(s压入堆栈。第三个替代方案)在从堆栈中弹出元素时匹配s(如果可能!)。
(
)
注意: 为澄清起见,我们只检查没有不匹配的括号!这意味着完全不包含括号的字符串 将 匹配,因为它们在语法上仍然有效(在某些语法中,您需要使用括号来匹配)。如果您要确保至少有一组括号,只需在后面添加一个超前(?=.*[(])行^。
(?=.*[(])
^
但是,这种模式并不完美(或完全正确)。
还有一个要注意的地方:这不能确保在字符串末尾堆栈为空(因此(foo(bar)将是有效的)。.NET(以及许多其他版本)还有一个可以帮助我们解决问题的结构:条件模式。通用语法是
(foo(bar)
(?(condition)truePattern|falsePattern)
其中的falsePattern是可选的- 如果省略,则错误情况将始终匹配。条件可以是模式,也可以是捕获组的名称。我将在这里集中讨论后一种情况。如果它是捕获组的名称,则truePattern仅当该特定组的捕获堆栈不为空时才使用。也就是说,类似的条件模式(?(name)yes|no)为“如果name已匹配并捕获了某些内容(仍在堆栈中,则使用模式,yes否则使用模式no”)。
falsePattern
truePattern
(?(name)yes|no)
name
yes
no
因此,在上述模式的结尾(?(Open)failPattern),如果Open-stack不为空,我们可以添加类似内容,从而导致整个模式失败。使模式无条件失败的最简单方法是(?!)(空的负向超前)。因此,我们有最终模式:
(?(Open)failPattern)
Open
(?!)
^(?:[^()]|(?<Open>[(])|(?<-Open>[)]))*(?(Open)(?!))$
请注意,这种条件语法本身与平衡组无关,但是有必要利用它们的全部功能。
从这里开始,天空才是极限。可能有许多非常复杂的用途,并且与其他.NET- Regex功能(例如变长后视功能)结合使用时,有些陷阱(我必须自己学习硬方法)。但是,主要问题始终是:使用这些功能时,代码是否仍可维护?您需要很好地记录它,并确保使用它的每个人也都知道这些功能。否则,您可能会更好,只需手动逐个字符地移动字符串并以整数形式计算嵌套级别。
(?<A-B>...)
这部分功劳归Kobi(有关更多详细信息,请参见下面的答案)。
现在,利用以上所有内容,我们可以验证字符串是否已正确括在括号中。但是,如果我们实际上可以获取(嵌套的)所有这些括号内容的捕获,它将有用得多。当然,我们可以记得在未清空的单独捕获堆栈中打开和关闭括号,然后根据它们在单独步骤中的位置进行一些子字符串提取。
但是.NET在这里提供了另一个便利功能:如果使用(?<A-B>subPattern),不仅从堆栈B弹出捕获,B而且将弹出的捕获与当前组之间的所有内容都压入堆栈A。因此,如果将这样的组用作右括号,则在从堆栈中弹出嵌套级别时,也可以将对的内容压入另一个堆栈中:
(?<A-B>subPattern)
B
A
^(?:[^()]|(?<Open>[(])|(?<Content-Open>[)]))*(?(Open)(?!))$
Kobi 在他的回答中提供了此现场演示
因此,将所有这些东西放在一起,我们可以:
全部在单个正则表达式中。如果那不令人兴奋…;)
当我第一次了解它们时,发现一些有用的资源: