Замена n-го экземпляра совпадения с регулярным выражением в Javascript - PullRequest
25 голосов
/ 30 августа 2008

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

12||34||56

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

12||34&&56

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

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

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

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

Ответы [ 3 ]

31 голосов
/ 31 октября 2011

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

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

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

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

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

Примеры * * тысяча тридцать одна // 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, например:

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

20 голосов
/ 30 августа 2008

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

"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&&");
}
0 голосов
/ 25 июля 2009
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;
        }
    });
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...