Solution
What you need to do is to make it more efficient by making the lookahead run once at the beginning:
/^(?!white$|[\s\S]*(?:cat[\s\S]*dog|dog[\s\S]*cat))[\s\S]*$/i
See the regex demo ([\s\S]
replaced with .
only for demo since the input is tested line by line).
Explanation
The /^((?!(white|cat.*dog|dog.*cat))[\s\S])*$/i
contains an anchored tempered greedy token from a well-known Regular expression to match line that doesn't contain a word? post. [\s\S]
matches any character (even a newline) but not in case it is a char that is the first in a sequence defined in the negative lookahead. So, the regex above matches any string but the one that contains either white
, or cat
followed with 0+ chars other than a newline and then dog
, or vice versa, dog
and then after 0+ chars other than a newline, cat
.
So, what is necessary is to make sure white
is tested in between anchors: ^(?!white$)[\s\S]*$
will do that check.
The rest of the alternatives still need to be checked inside, at any location within the string. So, the [\s\S]*
should be put before the (?:cat[\s\S]*dog|dog[\s\S]*cat)
group: [\s\S]*(?:cat[\s\S]*dog|dog[\s\S]*cat)
. That way, we make sure the string does not have these patterns inside. Note the .*
in the lookahead only checked if the patterns were not present on the first line.
Details:
^
- start of string
(?!
- the negative lookahead check:
white$
- the string can't equal white
|
- or
[\s\S]*(?:cat[\s\S]*dog|dog[\s\S]*cat)
- 0+ any chars followed with either cat
and then after any number of chars a dog
or vice versa
)
- end of the lookahead
[\s\S]*
- 0+ any chars
$
- end of string.