Safari Regex Issue with $0-9 In Replacement Text

On a financial site, such as, there are many opportunities to use regular expressions to replace numbers and currency. While replacing numbers with regular expressions will work fine, replacing US currency will cause issues that you should be aware of. The issues arise when using the "String.prototype.replace" method, because a dollar sign ($) followed by a number is a back-reference to a captured parenthesis in the regular expression. Meaning that when the replacement string contains $1, the $1 will actually be a pointer to the content matched by the first parenthesis in the regular expression, instead of the string $1. Consequently, replacing some text with $19.99, might not actually replace the text as intended. Here is an example:

Example 1: Problem with Replace and $19.99

var haystack = My budget is INSERT_AMOUNT.;
var result = haystack.replace(/(INSERT_AMOUNT)/, $19.99);

You might expect result in Example 1 to equal, "My budget is $19.99", however it is actually going to equal, "My budget is INSERT_AMOUNT9.99". The $1 back-references to the first parenthesis in the regular expression, which was INSERT_AMOUNT. You can prevent this by using the double $ escape, writing the $1.00 as $$1.00.

Alternatively, if your regular expression does not contain any capturing parenthesis, then you will not have to worry about accidentally back-referencing values, except for one exception with $0 in Safari. This special-case results from the fact that, in addition to the dollar sign and 1-9 back-referencing, there is also a special back-reference $0 which returns the substring of haystack matching your whole regular expression (not just the values captured by parenthesis). For example:

Example 2: The $0 Back-Reference

var haystack = My budget is INSERT_AMOUNT for MONTH_NAME.;
var result = haystack.replace(/INSERT_AMOUNT.*?MONTH_NAME/, $0.99 for February);
var result_zero_ref = haystack.replace(/(INSERT_AMOUNT)(.*?)(MONTH_NAME)/, $0.99);

The result variable in Example 2 will be set to "My budget is $0.99 for February" in all browsers except Safari, while result_zero_ref will be set to "My budget is INSERT_AMOUNT for MONTH_NAME.99", because we are using capturing parenthesis and the replacement is being replace with a pointer to itself plus the text .99. In Safari, result will also behave like result_zero_ref and be set to "My budget is INSERT_AMOUNT for MONTH_NAME.99 for February", even though there are no capturing parenthesis. Although, most browsers do not populate the $0 back-reference if there are no capturing parenthesis, Safari always populates it. Fortunately, Safari also supports dollar sign escaping $$ in the replacement text, so you can us that to prevent $0 from back-referencing.


There are actually 4 techniques you could use to prevent this problem. The double dollar escape is probably the best, but here are the other technique: 1) avoid putting $ in the replacement text, except for back-references, which can be done by crafting more complex regular expressions that use back-references to populate the dollar sign; 2) use an intermediary step that strips out all dollar signs from the replacement text, before replacing, then inserts them back in; 3) use the double dollar escape; or 4) use a function callback.

The first method is great, if you have no need to change the currency symbol. The dollar escape is still the best method, but before I knew about it, I used to prefer the second method. The second method is more robust than the first, allowing you to switch currencies. Here is my implementation:

Example 3: replaceCurrency

String.prototype.replaceCurrency = function(rx, s) {
	return (s && rx) ? this.replace(rx, s.replace(/\$/g, #_#)).replace(/#_#/g, $) :  + this;

In Example 3, we first globally replace all the $ in the replacement text s with #_#, then perform the regular expression. Once the expression completes we go ahead and replace all the #_# with $, thereby restoring the dollar signs. I choose to use #_#, because it contains no regular expression special characters and is a string of characters that does not occur in natural language. This technique requires 3 regular expression, instead of 1, but works in all browsers. The biggest draw-back to this method and the double dollar escape is that if you escape all $ in your replacement text, then you cannot mix currency with back-references in your replacement text. If you need something more effective than Example 3, you might want to check out Steven Levithans regex library.

Lastly, here is demo page that illustrates the issues expressed in Example 2.