Я пытаюсь написать функцию регулярного выражения, которая будет определять и заменять один экземпляр совпадения в строке, не затрагивая другие экземпляры. Например, у меня есть такая строка:
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 инструкции регулярного выражения.



![Безумие обратных вызовов в javascript [JS]](https://i.imgur.com/WsjO6zJb.png)


вот что работает:
"23||45||45||56||67".replace(/^((?:[0-9]+\|\|){n})([0-9]+)\|\|/,"&&")
где n меньше n-го канала (конечно, вам не нужно это первое подвыражение, если n = 0)
И если вы хотите, чтобы функция выполняла это:
function pipe_replace(str,n) {
var RE = new RegExp("^((?:[0-9]+\|\|){" + (n-1) + "})([0-9]+)\|\|");
return str.replace(RE,"&&");
}
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;
}
});
}
Я столкнулся с этим вопросом, и, хотя заголовок носит очень общий характер, принятый ответ касается только конкретного варианта использования вопроса.
Мне нужно было более универсальное решение, поэтому я написал его и решил поделиться им здесь.
Эта функция требует, чтобы вы передали ей следующие аргументы:
original: строка, в которой вы ищетеpattern: либо строка для поиска, либо RegExp с группой захвата. Без группы захвата будет выдана ошибка. Это потому, что функция вызывает split в исходной строке и только если предоставленный RegExp содержит группу захвата, результирующий массив будет содержать совпадения.n: порядковое вхождение, которое нужно найти; например, если вы хотите второе совпадение, передайте 2replace: либо строка для замены совпадения, либо функция, которая примет совпадение и вернет заменяющую строку.// 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"
};
Если вы это сделаете, вы можете вызвать метод для любой строки, например:
"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, удачи. :)
@vaughan - Спасибо! Не стесняйтесь сделать это, если хотите.
Что, если регулярное выражение содержит более одной пары круглых скобок?
@ Principal-ideal-domain имеет хороший смысл. Я не могу использовать это, потому что в моем шаблоне есть круглые скобки. Я пытаюсь найти изменение в коде, чтобы исправить это, но теперь мне повезло.
Браво! Это должен быть пакет Node.js и Bower.