regexp - Sustitución de la enésima instancia de una coincidencia de expresiones regulares en Javascript
probar expresiones regulares (3)
Una función más general
Me encontré con esta pregunta y, aunque el título es muy general, la respuesta aceptada maneja solo el caso de uso específico de la pregunta.
Necesitaba una solución más general, así que escribí una y pensé que la compartiría aquí.
Uso
Esta función requiere que le pase los siguientes argumentos:
-
original
: la cadena que estás buscando -
pattern
: una cadena para buscar o un RegExp con un grupo de captura . Sin un grupo de captura, arrojará un error. Esto se debe a que las llamadas a funciones sesplit
en la cadena original, y solo si el RegExp suministrado contiene un grupo de captura, la matriz resultante contendrá las coincidencias . -
n
: la ocurrencia ordinal para encontrar; por ejemplo, si quieres el segundo partido, pasa a2
-
replace
: o una cadena para reemplazar la coincidencia, o una función que tomará la coincidencia y devolverá una cadena de reemplazo.
Ejemplos
// 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"
El código
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('''');
}
Una forma alternativa de definirlo
La parte menos atractiva de esta función es que toma 4 argumentos. Se podría simplificar para necesitar solo 3 argumentos agregándolo como un método al prototipo de cadena, como este:
String.prototype.replaceNthMatch = function(pattern, n, replace) {
// Same code as above, replacing "original" with "this"
};
Si lo haces, puedes llamar al método en cualquier cadena, como esta:
"foo-bar-foo".replaceNthMatch("foo", 2, "baz"); // "foo-bar-baz"
Pasando Pruebas
Las siguientes son las pruebas de Jasmine que esta función pasa.
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]");
});
});
});
Puede no funcionar en IE7
Este código puede fallar en IE7 porque ese navegador divide incorrectamente cadenas usando una expresión regular, como se explica here . [sacude el puño en IE7]. Creo que this es la solución; si necesita soportar IE7, buena suerte. :)
Estoy intentando escribir una función de expresión regular que identificará y reemplazará una única instancia de una coincidencia dentro de una cadena sin afectar a las otras instancias. Por ejemplo, tengo esta cadena:
12||34||56
Quiero reemplazar el segundo conjunto de tuberías con ampersands para obtener esta cadena:
12||34&&56
La función de expresión regular debe ser capaz de manejar una cantidad x de tuberías y permitirme reemplazar el enésimo conjunto de tuberías, por lo que podría usar la misma función para hacer estos reemplazos:
23||45||45||56||67 -> 23&&45||45||56||67
23||34||98||87 -> 23||34||98&&87
Sé que podría simplemente dividir / reemplazar / concatenar la cadena en las tuberías, y también sé que puedo hacer coincidir en //|/|/
e iterar a través de la matriz resultante, pero me interesa saber si es posible escribe una sola expresión que puede hacer esto. Tenga en cuenta que esto sería para Javascript, por lo que es posible generar una expresión regular en tiempo de ejecución utilizando eval()
, pero no es posible utilizar ninguna instrucción de expresiones regulares específica de Perl.
aquí hay algo que funciona:
"23||45||45||56||67".replace(/^((?:[0-9]+/|/|){n})([0-9]+)/|/|/,"$1$2&&")
donde n es la menos que la enésima tubería, (por supuesto, no necesita esa primera subexpresión si n = 0)
Y si desea una función para hacer esto:
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;
}
});
}