Voii12
Voii12

Reputation: 45

match unique groups

I want to put my product filter in my URL, but to match filter words:

Expected match:

filter:city:cityname-type:something-type2:something123-price:1000 

filter:city:cityname-type2:something-type:something123-price:1000 

filter:city:cityname-price:1000 

To be more precise: string starts with "filter:" word, and after it, at least one filter must be present. Each filter is name:value

First filter (in this case "city") is mandatory, others are not, also order for others can be changed (city,type,type2 OR city,type2,type). Expected filters are at least 1 - 20.

This is regex I am using

^filter:((city:([a-z0-9]+)){1}((-type:([a-z0-9]+))|(-type2:([a-z0-9]+))|(-price:([0-9]+)))*)

How can I match each used filter only once, because with this regex above it will match:

filter:city:some-price:123-type2:aaa-type2:bbb 

(type2 is used more than once). And if there is better regex for this please post.

Upvotes: 2

Views: 103

Answers (1)

Wiktor Stribiżew
Wiktor Stribiżew

Reputation: 626738

You can use

^(?!.*[-:](\b(?:city|type2?|price):).*\1)filter:((city:([a-z0-9]+))((-type:([a-z0-9]+))|(-type2:([a-z0-9]+))|(-price:([0-9]+)))*)\s*$

See the regex demo

The lookahead (?!.*[-:](\b(?:city|type2?|price):).*\1) will check (right at the beginning of a string) and fail the match if a string contains (somewhere inside) - or : followed with whole words (as \b is a word boundary) city, or type, or type2 , or price followed with : (this key is captured into Group 1 with the (...)) and then some 0+ characters (.*) and then the same captured key (as \1 is a backreference to the captured value).

The \s*$ will allow trailing whitespace and the end-of-string and will disallow unknown filters.

To shorten the pattern, in PCRE/Boost, you can use

^(?!.*[-:](\b(?:city|type2?|price):).*\1)filter:(city|type2?|price):([a-z0-9]+)(?:-(?2):(?3))*\s*$

See regex demo (in Ruby, \g<1> and \g<2> can be used instead of (?1) and (?2)).

If the regex flavor is different and does not allow recursion, use

^(?!.*[-:](\b(?:city|type2?|price):).*\1)filter:(?:city|type2?|price):[a-z0-9]+(?:-(?:city|type2?|price):[a-z0-9]+)*$

See another demo

Upvotes: 1

Related Questions