Hasta donde yo sé, no hay grupos de captura con nombre en JavaScript. ¿Cuál es la forma alternativa de obtener una funcionalidad similar?
Hasta donde yo sé, no hay grupos de captura con nombre en JavaScript. ¿Cuál es la forma alternativa de obtener una funcionalidad similar?
Respuestas:
ECMAScript 2018 introduce grupos de captura con nombre en expresiones regulares de JavaScript.
Ejemplo:
const auth = 'Bearer AUTHORIZATION_TOKEN'
const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
console.log(token) // "Prints AUTHORIZATION_TOKEN"
Si necesita admitir navegadores antiguos, puede hacer todo con grupos de captura normales (numerados) que puede hacer con grupos de captura con nombre, solo necesita realizar un seguimiento de los números, lo que puede ser engorroso si el orden del grupo de captura en su cambios de expresiones regulares.
Solo se me ocurren dos ventajas "estructurales" de los grupos de captura con nombre:
En algunos sabores de expresiones regulares (.NET y JGSoft, hasta donde yo sé), puede usar el mismo nombre para diferentes grupos en su expresión regular ( vea aquí un ejemplo donde esto importa ). Pero la mayoría de los sabores de expresiones regulares no son compatibles con esta funcionalidad de todos modos.
Si necesita referirse a grupos de captura numerados en una situación en la que están rodeados de dígitos, puede tener un problema. Digamos que desea agregar un cero a un dígito y, por lo tanto, desea reemplazar (\d)
con $10
. En JavaScript, esto funcionará (siempre que tenga menos de 10 grupos de captura en su expresión regular), pero Perl pensará que está buscando un número de referencia en 10
lugar de un número 1
, seguido de a 0
. En Perl, puede usar ${1}0
en este caso.
Aparte de eso, los grupos de captura nombrados son simplemente "azúcar sintáctico". Es útil usar grupos de captura solo cuando realmente los necesita y usar grupos sin captura (?:...)
en todas las demás circunstancias.
El mayor problema (en mi opinión) con JavaScript es que no admite expresiones regulares detalladas, lo que facilitaría mucho la creación de expresiones regulares complejas y legibles.
La biblioteca XRegExp de Steve Levithan resuelve estos problemas.
Puede usar XRegExp , una implementación aumentada, extensible y de navegador cruzado de expresiones regulares, que incluye soporte para sintaxis, indicadores y métodos adicionales:
s
para que el punto coincida con todos los caracteres (también conocido como modo dotall o singleline) y x
para espacios libres y comentarios (también conocido como modo extendido).Otra posible solución: crear un objeto que contenga los nombres e índices del grupo.
var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };
Luego, use las teclas de objeto para hacer referencia a los grupos:
var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];
Esto mejora la legibilidad / calidad del código utilizando los resultados de la expresión regular, pero no la legibilidad de la propia expresión regular.
En ES6 puede usar la desestructuración de matrices para capturar sus grupos:
let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];
// count === '27'
// unit === 'months'
Aviso:
let
omite el primer valor de la matriz resultante, que es la cadena completa coincidente|| []
after .exec()
evitará un error de desestructuración cuando no haya coincidencias (porque .exec()
volverá null
)String.prototype.match
devuelve una matriz con: toda la cadena coincidente en la posición 0, luego cualquier grupo después de eso. La primera coma dice "omita el elemento en la posición 0"
RegExp.prototype.exec
más String.prototype.match
en lugares donde la cadena puede ser null
o undefined
.
Actualización: ¡Finalmente llegó a JavaScript (ECMAScript 2018)!
Los grupos de captura con nombre podrían ingresar a JavaScript muy pronto.
La propuesta está en la etapa 3 ya.
Un grupo de captura puede recibir un nombre dentro de corchetes angulares utilizando la (?<name>...)
sintaxis, para cualquier nombre de identificador. La expresión regular para una fecha se puede escribir como /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
. Cada nombre debe ser único y seguir la gramática de ECMAScript IdentifierName .
Se puede acceder a los grupos con nombre desde las propiedades de una propiedad de grupos del resultado de la expresión regular. También se crean referencias numeradas a los grupos, al igual que para los grupos sin nombre. Por ejemplo:
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';
// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';
let {year, month, day} = ((result) => ((result) ? result.groups : {}))(re.exec('2015-01-02'));
Nombrar grupos capturados proporciona una cosa: menos confusión con expresiones regulares complejas.
Realmente depende de su caso de uso, pero tal vez la impresión bonita de su expresión regular podría ayudar.
O podría intentar definir constantes para referirse a sus grupos capturados.
Los comentarios también pueden ayudar a mostrar a otros que leen su código, lo que ha hecho.
Por lo demás, debo estar de acuerdo con la respuesta de Tims.
Hay una biblioteca node.js llamada named-regexp que puede usar en sus proyectos node.js (en el navegador al empaquetar la biblioteca con browserify u otros scripts de empaquetado). Sin embargo, la biblioteca no se puede usar con expresiones regulares que contienen grupos de captura sin nombre.
Si cuenta las llaves de captura de apertura en su expresión regular, puede crear una asignación entre los grupos de captura con nombre y los grupos de captura numerados en su expresión regular y puede mezclar y combinar libremente. Solo tiene que eliminar los nombres de los grupos antes de usar la expresión regular. He escrito tres funciones que demuestran eso. Vea esta esencia: https://gist.github.com/gbirke/2cc2370135b665eee3ef
Como dijo Tim Pietzcker , ECMAScript 2018 introduce grupos de captura con nombre en expresiones regulares de JavaScript. Pero lo que no encontré en las respuestas anteriores fue cómo usar el grupo capturado nombrado en la expresión regular misma.
puede utilizar un grupo de captura llamado con esta sintaxis: \k<name>
. por ejemplo
var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/
y como Forivin dijo, puede usar el grupo capturado en el resultado del objeto de la siguiente manera:
let result = regexObj.exec('2019-28-06 year is 2019');
// result.groups.year === '2019';
// result.groups.month === '06';
// result.groups.day === '28';
var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/mgi;
function check(){
var inp = document.getElementById("tinput").value;
let result = regexObj.exec(inp);
document.getElementById("year").innerHTML = result.groups.year;
document.getElementById("month").innerHTML = result.groups.month;
document.getElementById("day").innerHTML = result.groups.day;
}
td, th{
border: solid 2px #ccc;
}
<input id="tinput" type="text" value="2019-28-06 year is 2019"/>
<br/>
<br/>
<span>Pattern: "(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>";
<br/>
<br/>
<button onclick="check()">Check!</button>
<br/>
<br/>
<table>
<thead>
<tr>
<th>
<span>Year</span>
</th>
<th>
<span>Month</span>
</th>
<th>
<span>Day</span>
</th>
</tr>
</thead>
<tbody>
<tr>
<td>
<span id="year"></span>
</td>
<td>
<span id="month"></span>
</td>
<td>
<span id="day"></span>
</td>
</tr>
</tbody>
</table>
Si bien no puede hacer esto con JavaScript de vainilla, tal vez pueda usar alguna Array.prototype
función como Array.prototype.reduce
convertir las coincidencias indexadas en nombradas usando algo de magia .
Obviamente, la siguiente solución necesitará que las coincidencias ocurran en orden:
// @text Contains the text to match
// @regex A regular expression object (f.e. /.+/)
// @matchNames An array of literal strings where each item
// is the name of each group
function namedRegexMatch(text, regex, matchNames) {
var matches = regex.exec(text);
return matches.reduce(function(result, match, index) {
if (index > 0)
// This substraction is required because we count
// match indexes from 1, because 0 is the entire matched string
result[matchNames[index - 1]] = match;
return result;
}, {});
}
var myString = "Hello Alex, I am John";
var namedMatches = namedRegexMatch(
myString,
/Hello ([a-z]+), I am ([a-z]+)/i,
["firstPersonName", "secondPersonName"]
);
alert(JSON.stringify(namedMatches));
var assocArray = Regex("hello alex, I am dennis", "hello ({hisName}.+), I am ({yourName}.+)");
RegExp
objeto agregando una función a su prototipo.
¿No tienes ECMAScript 2018?
Mi objetivo era hacer que funcionara lo más similar posible a lo que estamos acostumbrados con los grupos con nombre. Mientras que en ECMAScript 2018 puede colocar ?<groupname>
dentro del grupo para indicar un grupo con nombre, en mi solución para javascript anterior, puede colocar (?!=<groupname>)
dentro del grupo para hacer lo mismo. Entonces es un conjunto extra de paréntesis y un extra !=
. ¡Muy cerca!
Lo envolví todo en una función de prototipo de cadena
Caracteristicas
Instrucciones
(?!={groupname})
dentro de cada grupo que quieras nombrar()
colocando ?:
al principio de ese grupo. Estos no serán nombrados.arrays.js
// @@pattern - includes injections of (?!={groupname}) for each group
// @@returns - an object with a property for each group having the group's match as the value
String.prototype.matchWithGroups = function (pattern) {
var matches = this.match(pattern);
return pattern
// get the pattern as a string
.toString()
// suss out the groups
.match(/<(.+?)>/g)
// remove the braces
.map(function(group) {
return group.match(/<(.+)>/)[1];
})
// create an object with a property for each group having the group's match as the value
.reduce(function(acc, curr, index, arr) {
acc[curr] = matches[index + 1];
return acc;
}, {});
};
uso
function testRegGroups() {
var s = '123 Main St';
var pattern = /((?!=<house number>)\d+)\s((?!=<street name>)\w+)\s((?!=<street type>)\w+)/;
var o = s.matchWithGroups(pattern); // {'house number':"123", 'street name':"Main", 'street type':"St"}
var j = JSON.stringify(o);
var housenum = o['house number']; // 123
}
resultado de o
{
"house number": "123",
"street name": "Main",
"street type": "St"
}