Alexander Gladysh
Alexander Gladysh

Reputation: 41393

Replace capture group content in JavaScript

In my JavaScript code I have a regular expression with capture groups (that is configured by library user) and a source string which matches this regular expression. The regular expression matches whole string (i.e. it has ^ and $ characters at its start and end).

A silly example:

var regex = /^([a-zA-Z]{2})-([0-9]{3})_.*$/;
var sourceStr = "ab-123_foo";

I want to reassemble the source string, replacing values in the capture groups and leaving the rest of the string intact. Note that, while this example has most of the "rest of the string" at its end, it actually may be anywhere else.

For example:

var replacements = [ "ZX", "321" ];
var expectedString = "ZX-321_foo";

Is there a way to do this in JavaScript?

NB: The regular expression is configured by the library user via the legacy API. I can not ask user to provide a second regular expression to solve this problem.

Upvotes: 4

Views: 2665

Answers (2)

wolfhammer
wolfhammer

Reputation: 2661

I think this is close. It satisfies the two test cases but I'm unsure about leading and trailing groupings.

function replacer (regex, sourceStr, replacements) {

  // Make a new regex that adds groups to ungrouped items.

    var groupAll = "";
    var lastIndex = 0;
    var src = regex.source;
    var reGroup=/\(.*?\)/g;
    var match;
    while(match = reGroup.exec(src)){
        groupAll += "(" + src.substring(lastIndex, match.index) + ")";
        groupAll += match[0];
        lastIndex = match.index + match[0].length;
    }

    var reGroupAll = new RegExp(groupAll);

  // Replace the original groupings with the replacements
  // and append what was previously ungrouped.

    var rep = sourceStr.replace(reGroupAll, function(){

        // (match, $1, $2, ..., index, source)
      var len = arguments.length - 2;
      var ret = "";

      for (var i = 1,j=0; i < len; i+=2,j++) {
        ret += arguments[i];
        ret += replacements[j];
      }
      return ret;
    });

    return rep;
}


var regex = /^([a-zA-Z]{2})-([0-9]{3})_.*$/;
var sourceStr = "ab-123_foo";
var replacements = [ "ZX", "321" ];
var expectedString = "ZX-321_foo";

var replaced = replacer(regex, sourceStr, replacements);
console.log(replaced);
console.log(replaced === expectedString);

regex = /^.*_([a-zA-Z]{2})-([0-9]{3})$/;
sourceStr = "ab_ab-123";
expectedString = "ab_ZX-321";

var replaced = replacer(regex, sourceStr, replacements);
console.log(replaced);
console.log(replaced === expectedString);

Output:

ZX-321_foo
true
ab_ZX-321
true

Upvotes: 1

adeneo
adeneo

Reputation: 318182

Without changing the regex the best I can think of is a callback that replaces the matches

sourceStr = sourceStr.replace(regex, function(match, $1, $2, offset, str) {
    return str.replace($1, replacements[0]).replace($2, replacements[1]);
});

That's not a very good solution, as it would fail on something like

var sourceStr = "ab_ab-123_foo";

as it would replace the first ab instead of the matched one etc. but works for the given example and any string that doesn't repeat the matched characters

var regex = /^([a-zA-Z]{2})-([0-9]{3})_.*$/;
var sourceStr = "ab-123_foo";

var replacements = [ "ZX", "321" ];

sourceStr = sourceStr.replace(regex, function(match, $1, $2, offset, str) {
    return str.replace($1, replacements[0]).replace($2, replacements[1]);
});

document.body.innerHTML = sourceStr;

Upvotes: 3

Related Questions