Javascript에서 정규식 일치의 n번째 인스턴스 바꾸기
-
09-06-2019 - |
문제
다른 인스턴스에 영향을 주지 않고 문자열 내에서 일치 항목의 단일 인스턴스를 식별하고 바꾸는 정규식 함수를 작성하려고 합니다.예를 들어 다음 문자열이 있습니다.
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 관련 정규식 명령어를 사용하는 것은 불가능합니다.
해결책
여기 작동하는 것이 있습니다:
"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&&");
}
다른 팁
보다 범용적인 기능
이 질문을 접했는데 제목이 매우 일반적이지만 허용되는 답변은 질문의 특정 사용 사례만 처리합니다.
좀 더 범용적인 솔루션이 필요했기 때문에 하나를 작성하고 여기에서 공유해야겠다고 생각했습니다.
용법
이 함수를 사용하려면 다음 인수를 전달해야 합니다.
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에서 주먹을 흔든다].나는 그것을 믿는다 이것 해결책입니다;IE7을 지원해야 한다면 행운을 빕니다.:)
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;
}
});
}