Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Replacing only the first match of a global regex

I'm writing a function to recursively replace matches of a regex in a string. The replacement can be a function, as with vanilla .replace, and this function can access the original string through one of its arguments.

I would like my function to replace only one match on each iteration. With a non-global regex, this will always be the case. However, some of the regexes this function receives will be global. Doing a traditional .replace(regex, replacement) means it could replace multiple times on each iteration, not only messing up the order in which the matches are processed, but also passing an incorrect index and original string to the replacement function.

As an example:

function recursiveReplace(string, regex, replacement) {
  for (var i = 1e8; i > 0 && regex.test(string); i--)
    string = string.replace(regex, replacement);
  return string;
}

console.log(
  recursiveReplace("abcdef", /../g, function (match, index, original) {
    console.log(original);
    return match[0];
  })
);

This outputs

abcdef
abcdef
abcdef
ace
ae
a

when the desired output is

abcdef
acdef
adef
aef
af
a

How can I get the function to process only one match on each iteration, whether the regex has the g flag or not? Note that I'm using the function in such a way that the second argument will always be a regex (I have no control over this, nor over whether or not said regex has the g flag).

like image 289
ETHproductions Avatar asked Jul 26 '17 21:07

ETHproductions


People also ask

How do you replace all occurrences of a regex pattern in a string?

sub() method will replace all pattern occurrences in the target string. By setting the count=1 inside a re. sub() we can replace only the first occurrence of a pattern in the target string with another string. Set the count value to the number of replacements you want to perform.

What is $1 in regex replace?

For example, the replacement pattern $1 indicates that the matched substring is to be replaced by the first captured group.

Can I use regex in replace?

The Regex. Replace(String, String, MatchEvaluator, RegexOptions) method is useful for replacing a regular expression match if any of the following conditions is true: If the replacement string cannot readily be specified by a regular expression replacement pattern.

How does .replace work in regex?

The replace() method returns a new string with one, some, or all matches of a pattern replaced by a replacement . The pattern can be a string or a RegExp , and the replacement can be a string or a function called for each match. If pattern is a string, only the first occurrence will be replaced.


1 Answers

It would seem that the best way to do this would be to just manually remove the g flag from the regex. Here's the most cross-platform way I could find to do this, using regex.toString() to get the string representation of the regex:

function recursiveReplace(string, regex, replacement) {
  regex = eval(regex.toString().replace(/[a-z]*$/, function (s) {
    return s.replace('g', '');
  }));
  for (var i = 1e8; i > 0 && regex.test(string); i--)
    string = string.replace(regex, replacement);
  return string;
}

With the ES6 features RegExp(regex) and RegExp#flags this gets much easier:

function recursiveReplace(string, regex, replacement) {
  regex = RegExp(regex, regex.flags.replace('g', ''));
  for (var i = 1e8; i > 0 && regex.test(string); i--)
    string = string.replace(regex, replacement);
  return string;
}
like image 114
ETHproductions Avatar answered Oct 12 '22 13:10

ETHproductions