Reemplazo de la enésima instancia de una coincidencia de expresiones regulares en Javascript

StackOverflow https://stackoverflow.com/questions/36183

  •  09-06-2019
  •  | 
  •  

Pregunta

Estoy intentando escribir una función de expresión regular que identifique y reemplace una única instancia de una coincidencia dentro de una cadena sin afectar las otras instancias.Por ejemplo, tengo esta cadena:

12||34||56

Quiero reemplazar el segundo conjunto de tuberías con símbolos para obtener esta cadena:

12||34&&56

La función regex debe poder manejar x cantidad de tuberías y permitirme reemplazar el enésimo conjunto de tuberías, por lo que podría usar la misma función para realizar estos reemplazos:

23||45||45||56||67 -> 23&&45||45||56||67

23||34||98||87 -> 23||34||98&&87

Sé que podría simplemente dividir/reemplazar/concatenar la cadena en las tuberías, y también sé que puedo hacer coincidir /\|\|/ e iterar a través de la matriz resultante, pero me interesa saber si es posible escribir una única expresión que pueda hacer esto.Tenga en cuenta que esto sería para Javascript, por lo que es posible generar una expresión regular en tiempo de ejecución usando eval(), pero no es posible utilizar ninguna instrucción de expresiones regulares específicas de Perl.

¿Fue útil?

Solución

aquí hay algo que funciona:

"23||45||45||56||67".replace(/^((?:[0-9]+\|\|){n})([0-9]+)\|\|/,"$1$2&&")

donde n es el menor que el enésimo tubo (por supuesto, no necesitas esa primera subexpresión si n = 0)

Y si desea una función para hacer esto:

function pipe_replace(str,n) {
   var RE = new RegExp("^((?:[0-9]+\\|\\|){" + (n-1) + "})([0-9]+)\|\|");
   return str.replace(RE,"$1$2&&");
}

Otros consejos

Una función de propósito más general

Me encontré con esta pregunta y, aunque el título es muy general, la respuesta aceptada solo maneja el caso de uso específico de la pregunta.

Necesitaba una solución más general, así que escribí una y pensé en compartirla aquí.

Uso

Esta función requiere que le pase los siguientes argumentos:

Ejemplos

// Pipe examples like the OP's
replaceNthMatch("12||34||56", /(\|\|)/, 2, '&&') // "12||34&&56"
replaceNthMatch("23||45||45||56||67", /(\|\|)/, 1, '&&') // "23&&45||45||56||67"

// Replace groups of digits
replaceNthMatch("foo-1-bar-23-stuff-45", /(\d+)/, 3, 'NEW') // "foo-1-bar-23-stuff-NEW"

// Search value can be a string
replaceNthMatch("foo-stuff-foo-stuff-foo", "foo", 2, 'bar') // "foo-stuff-bar-stuff-foo"

// No change if there is no match for the search
replaceNthMatch("hello-world", "goodbye", 2, "adios") // "hello-world"

// No change if there is no Nth match for the search
replaceNthMatch("foo-1-bar-23-stuff-45", /(\d+)/, 6, 'NEW') // "foo-1-bar-23-stuff-45"

// Passing in a function to make the replacement
replaceNthMatch("foo-1-bar-23-stuff-45", /(\d+)/, 2, function(val){
  //increment the given value
  return parseInt(val, 10) + 1;
}); // "foo-1-bar-24-stuff-45"

