Regex matching a whole URL subpart that is not followed with a specific whole subpart

Viewed 170

I'm trying to not be able to have a match if path string has a specific word

e.g. If quotes is included in the string, there shouldn't be any match.

But, /love/something/here and others should be okay.

/love/quotes

Here's what I have so far: https://rubular.com/r/W7EjN9KWIN5Jpa

I've tried the ff:

([^\/love\/quotes]|\/love(.*))
\/love(^(quotes).)*

^ But, still keeps getting one or 2 matches

Sorry, I'm new to Regex.

2 Answers

You want to match a whole URL subpart that is not followed with a specific whole subpart.

You need a negative lookahead and, in this case, your boundaries are (?![^\/]), equal to (?=\/|\z), that is, / or end of string.

To just match /love use

\/love(?!\/quotes(?![^\/]))(?![^\/])

See this regex demo

To also capture all after /love, use

\/love(?!\/quotes(?![^\/]))(\/.*)?\z

See the regex demo

Details

  • \/love - /love substring
  • (?!\/quotes(?![^\/])) - a negative lookahead that fails the match if, immediately to the right of the current position, there is /quotes followed with / or end of string position
  • (?![^\/]) - / or end of string required immediately after /love
  • (.*) - any 0+ chars other than linebreak chars, as many as possible
  • (\/.*)? - an optional capturing group matching 1 or 0 occurrences of / and then any 0+ chars other than linebreak chars, as many as possible
  • \z - end of string.

Regular expression

bad_word = "quotes"

The following regular expression matches the string only if the sentence does not contain the word held by bad_word.

r = /
    \A                     # match the beginning of the string
    (?!                    # begin negative lookahead
      .*#{bad_word}\z      # do not permit string to end with the bad word 
    )                      # end negative lookahead 
    (?=                    # begin a positive lookahead
      (?:                  # begin a non-capture group
        (?!                # begin a negative lookahead
          \b#{bad_word}\b  # do not match the bad word between word breaks
        )                  # end negative lookahead
        .                  # match a character
      )*                   # end non-capture group and execute >= 0 times
      \z                   # match end-of-string
    )                      # end positive lookahead
    /ix                    # case indifferent and free-spacing modes

r is conventionally written:

r = /\A(?!.*#{bad_word}\z)(?=(?:(?!\b#{bad_word}\b).)*\z)/i
  #=> /\A(?!.*quotes\z)(?=(?:(?!\bquotes\b).)*\z)/i

This expression ensures that the string does not contain the bad word. It simply requires that that the string not end with the bad word and that every character in the string is not preceded by the bad word, in which case the bad word cannot be present anywhere in the string. It can be pictured as follows (credit REGEXPER).

enter image description here

Combine r with another regular expression

For any string str and regular expression t we will attempt to match str against the regular expression /#{r}#{t}/. If the match against r is successful (i.e., str does not contain bad_word), an attempt is made to match t, with the internal regex pointer starting at the beginning of the string. The match obviously fails if r is not matched.

Examples

str = "The Donald believes Colorado borders Mexico."
str[/#{r}.*/] #=> "The Donald believes Colorado borders Mexico." 

str = "The Donald believes Colorado borders Mexico."
str[/#{r}.*\K(?<=borders )\w+/] #=> "Mexico"

str = "The Donald misquotes everybody."
str[/#{r}.*\AThe \KDonald/] #=> "Donald"

str = "Quotes are a politician's friend"
str[/#{r}.*/] #=> nil 

str = "In a sentence quotes are used to repeat something said by someone else."
str[/#{r}In/] #=> nil 

str = "no quotes"
str[/#{r}.*/] #=> nil

Note that, for example,

/#{r}.*/
  #=> /(?i-mx:\A(?!.*quotes\z)(?=(?:(?!\bquotes\b).)*\z)).*/

Simpler approach

For a string str and regular expression t it would course be much easier to compute

(str !~ /\bquotes\b/i) && str[t]
Related