在Javascript中replace正则expression式匹配的第n个实例

我正在尝试编写一个正则expression式函数,它将识别并replacestring中单个匹配的实例,而不会影响其他实例。 例如,我有这个string:

12||34||56 

我想用&符replace第二组pipe道来获得这个string:

 12||34&&56 

正则expression式函数需要能够处理x数量的pipe道,并允许我replace第n组pipe道,所以我可以使用相同的函数来进行这些replace:

 23||45||45||56||67 -> 23&&45||45||56||67 23||34||98||87 -> 23||34||98&&87 

我知道我可以在pipe道上拆分/replace/连接string,并且我也知道我可以在/\|\|/上匹配,并遍历结果数组,但是我很想知道是否有可能写一个可以做到这一点的expression式。 请注意,这将用于Javascript,所以可以使用eval()在运行时生成正则expression式,但不能使用任何Perl特定的正则expression式指令。

这是有用的东西:

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

其中n是小于第n个pipe道的那个(当然如果n = 0则不需要第一个子expression式)

如果你想要一个function来做到这一点:

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

更通用的function

我遇到了这个问题,虽然标题是非常一般的,但是接受的答案只能处理问题的具体用例。

我需要一个更通用的解决scheme,所以我写了一个,并认为我会在这里分享。

用法

这个函数要求你传递下面的参数:

  • original :你正在search的string
  • pattern :要search的string或带有捕获组的RegExp。 没有捕获组,它会抛出一个错误。 这是因为该函数调用原始string上的split ,并且只有当提供的RegExp包含捕获组时,结果数组才包含匹配项 。
  • n :find的有序发生; 例如,如果你想要第二场比赛,那就通过2
  • replace :或者用来replace匹配的string,或者用来匹配并返回replacestring的函数。

例子

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

如果你这样做,你可以调用任何string的方法,如下所示:

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

通过testing

以下是该函数通过的Jasminetesting。

 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中失败,因为该浏览器不正确地使用正则expression式分割string,如此处所述。 [在IE7握拳]。 我相信这是解决办法; 如果你需要支持IE7,祝你好运。 🙂

这个答案超出了这个问题的范围。

但是这里是你如何在perl中取代第n个事件。

 perl -pe 's/(((.*?\|\|){1}){n-1})(.*?)\|\|/\1\4&\&/' 
 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; } }); }