How to match multiple different patterns with exactly one occurrence in any order?

Viewed 100

I'm fine-tuning the creation of a complex regex. I'm nearly there, but I think I'm missing something.

I would like to test the validity of a string that is to be used as a formatting pattern, such that the result of the test is true or false and the entire expression is captured fully within the regex.

A. The pattern MUST contain four different placeholder identifiers exactly once.

B. The pattern MAY contain the four placeholder identifiers in any order.

C. The pattern MUST NOT nest placeholders.

D. The placeholder identifiers SHOULD be matched with case sensitivity.

E. The placeholder identifiers SHOULD take the form {\w{2,4}}.

F. The pattern SHOULD ignore patterns in the placeholder that are similar in form to the placeholder identifiers.

For this question, the placeholder identifiers (including their delimiters) are:

  • {Desc}
  • {Rg}\
  • {RqID}
  • {RT}

Here is a set of test patterns and the expected results:

  1. $testPattern -cmatch '{RqID}{Rg}{Desc}{RT}'$true
  2. $testPattern -cmatch '{Desc}-{RqID}-{RT}-{Rg}'$true
  3. $testPattern -cmatch '#{RqID}: {Desc} [{Rg}-{RT}]'$true
  4. $testPattern -cmatch '{Desc}-{RqId}-{RT}-{Rg}'$false (fails D on case matching)
  5. $testPattern -cmatch '{Desc}-{Rq}-{RT}-{Rg}'$false (fails A, badly named placeholder)
  6. $testPattern -cmatch '{RqID}{RT}{Rg}{Desc}{RT}'$false (fails A, duplicate placeholder)
  7. $testPattern -cmatch '{RqID}, {Desc, {Rg}}, {RT}'$false (fails C, nesting)

I think that's probably adequate to show how I want this to work.

What I tried:

I tried a combination of negative lookbehind and positive lookahead assertions combined with a backreference.

The closest I can get is this:

(?<!{\1}.*){(Desc|Rg|RqID|RT)}(?!.*{\1})

And to check that $Matches.Count -eq 4. This seems to work... but ...

Ideally, I would like to express the entire condition with the regex such that my test would simply yield $true, rather than checking how many matches I found.

I feel like I'm either missing something about lookaround assertions or backreference that would make this produce the result I want.

1 Answers

Based on your description you should be able to get a pass/fail result with this regex:

^(?=.*\{RqID})(?=.*\{Rg})(?=.*\{Desc})(?=.*\{RT})(?!.*(\{(Desc|Rg|RqID|RT)}).*\1)

It includes positive lookaheads for each of the four placeholders, and a negative lookahead to ensure that none of the placeholders are repeated.

Demo on regex101

That regex will simply yield a match/no-match result. If you also want to capture the expression with the regex, add .*$ to the end of it:

^(?=.*\{RqID})(?=.*\{Rg})(?=.*\{Desc})(?=.*\{RT})(?!.*(\{(Desc|Rg|RqID|RT)}).*\1).*$

Demo on regex101

Related