I am implementing a plugin code for my CMS system. Something like a shortcode but will be applicable in many scenarios. I want a case where an admin writes his code like thi
This regex will work as you specified:
$regex = '~
#opening tag
\{(RAW|ACCESS|DWNLINK|MODL|\w+)
#optional attributes
(?>
\{ ([^}]*) }
)?
}
#optional text and closing tag
(?:
( #text:= any char except "{", or a "{" not followed by /commandname
[^{]*+
(?>\{(?!/?\1[{}])[^{]*)*+
)
#closing tag
( \{/\1} )
)?
~ix';
regex101 demo
Compared to what you had:
First of all, I used the /x
modifier (at the end), which ignores whitespace and #comments
.
In the opening tag, I used your options, but you may as well use \w+
to match any command name:
\{(RAW|ACCESS|DWNLINK|MODL|\w+)
For the optional attributes, you had [\{]{0,1}([\w\W\s]*?)\}{0}
, which was avalid attempt to make every part optional. Instead, I'm using a (?> group )?
(See non-capturing groups and atomic groups) to make the whole subpattern optional (with the ?
quantifier).
(?>
\{ ([^}]*) }
)?
The same logic is applied to the text and closing tag, to make it optional.
You were using [\w\s]+
to match the text, which matches word characters and whitespace, but fails to match punctuation and other characters. I could have used .*?
and it would work just as fine. However, I used the following construct, which matches the same, but performs better:
( #text:= any char except "{", or a "{" not followed by /commandname
[^{]*+
(?>\{(?!/?\1[{}])[^{]*)*?
)
And finally, I'm matching the closing tag using \1
, which is a backreference to the text matched in group 1 (the opening tag name):
\{/\1}
Assumptions:
"te}xt"
that could make it break.