Enforcing a regex to be wrapped with quotation marks from both sides or not at all without duplicating the expression

Viewed 60

I have an expression that should either be wrapped with quotation marks from both sides or not at all, and I want to create a regex to capture this expression.
The point is, I don't want to duplicate the expression.
For example, if the expression is any single letter in english then the duplicated regex would be

^[a-z]|'[a-z]'$

Now both a and 'a' would be accepted.

Is there a way to define this regex without duplicating the expression?

3 Answers

How about:

^('?)[a-z]\1$

In group 1, match ' or nothing. And after the [a-z] part, match what we matched in group 1 (\1). So either:

  • group 1 matches ', in which case \1 matches ' --> ^'[a-z]'$, or;
  • group 1 matches nothing, in which case \1 matches nothing --> ^[a-z]$

Therefore, this matches the same things as ^([a-z]|'[a-z]')$..

Demo

What about this?

^(?:'|(?=.*[^']$))[a-z]'?$

Actually this is broken, as it also matches 'a. This is better, but a bit longer too:

^(?:'(?=.*')|(?=[^']+$))[a-z]'?$

Explanation of this second, working version:

  • [a-z]'?$ matches the letter optionally followed by a ' at the end of the line. Whether this single quote is there or not is governed by the other part of the regex
  • ^(?:'(?=.*')|(?=[^']+$)) matches a ' if the line also ends with ', or just a bunch of non-single quotes (well, in reality just one)
    • (?:…) is a non-capturing group
    • '(?=.*') is the alternative that matches and consumes the leading ' if the line end with a ' too; the reason why (?=.*') and not (?=.*'$) is just that the $ is already imposed by the other piece of the regex
    • (?=[^']+$) doesn't consume anything, but only asserts that the line does not have single quotes

Another option could asserting that the string does not consists of a single ' using a single negative lookahead.

^(?![^'\r\n]*'[^'\r\n]*$)'?[a-z]'?$

Explanation

  • ^ Start of string
  • (?! Negative lookahead, assert what from this position at the right is not
  • ) Close lookahead
  • '?[a-z]'? Match [a-z] between optional '
  • $ End of string

Regex demo

Related