【问题标题】:Replacing the nth instance of a regex match in Javascript替换 Javascript 中正则表达式匹配的第 n 个实例
【发布时间】:2010-09-07 08:42:21
【问题描述】:

我正在尝试编写一个正则表达式函数,该函数将识别和替换字符串中匹配的单个实例,而不会影响其他实例。例如,我有这个字符串:

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 特定的正则表达式指令。

【问题讨论】:

    标签: javascript regex


    【解决方案1】:

    更通用的函数

    我遇到了这个问题,虽然标题很笼统,但接受的答案只处理问题的特定用例。

    我需要一个更通用的解决方案,所以我写了一个并想在这里分享。

    用法

    此函数要求您向其传递以下参数:

    • original: 你要搜索的字符串
    • pattern:要么是要搜索的字符串,要么是正则表达式带有捕获组。如果没有捕获组,它将引发错误。这是因为函数在原始字符串上调用了split,而only if the supplied RegExp contains a capture group will the resulting array contain the matches
    • 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'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 个参数。通过将其作为方法添加到 String 原型,可以将其简化为只需要 3 个参数,如下所示:

    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 中可能会失败,因为该浏览器使用正则表达式错误地拆分字符串,如 here 所述。 [对着 IE7 握拳]。我相信this 是解决方案;如果你需要支持 IE7,祝你好运。 :)

    【讨论】:

    • 太棒了!这应该是一个 Node.js 和 Bower 包。
    • @vaughan - 谢谢!如果您愿意,请随意制作。
    • 如果正则表达式包含一对以上的括号怎么办?
    • @principal-ideal-domain 有一个很好的观点。我不能使用它,因为我的模式中有括号。我正在尝试对代码进行更改以解决此问题,但我现在很幸运。
    【解决方案2】:

    以下是可行的:

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

    【讨论】:

      【解决方案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;
              }
          });
      }
      

      【讨论】:

        猜你喜欢
        • 2023-03-25
        • 2019-04-14
        • 2015-08-07
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2014-06-30
        相关资源
        最近更新 更多