Замена n-го экземпляра соответствия регулярному выражению в Javascript

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

  •  09-06-2019
  •  | 
  •  

Вопрос

Я пытаюсь написать функцию регулярного выражения, которая идентифицирует и заменяет один экземпляр соответствия внутри строки, не затрагивая другие экземпляры.Например, у меня есть такая строка:

12||34||56

Я хочу заменить второй набор каналов амперсандами, чтобы получить эту строку:

12||34&&56

Функция регулярного выражения должна быть способна обрабатывать x количество каналов и позволять мне заменять n-й набор каналов, чтобы я мог использовать ту же функцию для выполнения этих замен:

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

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

Я знаю, что я мог бы просто разделить / заменить / объединить строку в каналах, и я также знаю, что я могу сопоставить /\|\|/ и выполняем итерацию по результирующему массиву, но мне интересно знать, возможно ли написать единственное выражение, которое может это сделать.Обратите внимание, что это будет для Javascript, поэтому можно сгенерировать регулярное выражение во время выполнения, используя eval(), но невозможно использовать какие-либо инструкции регулярных выражений, специфичные для Perl.

Это было полезно?

Решение

вот что работает:

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

где n на единицу меньше n-го канала (конечно, вам не нужно это первое подвыражение, если n = 0)

И если вы хотите, чтобы функция делала это:

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

Другие советы

Функция более общего назначения

Я наткнулся на этот вопрос, и, хотя название очень общее, принятый ответ рассматривает только конкретный вариант использования вопроса.

Мне нужно было решение более общего назначения, поэтому я написал одно и решил поделиться им здесь.

Использование

Эта функция требует, чтобы вы передали ей следующие аргументы:

Примеры

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

Код

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

  }

Альтернативный Способ Его определения

Наименее привлекательной частью этой функции является то, что она принимает 4 аргумента.Это можно было бы упростить, чтобы требовалось всего 3 аргумента, добавив его в качестве метода к String prototype, например:

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

Если вы сделаете это, вы можете вызвать метод для любой строки, например:

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

Прохождение Тестов

Ниже приведены тесты Jasmine, которые проходит эта функция.

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

    });

  });

});

Может не работать в IE7

Этот код может завершиться ошибкой в IE7 из-за того, что браузер неправильно разделяет строки с помощью регулярного выражения, как описано выше здесь.[грозит кулаком IE7].Я верю, что это это решение;если вам нужна поддержка IE7, удачи.:)

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;
        }
    });
}
Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top