我正在尝试编写一个正则表达式函数,该函数将识别并替换字符串中匹配的单个实例,而不会影响其他实例.例如,我有这个字符串:
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
我知道,我可以只拆分/替换/ CONCAT字符串在管,我也知道,我可以匹配/\|\|/
,并通过生成的数组迭代,但我想知道如果有可能写一个表达式,可以做这个.请注意,这将是JavaScript,因此有可能产生在运行时使用正则表达式eval()
,但它不可能使用任何Perl的正则表达式的具体说明.
我遇到了这个问题,虽然标题很一般,但是接受的答案只处理问题的具体用例.
我需要一个更通用的解决方案,所以我写了一个并认为我会在这里分享它.
此函数要求您传递以下参数:
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'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上握拳].我相信这是解决方案; 如果你需要支持IE7,祝你好运.:)
这是有用的东西:
"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&&"); }