Edit3
It appears as if it didn't work for you, so here's another attempt. And since mickmack seems to be worried about performance, he'll be glad that it's down to 146 steps ;)
Replace
([\w\s]*(?:\([^)]*\)[\w\s]*)*)([*?$&])
with
$1\\$2
Here at regex101.
It captures an optional range of non special characters. It goes on capturing an optional parenthesized group, followed by an optional range of non special characters. This last part can repeat any number of times. Finally it captures the special character.
So we have to capture groups - one with the text leading up to the special character (if any), and one with the special character.
Replacing this with the content of them with a \
in between, does the trick.
This is also more flexible with the parentheses part (happy mick? ;). It allows more complex regex'es inside the brackets (just not nested parentheses).
If the new requirement of handling \
's isn't a must, and a negated word class is OK \W
, we're down to a blazing 76 steps :) Here at regex101.
--Original answer--
This is one way of doing it - replace
(?<!\(|\(.|\(..)([^\w\s])(?![^(]*\))
with
\$1
Note! You have to escape the \
in the php string - i.e. "\\$1".
Since php only allows fixed with look-behinds, it tests that there isn't an opening parentheses before the special character in four steps with the (?<!\(|\(.|\(..|\(...)
construct. Then it matches, and captures, the special character (not a word character, nor a space). Lastly it uses a negative look-ahead to make sure it isn't followed by a closing parentheses. Checking the parentheses both before and after may be redundant though.
Replacing the matched, and captured, character by itself - $1
- preceded by the wanted escape character \
will do the trick.
See it here at regex101.
Edit
Here's an alternative way if the special characters are limited to the one in your example - use
(?<!\(\.)([*?$&])(?!\))
as the search string and replace with \$1
.
It matches your special characters as long as they're not preceded by (.
, nor followed by )
.
Here at regex101.
(Neither of the ways are waterproof since they would fail to escape the &
in (.& )
.)
Edit2
Updated since OP changed escape character in question from /
to \
.
And removed the space inside the capturing group as it was not wanted by OP.