你的方法有两个问题。
- 你的第一个前瞻需要是一个后瞻。当您编写
(?!cat)
时,引擎会检查接下来的三个字符cat
,然后重置到它开始的位置(这就是它向前看的方式),然后您尝试匹配dog
这三个相同的字符。因此,前瞻不会添加任何内容:如果您可以匹配dog
,那么您显然无法cat
在同一位置匹配。您想要的是(?<!cat)
检查前面的字符是否不是的lookbehind cat
。不幸的是,JavaScript 不支持lookbehind。
- 您想对这两个环视进行逻辑或运算。在您的情况下,如果任一环顾失败,则会导致模式失败。因此,需要满足两个要求(两端都没有)
cat
。但你实际上想要OR那个。如果支持lookbehinds,那会更喜欢(?<!cat)dog|dog(?!cat)
(注意交替将整个模式分开)。但正如我所说,不支持后视。catdogdog
您似乎在第一个位中对两个外观进行了 *OR*ed 的原因是前面cat
根本没有检查(参见第 1 点)。
那么如何解决lookbehinds呢?Kolink 的回答建议(?!cat)...dog
,它将环视放在 acat
开始的位置,并使用前瞻。这有两个新问题:它不能匹配dog
字符串开头的a(因为前面的三个字符是必需的。它不能匹配两个连续dog
的s,因为匹配不能重叠(匹配第一个之后dog
,引擎需要三个新字符,这将在再次实际匹配之前...
消耗下一个)。dog
dog
有时您可以通过反转模式和字符串来解决它,从而将向后看变成向前看 - 但在您的情况下,这会将最后的向前看变成向后看。
仅正则表达式的解决方案
我们必须聪明一点。由于匹配不能重叠,我们可以尝试catdogcat
显式匹配,而不替换它(因此在目标字符串中跳过它们),然后只替换dog
我们找到的所有 s。我们将这两种情况交替放置,因此它们都在字符串中的每个位置都进行了尝试(catdogcat
尽管选项在这里并不重要)。问题是如何获得条件替换字符串。但是让我们看看到目前为止我们得到了什么:
text.replace(/(catdog)(?=cat)|dog/g, "$1[or 000 if $1 didn't match]")
因此,在第一个替代方案中,我们匹配 acatdog
并将其捕获到组1
中,并检查是否有另一个cat
追随者。在替换字符串中,我们简单地写$1
回。美妙之处在于,如果第二个备选方案匹配,则第一组将未被使用,因此将是一个空字符串作为替代品。我们只匹配catdog
和使用前瞻而不是catdogcat
立即匹配的原因再次是重叠匹配。如果我们使用catdogcat
,那么在输入catdogcatdogcat
中,第一个匹配项将消耗所有内容,直到并包括第二个cat
,因此第dog
一个替代项无法识别第二个。
现在唯一的问题是,如果我们使用第二种选择,我们如何000
进入替换。
不幸的是,我们无法想出不属于输入字符串的条件替换。诀窍是将 a 添加000
到输入字符串的末尾,如果我们找到 a dog
,则在前瞻中捕获它,然后将其写回:
text.replace(/$/, "000")
.replace(/(catdog)(?=cat)|dog(?=.*(000))/g, "$1$2")
.replace(/000$/, "")
第一个替换添加000
到字符串的末尾。
第二个替换匹配catdog
(检查另一个cat
跟随)并将其捕获到组1
(2
留空)或匹配dog
并捕获000
到组2
(1
留空组)。然后我们写$1$2
回,要么是朴素的,要么catdog
是000
.
第三个替换消除000
了字符串末尾的多余内容。
回调解决方案
如果您不喜欢准备正则表达式和第二个选项中的前瞻,您可以使用稍微简单的正则表达式和替换回调:
text.replace(/(catdog)(?=cat)|dog/g, function(match, firstGroup) {
return firstGroup ? firstGroup : "000"
})
每次匹配都会调用所提供函数的版本,replace
并将其返回值用作替换字符串。函数的第一个参数是整个匹配,第二个参数是第一个捕获组(undefined
如果该组不参与匹配)等等......
所以在替换回调中,我们可以自由地想象我们的000
iffirstGroup
是未定义的(即dog
选项匹配)或只返回firstGroup
如果它存在(即catdogcat
选项匹配)。这更简洁一些,可能更容易理解。但是,调用该函数的开销会使其显着变慢(尽管这是否重要取决于您想要执行此操作的频率)。选择你最喜欢的!