Remplacement de la nième instance d'une correspondance d'expression régulière en Javascript

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

  •  09-06-2019
  •  | 
  •  

Question

J'essaie d'écrire une fonction regex qui identifiera et remplacera une seule instance d'une correspondance dans une chaîne sans affecter les autres instances.Par exemple, j'ai cette chaîne :

12||34||56

Je souhaite remplacer le deuxième jeu de tuyaux par des esperluettes pour obtenir cette chaîne :

12||34&&56

La fonction regex doit être capable de gérer un nombre x de tuyaux et me permettre de remplacer le nième ensemble de tuyaux, afin que je puisse utiliser la même fonction pour effectuer ces remplacements :

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

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

Je sais que je pourrais simplement diviser/remplacer/concaténer la chaîne au niveau des tuyaux, et je sais aussi que je peux faire correspondre /\|\|/ et parcourir le tableau résultant, mais je souhaite savoir s'il est possible d'écrire une seule expression capable de faire cela.Notez que ce serait pour Javascript, il est donc possible de générer une expression régulière au moment de l'exécution en utilisant eval(), mais il n'est pas possible d'utiliser des instructions regex spécifiques à Perl.

Était-ce utile?

La solution

voici quelque chose qui fonctionne :

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

où n est celui de moins que le nième tube (bien sûr, vous n'avez pas besoin de cette première sous-expression si n = 0)

Et si vous souhaitez qu'une fonction fasse cela :

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

Autres conseils

Une fonction plus généraliste

Je suis tombé sur cette question et, bien que le titre soit très général, la réponse acceptée ne traite que du cas d'utilisation spécifique de la question.

J'avais besoin d'une solution plus générale, j'en ai donc écrit une et j'ai pensé la partager ici.

Usage

Cette fonction nécessite que vous lui passiez les arguments suivants :

  • original:la chaîne dans laquelle vous recherchez
  • pattern:soit une chaîne à rechercher, soit une RegExp avec un groupe de capture.Sans groupe de capture, une erreur sera générée.C'est parce que la fonction appelle split sur la chaîne d'origine, et ce n'est que si la RegExp fournie contient un groupe de capture que le tableau résultant contiendra les correspondances.
  • n:l'occurrence ordinale à trouver ;par exemple, si vous voulez le 2ème match, passez 2
  • replace:Soit une chaîne avec laquelle remplacer la correspondance, soit une fonction qui prendra en compte la correspondance et renverra une chaîne de remplacement.

Exemples

// 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"

Le code

  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('');

  }

Une autre façon de le définir

La partie la moins attrayante de cette fonction est qu’elle prend 4 arguments.Il pourrait être simplifié de n'avoir besoin que de 3 arguments en l'ajoutant comme méthode au prototype String, comme ceci :

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

Si vous faites cela, vous pouvez appeler la méthode sur n'importe quelle chaîne, comme ceci :

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

Réussir les tests

Voici les tests Jasmine que cette fonction réussit.

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]");

    });

  });

});

Peut ne pas fonctionner dans IE7

Ce code peut échouer dans IE7 car ce navigateur divise incorrectement les chaînes à l'aide d'une expression régulière, comme indiqué ici.[serre le poing à IE7].Je crois que ce est la solution ;si vous avez besoin de prendre en charge IE7, bonne chance.:)

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;
        }
    });
}
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top