[英]Replacing the nth instance of a regex match in Javascript
I'm trying to write a regex function that will identify and replace a single instance of a match within a string without affecting the other instances.我正在尝试编写一个正则表达式 function,它将识别并替换字符串中匹配项的单个实例,而不会影响其他实例。 For example, I have this string:
例如,我有这个字符串:
12||34||56
I want to replace the second set of pipes with ampersands to get this string:我想用&符号替换第二组管道以获取此字符串:
12||34&&56
The regex function needs to be able to handle x amount of pipes and allow me to replace the nth set of pipes, so I could use the same function to make these replacements:正则表达式 function 需要能够处理 x 数量的管道并允许我替换第 n 组管道,因此我可以使用相同的 function 来进行这些替换:
23||45||45||56||67 -> 23&&45||45||56||67
23||34||98||87 -> 23||34||98&&87
I know that I could just split/replace/concat the string at the pipes, and I also know that I can match on /\|\|/
and iterate through the resulting array, but I'm interested to know if it's possible to write a single expression that can do this.我知道我可以在管道上拆分/替换/连接字符串,我也知道我可以匹配
/\|\|/
并遍历结果数组,但我想知道是否有可能写一个可以做到这一点的表达式。 Note that this would be for Javascript, so it's possible to generate a regex at runtime using eval()
, but it's not possible to use any Perl-specific regex instructions.请注意,这将用于 Javascript,因此可以在运行时使用
eval()
生成正则表达式,但不可能使用任何特定于 Perl 的正则表达式指令。
I came across this question and, although the title is very general, the accepted answer handles only the question's specific use case. 我遇到了这个问题,虽然标题很一般,但是接受的答案只处理问题的具体用例。
I needed a more general-purpose solution, so I wrote one and thought I'd share it here. 我需要一个更通用的解决方案,所以我写了一个并认为我会在这里分享它。
This function requires that you pass it the following arguments: 此函数要求您传递以下参数:
original
: the string you're searching in original
:您正在搜索的字符串 pattern
: either a string to search for, or a RegExp with a capture group . pattern
:要搜索的字符串,要么是带有捕获组的RegExp。 Without a capture group, it will throw an error. split
on the original string, and only if the supplied RegExp contains a capture group will the resulting array contain the matches . split
,并且只有当提供的RegExp包含捕获组时,结果数组才会包含匹配项 。 n
: the ordinal occurrence to find; n
:要查找的序数事件; eg, if you want the 2nd match, pass in 2
2
replace
: Either a string to replace the match with, or a function which will take in the match and return a replacement string. 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('');
}
The least appealing part of this function is that it takes 4 arguments. 这个函数最不吸引人的部分是它需要4个参数。 It could be simplified to need only 3 arguments by adding it as a method to the String prototype, like this:
通过将它作为方法添加到String原型,可以简化为只需要3个参数,如下所示:
String.prototype.replaceNthMatch = function(pattern, n, replace) {
// Same code as above, replacing "original" with "this"
};
If you do that, you can call the method on any string, like this: 如果你这样做,你可以在任何字符串上调用方法,如下所示:
"foo-bar-foo".replaceNthMatch("foo", 2, "baz"); // "foo-bar-baz"
The following are the Jasmine tests that this function passes. 以下是此功能通过的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]");
});
});
});
This code may fail in IE7 because that browser incorrectly splits strings using a regex, as discussed here . 该代码可以在IE7中失败,因为该浏览器使用正则表达式正确分割字符串,如讨论在这里 。 [shakes fist at IE7].
[在IE7上握拳]。 I believe that this is the solution;
我相信这是解决方案; if you need to support IE7, good luck.
如果你需要支持IE7,祝你好运。 :)
:)
here's something that works: 这是有用的东西:
"23||45||45||56||67".replace(/^((?:[0-9]+\|\|){n})([0-9]+)\|\|/,"$1$2&&")
where n is the one less than the nth pipe, (of course you don't need that first subexpression if n = 0) 其中n是小于第n个管道的那个(当然,如果n = 0,则不需要第一个子表达式)
And if you'd like a function to do this: 如果你想要一个函数来做到这一点:
function pipe_replace(str,n) {
var RE = new RegExp("^((?:[0-9]+\\|\\|){" + (n-1) + "})([0-9]+)\|\|");
return str.replace(RE,"$1$2&&");
}
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;
}
});
}
Thanks Binda, I have modified the code for generic uses:谢谢 Binda,我修改了通用代码:
private replaceNthMatch(original, pattern, n, replace) {
let m = -1;
return original.replaceAll(pattern, x => {
m++;
if ( n == m ) {
return replace;
} else {
return x;
}
});
}
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.