Подтвердить что ты не робот

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

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

12||34||56

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

12||34&&56

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

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

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

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

4b9b3361

Ответ 1

здесь что-то работает:

"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&&");
}

Ответ 2

Более универсальная функция

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

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

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

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

  • original: строка, которую вы ищете в
  • pattern: либо строка для поиска, либо RegExp с группой захвата. Без группы захвата он выдаст ошибку. Это связано с тем, что функция вызывает split в исходной строке, а только если поставляемый RegExp содержит группу захвата, результирующий массив содержит совпадения.
  • n: порядковый номер для поиска; например, если вы хотите выполнить второй матч, перейдите в 2
  • replace: либо строка для замены соответствия, либо функция, которая будет принимать совпадение, и возвращает заменяющую строку.

Примеры

// 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 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, например:

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"

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

Ниже перечислены тесты Жасмина, которые выполняет эта функция.

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, удачи.:)

Ответ 3

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;
        }
    });
}