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