El código

  var replaceNthMatch = function (original, pattern, n, replace) {
    var parts, tempParts;

    if (pattern.constructor === RegExp) {

      // If there's no match, bail
      if (original.search(pattern) === -1) {
        return original;
      }

      // Every other item should be a matched capture group;
      // between will be non-matching portions of the substring
      parts = original.split(pattern);

      // If there was a capture group, index 1 will be
      // an item that matches the RegExp
      if (parts[1].search(pattern) !== 0) {
        throw {name: "ArgumentError", message: "RegExp must have a capture group"};
      }
    } else if (pattern.constructor === String) {
      parts = original.split(pattern);
      // Need every other item to be the matched string
      tempParts = [];

      for (var i=0; i < parts.length; i++) {
        tempParts.push(parts[i]);

        // Insert between, but don't tack one onto the end
        if (i < parts.length - 1) {
          tempParts.push(pattern);
        }
      }
      parts = tempParts;
    }  else {
      throw {name: "ArgumentError", message: "Must provide either a RegExp or String"};
    }

    // Parens are unnecessary, but explicit. :)
    indexOfNthMatch = (n * 2) - 1;

  if (parts[indexOfNthMatch] === undefined) {
    // There IS no Nth match
    return original;
  }

  if (typeof(replace) === "function") {
    // Call it. After this, we don't need it anymore.
    replace = replace(parts[indexOfNthMatch]);
  }

  // Update our parts array with the new value
  parts[indexOfNthMatch] = replace;

  // Put it back together and return
  return parts.join('');

  }

Una forma alternativa de definirlo

La parte menos atractiva de esta función es que requiere 4 argumentos.Se podría simplificar para necesitar solo 3 argumentos agregándolo como método al prototipo String, así:

String.prototype.replaceNthMatch = function(pattern, n, replace) {
  // Same code as above, replacing "original" with "this"
};

Si haces eso, puedes llamar al método en cualquier cadena, como esta:

"foo-bar-foo".replaceNthMatch("foo", 2, "baz"); // "foo-bar-baz"

Pasar pruebas

Las siguientes son las pruebas de Jasmine que pasa esta función.

describe("replaceNthMatch", function() {

  describe("when there is no match", function() {

    it("should return the unmodified original string", function() {
      var str = replaceNthMatch("hello-there", /(\d+)/, 3, 'NEW');
      expect(str).toEqual("hello-there");
    });

  });

  describe("when there is no Nth match", function() {

    it("should return the unmodified original string", function() {
      var str = replaceNthMatch("blah45stuff68hey", /(\d+)/, 3, 'NEW');
      expect(str).toEqual("blah45stuff68hey");
    });

  });

  describe("when the search argument is a RegExp", function() {

    describe("when it has a capture group", function () {

      it("should replace correctly when the match is in the middle", function(){
        var str = replaceNthMatch("this_937_thing_38_has_21_numbers", /(\d+)/, 2, 'NEW');
        expect(str).toEqual("this_937_thing_NEW_has_21_numbers");
      });

      it("should replace correctly when the match is at the beginning", function(){
        var str = replaceNthMatch("123_this_937_thing_38_has_21_numbers", /(\d+)/, 2, 'NEW');
        expect(str).toEqual("123_this_NEW_thing_38_has_21_numbers");
      });

    });

    describe("when it has no capture group", function() {

      it("should throw an error", function(){
        expect(function(){
          replaceNthMatch("one_1_two_2", /\d+/, 2, 'NEW');
        }).toThrow('RegExp must have a capture group');
      });

    });


  });

  describe("when the search argument is a string", function() {

    it("should should match and replace correctly", function(){
      var str = replaceNthMatch("blah45stuff68hey", 'stuff', 1, 'NEW');
      expect(str).toEqual("blah45NEW68hey");
    });

  });

  describe("when the replacement argument is a function", function() {

    it("should call it on the Nth match and replace with the return value", function(){

      // Look for the second number surrounded by brackets
      var str = replaceNthMatch("foo[1][2]", /(\[\d+\])/, 2, function(val) {

        // Get the number without the [ and ]
        var number = val.slice(1,-1);

        // Add 1
        number = parseInt(number,10) + 1;

        // Re-format and return
        return '[' + number + ']';
      });
      expect(str).toEqual("foo[1][3]");

    });

  });

});

Puede que no funcione en IE7

Este código puede fallar en IE7 porque ese navegador divide incorrectamente cadenas usando una expresión regular, como se discutió aquí.[agita el puño hacia IE7].Creo que este es la solución;Si necesitas soporte para IE7, buena suerte.:)

function pipe_replace(str,n) {
    m = 0;
    return str.replace(/\|\|/g, function (x) {
        //was n++ should have been m++
        m++;
        if (n==m) {
            return "&&";
        } else {
            return x;
        }
    });
}
Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top