Вопрос:

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

javascript regex

15602 просмотра

3 ответа

4165 Репутация автора

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

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 инструкции регулярного выражения.

Автор: Ant Источник Размещён: 30.08.2008 05:26

Ответы (3)


20 плюса

11429 Репутация автора

Решение

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

"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&&");
}
Автор: Sam Hasler Размещён: 30.08.2008 05:40

0 плюса

0 Репутация автора

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;
        }
    });
}
Автор: Binda Размещён: 25.07.2009 07:42

31 плюса

77480 Репутация автора

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

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

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

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

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

Примеры

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

Автор: Nathan Long Размещён: 31.10.2011 07:24
Вопросы из категории :
32x32