IAdapter
IAdapter

Reputation: 64737

How to negate the whole regex?

I have a regex, for example (ma|(t){1}). It matches ma and t and doesn't match bla.

I want to negate the regex, thus it must match bla and not ma and t, by adding something to this regex. I know I can write bla, the actual regex is however more complex.

Upvotes: 147

Views: 272676

Answers (6)

Lionel Rowe
Lionel Rowe

Reputation: 5926

The following works in JavaScript:

^(?![\s\S]*(?:ORIGINAL_REGEX_SOURCE))[\s\S]*$

So for example, in your case, your negated regex would be ^(?![\s\S]*(ma|(t){1}))[\s\S]*$.

The negate function below can be used to convert a regex into its negated form:

function negate(regex) {
    return new RegExp(
        String.raw`^(?![\s\S]*(?:${regex.source}))[\s\S]*$`,
        regex.flags,
    )
}

const tests = [
    { regex: /(ma|(t){1})/, matches: ['ma', 't'], nonMatches: ['bla'] },
    { regex: /foo/, matches: ['foo'], nonMatches: ['bar'] },
    { regex: /(foo|bar)/, matches: ['foo', 'bar'], nonMatches: ['baz'] },
    { regex: /\d{3}/, matches: ['123', '456', '999'], nonMatches: ['foo'] },
]

const results = []

function check(condition, message = 'Condition failed') {
    if (!condition) {
        throw new Error(message)
    }
    results.push(message)
}

for (const { regex, matches, nonMatches } of tests) {
    for (const text of matches) {
        const atStart = `${text} ...`
        const atEnd = `... ${text}`
        const inMiddle = `... ${text} ...`

        check(regex.test(text), `${regex} matches ${JSON.stringify(text)}`)
        check(regex.test(atStart), `${regex} matches ${JSON.stringify(atStart)}`)
        check(regex.test(atEnd), `${regex} matches ${JSON.stringify(atEnd)}`)
        check(regex.test(inMiddle), `${regex} matches ${JSON.stringify(inMiddle)}`)

        const negated = negate(regex)

        check(!negated.test(text), `${negated} doesn't match ${JSON.stringify(text)}`)
        check(!negated.test(atStart), `${negated} doesn't match ${JSON.stringify(atStart)}`)
        check(!negated.test(atEnd), `${negated} doesn't match ${JSON.stringify(atEnd)}`)
        check(!negated.test(inMiddle), `${negated} doesn't match ${JSON.stringify(inMiddle)}`)

        const doubleNegated = negate(negated)

        check(doubleNegated.test(text), `${doubleNegated} matches ${JSON.stringify(text)}`)
        check(doubleNegated.test(atStart), `${doubleNegated} matches ${JSON.stringify(atStart)}`)
        check(doubleNegated.test(atEnd), `${doubleNegated} matches ${JSON.stringify(atEnd)}`)
        check(doubleNegated.test(inMiddle), `${doubleNegated} matches ${JSON.stringify(inMiddle)}`)
    }

    for (const text of nonMatches) {
        const atStart = `${text} ...`
        const atEnd = `... ${text}`
        const inMiddle = `... ${text} ...`

        check(!regex.test(text), `${regex} doesn't match ${JSON.stringify(text)}`)
        check(!regex.test(atStart), `${regex} doesn't match ${JSON.stringify(atStart)}`)
        check(!regex.test(atEnd), `${regex} doesn't match ${JSON.stringify(atEnd)}`)
        check(!regex.test(inMiddle), `${regex} doesn't match ${JSON.stringify(inMiddle)}`)

        const negated = negate(regex)

        check(negated.test(text), `${negated} matches ${JSON.stringify(text)}`)
        check(negated.test(atStart), `${negated} matches ${JSON.stringify(atStart)}`)
        check(negated.test(atEnd), `${negated} matches ${JSON.stringify(atEnd)}`)
        check(negated.test(inMiddle), `${negated} matches ${JSON.stringify(inMiddle)}`)

        const doubleNegated = negate(negated)

        check(!doubleNegated.test(text), `${doubleNegated} doesn't match ${JSON.stringify(text)}`)
        check(!doubleNegated.test(atStart), `${doubleNegated} doesn't match ${JSON.stringify(atStart)}`)
        check(!doubleNegated.test(atEnd), `${doubleNegated} doesn't match ${JSON.stringify(atEnd)}`)
        check(!doubleNegated.test(inMiddle), `${doubleNegated} doesn't match ${JSON.stringify(inMiddle)}`)
    }
}

console.info(['', ...results].join('\n'))
console.info('All tests passed')

Upvotes: 0

Podrepny
Podrepny

Reputation: 37

This regexp math your condition:

^.*(?<!ma|t)$

Look at how it works: https://regex101.com/r/Ryg2FX/1

Upvotes: -1

DAVID AJAYI
DAVID AJAYI

Reputation: 2154

Apply this if you use laravel.

Laravel has a not_regex where field under validation must not match the given regular expression; uses the PHP preg_match function internally.

'email' => 'not_regex:/^.+$/i'

Upvotes: -2

Ofer Skulsky
Ofer Skulsky

Reputation: 743

\b(?=\w)(?!(ma|(t){1}))\b(\w*)

this is for the given regex.
the \b is to find word boundary.
the positive look ahead (?=\w) is here to avoid spaces.
the negative look ahead over the original regex is to prevent matches of it.
and finally the (\w*) is to catch all the words that are left.
the group that will hold the words is group 3.
the simple (?!pattern) will not work as any sub-string will match
the simple ^(?!(?:m{2}|t)$).*$ will not work as it's granularity is full lines

Upvotes: 2

polygenelubricants
polygenelubricants

Reputation: 383746

Use negative lookaround: (?!pattern)

Positive lookarounds can be used to assert that a pattern matches. Negative lookarounds is the opposite: it's used to assert that a pattern DOES NOT match. Some flavor supports assertions; some puts limitations on lookbehind, etc.

Links to regular-expressions.info

See also

More examples

These are attempts to come up with regex solutions to toy problems as exercises; they should be educational if you're trying to learn the various ways you can use lookarounds (nesting them, using them to capture, etc):

Upvotes: 141

Alan Moore
Alan Moore

Reputation: 75222

Assuming you only want to disallow strings that match the regex completely (i.e., mmbla is okay, but mm isn't), this is what you want:

^(?!(?:m{2}|t)$).*$

(?!(?:m{2}|t)$) is a negative lookahead; it says "starting from the current position, the next few characters are not mm or t, followed by the end of the string." The start anchor (^) at the beginning ensures that the lookahead is applied at the beginning of the string. If that succeeds, the .* goes ahead and consumes the string.

FYI, if you're using Java's matches() method, you don't really need the the ^ and the final $, but they don't do any harm. The $ inside the lookahead is required, though.

Upvotes: 66

Related Questions