Замена n-го экземпляра соответствия регулярному выражению в Javascript
-
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
:либо строка для поиска, либо регулярное выражение с группой захвата.Без группы захвата он выдаст сообщение об ошибке.Это происходит потому, что функция вызываетsplit
в исходной строке, и только в том случае, если предоставленное регулярное выражение содержит группу захвата, результирующий массив будет содержать совпадения.n
:порядковое вхождение, которое нужно найти;например, если вы хотите провести 2-й матч, передайте в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 аргумента.Это можно было бы упростить, чтобы требовалось всего 3 аргумента, добавив его в качестве метода к String prototype, например:
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;
}
});
}