先读这个!
这篇文章是为了展示可能性,而不是支持解决问题的“一切正则表达式”方法。作者编写了 3-4 个变体,每个变体都有难以检测的细微错误,在达到当前解决方案之前。
对于您的具体示例,还有其他更好的解决方案更易于维护,例如沿分隔符匹配和拆分匹配项。
这篇文章处理你的具体例子。我真的怀疑一个完整的概括是可能的,但背后的想法是可重复用于类似的情况。
概括
- .NET 支持使用
CaptureCollection
类捕获重复模式。
- 对于支持
\G
和look-behind 的语言,我们也许可以构造一个与全局匹配函数一起工作的正则表达式。完全正确地编写它并不容易,编写一个有细微错误的正则表达式也很容易。
- 对于没有
\G
和后向支持的语言:可以通过在单个匹配后对输入字符串进行 chomping来模拟\G
with 。^
(此答案未涵盖)。
解决方案
此解决方案假定正则表达式引擎支持\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
)与分隔符(例如-
)交替混合,请确保您最终不会允许后缀正则表达式作为分隔符。