您的示例中的问题是,您的第一个目标的最后一个单词是源字符串中第二个目标的第一个单词; “正常”RegEx 语法使 RE 引擎消耗它匹配的字符,即这些字符不可用于进一步匹配。
原则上,您可以使用 PCRE 等 Regex 风格,在前瞻断言中使用捕获组,因为它们不会导致断言中的字符消耗。但是,所有这些断言都有其性能的代价。比赛将在两个捕获组中。两个例子:
直截了当:
/
\b(?=(.*?cheese))butter # match butter, assert that cheese comes after it and capture
| # or
\b(?=(.*?butter))cheese # match cheese, assert that butter comes after it and capture
/gsx # flags: global, single line, free spacing
让我们看看\b(?=(.*?cheese))butter的成功匹配是如何工作的;同样的原则也反映在另一种选择中。正则表达式引擎首先查找单词边界\b,即文本中两边都没有单词字符的位置。一旦找到,它将尝试在此位置断言(?=(.*?cheese))。在自然语言中:“从这里开始,尽快找到cheese。只有找到了,才将刚刚遍历的整个字符串捕获在一个编号组中,并将匹配指针返回到我们开始的位置。然后,允许匹配继续。”如果断言成功,则匹配继续,然后使用 butter。我们有了匹配项,匹配指针位于butter 后面,正则表达式引擎对文本的其余部分尝试相同的操作(当然也包括替代项)。
见regex demo。
稍微优化的版本:
/
\b(?=((?:[^c]*+|c(?!heese))*cheese))butter
|
\b(?=((?:[^b]*+|b(?!utter))*butter))cheese
/gsx
见regex demo。
输出:
Match 1
Full match 27-33 butter
Group 1. 27-70 butter is called "pindakaas" (peanut cheese
Match 2
Full match 64-70 cheese
Group 2. 64-111 cheese) rather than "pindaboter" (peanut butter
或
如果不反对在事后为每个匹配连接匹配的字符串和捕获的字符串,这也可以工作,并且在性能方面会更好。 (仍然可能不如 Booboo 的答案中看到的 overlap 解决方案。)
/\bbutter\b.*?\b(?=(cheese))|\bcheese\b.*?\b(?=(butter))/sg
这只会匹配每个替代项,直到第二个术语之前的单词边界,这允许下一次匹配尝试从该术语开始。第二项不是匹配字符串的一部分,而是存储在捕获的组中:['butter is called "pindakaas" (peanut ', 'cheese'], etc.。
见regex demo。