3

I am using Regex to replace the substring $0 in a var. But now I am looking for an expression to not match $0 when it is escaped like \$0.

A code example looks like this:

let string = "Hello '$0'. '$0' becomes the World. This shall remain \$0";
string = string.replace(/.../g, "World");
// "Hello 'World'. 'World' becomes World. This shall remain \$0"

What is the proper expression to handle that situation shown in the example code above?

Thank you very much in advance!

Casimir et Hippolyte
  • 88,009
  • 5
  • 94
  • 125
Daniel Messner
  • 2,305
  • 2
  • 15
  • 15

4 Answers4

1

The proper way is to account for any escaped backslashes before a $0: \\$0 \$0 should become \\World \$0 as the first \\ denotes a single backslash and the $0 right after it is unescaped.

So, you may use

.replace(/((?:^|[^\\])(?:\\{2})*)\$0/g, "$1World")

See the regex demo.

Mote that even if World starts with a digit, that should still work fine in JavaScript. You may add more restrictions to the pattern, e.g. a word boundary after \$0 or a (?!\d) lookahead to make sure there is no digit right after, but that is not in the question scope.

Pattern details

  • ((?:^|[^\\])(?:\\{2})*) - Capturing group 1:
    • (?:^|[^\\]) - either the string start position or a char other than \
    • (?:\\{2})* - 0 or more repetitions of a double backslash
  • \$0 - a $0 substring.

JS demo:

var s = "Hello '$0'. '$0' becomes the World. This shall remain \\$0. Extra: \\\\$0 is World, \\$0 is escaped.";
console.log("The string is: '" + s + "'");
console.log("The result is: '" + s.replace(/((?:^|[^\\])(?:\\{2})*)\$0/g, "$1World") + "'");

NOTE: The string literal contains two backslashes where the string contains a literal backslash. If you use a single backslash, it will either disappear (when the escape sequence is unknown) or will form a string escape sequence (like \n is for newline, \t will define a tab char, etc.).

Wiktor Stribiżew
  • 607,720
  • 39
  • 448
  • 563
0

Normally you would use Negative Lookbehind for such situations, but since it can not be used in your situation, you can perhaps go around it like this:

([^ \\])\$0([^ \\])

enter image description here

Regex demo - top right you can see description of individual regex components.

The thinking behind this is to separate '$0' into three parts - ' + $0 + '. The opening and closing braces are in their own groups - this allows you to extract the symbol that precedes and succeeds the $0 that you want to replace (you will see why this matters soon). We do not match cases where there is a \ or whitespace preceding the $0. This allows us to capture the entire $0 together with the surrounding symbols, whatever they might be (e.g. '', "", [], ()).

Inside your code, you replace the $0 with the word World, surrounded by the symbols surrounding the original $0 - this is possible, since we originally captured them as groups. Your code therefore will be:

let string = "Hello '$0'. '$0' becomes the World. This shall remain \$0";
string = string.replace(/([^ \\])\$0([^ \\])/g, "$1World$2");

Bonus: some example results:

'' -> Hello 'World'. 'World' becomes the World. This shall remain $0
"" -> Hello "World". "World" becomes the World. This shall remain $0
[] -> Hello [World]. [World] becomes the World. This shall remain $0
vs97
  • 5,765
  • 3
  • 28
  • 41
-1

Ideal would be the use of negativ lookbehind:

/(?<!\\)\$0/g

which sadly isnt supported in all major browsers (if any). You could also only capture if the char before the $0 is not a \

/(?<!\\)\$0/g

but this would also capture (and replace) the char in front of $0.
I cant think of a way to do this using regex and would therefore suggest looping over the string and comparing the chars (tip: start from behind)

Teiem
  • 1,329
  • 15
  • 32
-1

In this case negative lookbehind is used:

let string = "Hello '$0'. '$0' becomes the World. This shall remain \$0";
string = string.replace(/(?<!\\)\$0/g, "World");
// "Hello 'World'. 'World' becomes World. This shall remain \$0"

Negative lookbehind does not work in most browsers, but was accepted in ECMAScript 2018. By now it is implemented in V8 engine and therefore should work in Chrome 62 and Node.js. For more details look at this this answer.

If you want to support e.g. Edge or Firefox, you could use reference replacement string:

let string = "Hello '$0'. '$0' becomes the World. This shall remain \$0";
string = string.replace(/([^\\]|^)\$0/g, "$1World");
// "Hello 'World'. 'World' becomes World. This shall remain \$0"