首先阅读!
本文旨在展示问题的可能性,而不是支持“一切正则表达式”方法。作者编写了3-4个变体,在达到当前解决方案之前,每个变体都有微妙的错误很难检测出来。
对于您的特定示例,有其他更可维护的更好的解决方案,例如匹配和沿分隔符拆分匹配。
本文涉及您的具体示例。我真的怀疑一个完全概括是可能的,但背后的思想对于类似情况是可重用的。
摘要
- .NET支持使用
CaptureCollection
类捕获重复模式。
- 对于支持
\G
和反向查找的语言,我们可以构建一个与全局匹配函数一起使用的正则表达式。编写完全正确且容易编写微妙有bug的正则表达式并不容易。
- 对于不支持
\G
和反向查找支持的语言:可以使用^
模拟\G
,通过在单个匹配后吞咽输入字符串来实现。(本答案未涉及此内容)。
解决方案
此解决方案假定正则表达式引擎支持\G
匹配边界、前瞻匹配(?=pattern)
和反向查找(?<=pattern)
。Java、Perl、PCRE、.NET、Ruby正则表达式支持所有这些高级功能。
但是,您可以使用.NET中的正则表达式。由于.NET支持通过CaptureCollection
类捕获重复的所有匹配项。
对于您的情况,可以使用一个正则表达式完成,其中使用了\G
匹配边界,并使用前瞻来限制重复次数:
(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)
DEMO。这种结构是重复使用\w+-
,然后是 \w+:end
。
(?:start:(?=\w+(?:-\w+){2,9}:end)|(?!^)\G-)(\w+)
DEMO. 第一个部分的正则表达式为
\w+
,然后是
-\w+
重复。这个结构更容易理解它的正确性,因为它的选择较少。
\G
匹配边界在需要进行标记化(tokenization)时特别有用,您需要确保引擎不会跳过并匹配应该无效的内容。
解释:
让我们拆分一下正则表达式:
(?:
start:(?=\w+(?:-\w+){2,9}:end)
|
(?<=-)\G
)
(\w+)
(?:-|:end)
最容易识别的部分是在倒数第二行的(\w+)
,这是你想要捕获的单词。
最后一行也很容易识别:匹配的单词可能会跟着一个-
或者:end
。
我允许正则表达式在字符串中任意位置开始匹配。换句话说,start:...:end
可以出现在字符串的任何地方,并且可以出现多次;正则表达式将简单地匹配所有单词。你只需要处理返回的数组,以分离匹配标记的实际来源。
至于解释,正则表达式的开头检查是否存在字符串start:
,接下来的前瞻检查单词数量是否在指定限制范围内,并以:end
结尾。或者我们检查前一个匹配之前的字符是否为-
,并从上一个匹配继续。
对于另一种情况:
(?:
start:(?=\w+(?:-\w+){2,9}:end)
|
(?!^)\G-
)
(\w+)
除了我们首先匹配形式为start:\w+
的重复之前,一切几乎相同。与第一个构造不同的是,在第一个构造中,我们首先匹配start:\w+-
和\w+-
(或最后一个重复的\w+:end
)。
要使这个正则表达式在字符串中间匹配非常棘手:
我们需要检查在start:
和:end
之间的单词数(作为原始正则表达式要求的一部分)。
\G
也会匹配字符串的开头! (?!^)
用于防止这种行为。 如果不注意这一点,则可能在没有start:
时产生匹配。
对于第一个构造,回溯(?<=-)
已经防止了这种情况((?!^)
被(?<=-)
暗示)。
对于第一个构造(?:start:(?=\w+(?:-\w+){2,9}:end)|(?<=-)\G)(\w+)(?:-|:end)
,我们需要确保在:end
之后不匹配任何奇怪的东西。 回溯是为了这个目的:它防止:end
后面的任何垃圾匹配。
第二个构造不会遇到这个问题,因为在匹配所有中间标记之后,我们将卡在:
(:end
的)处。
验证版本
如果您想验证输入字符串是否符合格式(前面和后面没有额外的内容),并提取数据,则可以添加锚点:
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G-)(\w+)
(?:^start:(?=\w+(?:-\w+){2,9}:end$)|(?!^)\G)(\w+)(?:-|:end)
(预查不是必需的,但我们仍然需要(?!^)
来防止\G
匹配字符串的开头)。
构造
对于所有想要捕获重复实例的问题,我认为没有一种通用的方法可以修改正则表达式。一个“困难”(或者说不可能?)的转换案例是当一个重复必须回溯一个或多个循环以满足某些条件来匹配时。
当原始正则表达式描述整个输入字符串(验证类型)时,与试图从字符串中间匹配的正则表达式相比,通常更容易转换。但是,您总是可以使用原始正则表达式进行匹配,并将匹配类型问题转换回验证类型问题。
我们通过以下步骤构建这样的正则表达式:
- 编写覆盖重复之前部分的正则表达式(例如
start:
)。我们称之为前缀正则表达式。 - 匹配并捕获第一个实例(例如
(\w+)
)
(此时,应该已经匹配了第一个实例和分隔符) - 将
\G
添加为可选项。通常还需要防止它匹配字符串的开头。 - 添加分隔符(如果有的话)(例如
-
)
(此时,除了最后一个令牌可能没有被匹配之外,其余令牌都应该已经被匹配) - 添加覆盖重复之后部分的部分(如果有必要)(例如
:end
)。我们将重复之后的部分称为后缀正则表达式(是否将其添加到构造中并不重要)。 - 现在是困难的部分。您需要检查以下内容:
- 除了前缀正则表达式,没有其他启动匹配的方法。注意
\G
分支。 - 在匹配后缀正则表达式之后,没有任何启动任何匹配的方法。注意
\G
分支如何开始匹配。 - 对于第一次构造,如果您在可选项中混合使用后缀正则表达式(例如
:end
)和分隔符(例如-
),请确保您不会允许后缀正则表达式作为分隔符。
\G
(或类似API功能)的正则表达式风格。 - Qtax