Michael Doye
Michael Doye

Reputation: 8171

Multiple replace with special characters

I am trying to refactor some code:

const newValue = value
  .replace(/\{1\}/g, '<i class="ms ms-1 inline"></i>')
  .replace(/\{2\}/g, '<i class="ms ms-2 inline"></i>')
  ... etc etc
return newValue;

I would prefer not to use replace each time.


I have been trying the following without success:

const mapObj = {
  '/\{1\}/': '<i class="ms ms-1 inline"></i>',
  '/\{2\}/': '<i class="ms ms-2 inline"></i>',
  ...
}
const reg = new RegExp(Object.keys(mapObj).join("|"), "g");
return value.replace(reg, (m) => mapObj[m]);

For simple replaces this usually works pretty well, however because of the need to escape the special characters ({ and }) something is clearly going wrong with my regex pattern in the mapObj object.

Here is demo of the current simplified code.

How do I go about fixing the pattern (/\{1\}/) in the mapObj to get the refactored code to work?

Upvotes: 0

Views: 55

Answers (2)

Shaun A. Noordin
Shaun A. Noordin

Reputation: 1

It appears that your refactor is confusing string, regular expressions, and the escape sequences for both.

Given your code...

const mapObj = {
  '/\{1\}/': '<i class="ms ms-1 inline"></i>',
  '/\{2\}/': '<i class="ms ms-2 inline"></i>',
  ...
}
const reg = new RegExp(Object.keys(mapObj).join("|"), "g");
return value.replace(reg, (m) => mapObj[m]);
  1. The reg regular expression will be /\/{1}\/|\/{2}\//g, (which is looking for curly brackets surrounded by slashes,) which is not what you want. You want something like /\{1\}|\{2\}/g

  2. The (m) => mapObj[m] won't work as intended, because the value string will have words like "pay {1} to cast", whereas mapObj[m] is looking for keys like "\/{1}\/" (note the slashes around the curly brackets)

What you want to do is remove the unnecessary / slashes when converting between Regex and strings and back.

Recommended solution:

function magicReplace(value) {
  mapObj = {
    '{1}': '<i class="ms ms-1 inline"></i>',
    '{2}': '<i class="ms ms-2 inline"></i>',
  };
  reg = new RegExp( Object.keys(mapObj).join("|").replace(/\{/g, '\\{').replace(/\}/g, '\\}'), "g");
  return value.replace(reg, (m) => mapObj[m]);
}

magicReplace('Whenever you cast a spell, you may pay {1}.');
//Returns: 'Whenever you cast a spell, you may pay <i class="ms ms-1 inline"></i>.'

Upvotes: 0

T.J. Crowder
T.J. Crowder

Reputation: 1074475

If your replacments really are the same other than ms-1 for {1} and ms-2 for {2}, then use a capture group:

return value.replace(/\{(\d)}/g, '<i class="ms ms-$1 inline"></i>');

The $1 is the content of the capture group. That expression matches a single digit within {}; adjust as needed.

Live Example:

function process(value) {
    return value.replace(/\{(\d)}/g, '<i class="ms ms-$1 inline"></i>');
}

console.log(process("Testing {1} testing {2}"));


If the replacements vary and you really need to map them, use the function callback:

const mapObj = {
    "{1}": "replacement for 1",
    "{2}": "different replacement for 2"
};
return value.replace(/\{\d}/g, m => mapObj[m] || "some default");

You could use a capture group to avoid having to repeat {} in the keys in mapObj.

Live Example:

const mapObj = {
    "{1}": "replacement for 1",
    "{2}": "different replacement for 2"
};
function process(value) {
    return value.replace(/\{\d}/g, m => mapObj[m] || "some default");
}

console.log(process("Testing {1} testing {2}"));

Upvotes: 1

Related Questions