Pour autant que je sache, il n'existe pas de groupes de capture nommés en JavaScript. Quelle est la méthode alternative pour obtenir des fonctionnalités similaires?
Pour autant que je sache, il n'existe pas de groupes de capture nommés en JavaScript. Quelle est la méthode alternative pour obtenir des fonctionnalités similaires?
Réponses:
ECMAScript 2018 introduit des groupes de capture nommés dans les expressions rationnelles JavaScript.
Exemple:
const auth = 'Bearer AUTHORIZATION_TOKEN'
const { groups: { token } } = /Bearer (?<token>[^ $]*)/.exec(auth)
console.log(token) // "Prints AUTHORIZATION_TOKEN"
Si vous devez prendre en charge des navigateurs plus anciens, vous pouvez tout faire avec des groupes de capture normaux (numérotés) que vous pouvez faire avec des groupes de capture nommés, il vous suffit de garder une trace des numéros - ce qui peut être lourd si l'ordre de capture du groupe dans votre changements d'expression régulière.
Les groupes de capture nommés ne présentent que deux avantages "structurels":
Dans certaines versions de regex (.NET et JGSoft, pour autant que je sache), vous pouvez utiliser le même nom pour différents groupes dans votre regex ( voir ici pour un exemple où cela est important ). Mais la plupart des versions regex ne prennent pas en charge cette fonctionnalité de toute façon.
Si vous devez faire référence à des groupes de capture numérotés dans une situation où ils sont entourés de chiffres, vous pouvez obtenir un problème. Disons que vous voulez ajouter un zéro à un chiffre et que vous souhaitez donc le remplacer (\d)
par $10
. En JavaScript, cela fonctionnera (tant que vous avez moins de 10 groupes de capture dans votre expression régulière), mais Perl pensera que vous recherchez un numéro de référence 10
au lieu d'un nombre 1
, suivi d'un 0
. En Perl, vous pouvez utiliser ${1}0
dans ce cas.
En dehors de cela, les groupes de capture nommés ne sont que du «sucre syntaxique». Il est utile d'utiliser des groupes de capture uniquement lorsque vous en avez vraiment besoin et d'utiliser des groupes non capturés.(?:...)
dans toutes les autres circonstances.
Le plus gros problème (à mon avis) avec JavaScript est qu'il ne prend pas en charge les expressions rationnelles verbeuses, ce qui faciliterait la création d'expressions régulières complexes et lisibles.
La bibliothèque XRegExp de Steve Levithan résout ces problèmes.
Vous pouvez utiliser XRegExp , une implémentation augmentée, extensible et multi-navigateur d'expressions régulières, y compris la prise en charge de syntaxe, indicateurs et méthodes supplémentaires:
s
pour faire correspondre les points à tous les caractères (aka mode dotall ou singleline), et x
, pour l'espacement libre et les commentaires (aka mode étendu).Autre solution possible: créer un objet contenant les noms de groupe et les index.
var regex = new RegExp("(.*) (.*)");
var regexGroups = { FirstName: 1, LastName: 2 };
Ensuite, utilisez les clés d'objet pour référencer les groupes:
var m = regex.exec("John Smith");
var f = m[regexGroups.FirstName];
Cela améliore la lisibilité / qualité du code en utilisant les résultats de l'expression régulière, mais pas la lisibilité de l'expression régulière elle-même.
Dans ES6, vous pouvez utiliser la déstructuration de tableau pour intercepter vos groupes:
let text = '27 months';
let regex = /(\d+)\s*(days?|months?|years?)/;
let [, count, unit] = regex.exec(text) || [];
// count === '27'
// unit === 'months'
Remarquer:
let
ignore la première valeur du tableau résultant, qui correspond à la chaîne entière correspondante|| []
after .exec()
empêchera une erreur de déstructuration quand il n'y a pas de correspondance (car .exec()
reviendra null
)String.prototype.match
renvoie un tableau avec: toute la chaîne correspondante à la position 0, puis tous les groupes après cela. La première virgule dit "sauter l'élément en position 0"
RegExp.prototype.exec
plus String.prototype.match
d'endroits où la chaîne peut être null
ou undefined
.
Mise à jour: il est enfin devenu JavaScript (ECMAScript 2018)!
Les groupes de capture nommés pourraient très bientôt devenir JavaScript.
La proposition en est déjà à l'étape 3.
Un groupe de capture peut recevoir un nom entre crochets angulaires à l'aide de la (?<name>...)
syntaxe, pour tout nom d'identifiant. L'expression régulière d'une date peut alors s'écrire /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u
. Chaque nom doit être unique et suivre la grammaire d'ECMAScript IdentifierName .
Les groupes nommés sont accessibles à partir des propriétés d'une propriété de groupes du résultat de l'expression régulière. Des références numérotées aux groupes sont également créées, tout comme pour les groupes non nommés. Par exemple:
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'));
Nommer les groupes capturés fournit une chose: moins de confusion avec les expressions régulières complexes.
Cela dépend vraiment de votre cas d'utilisation, mais peut-être qu'une jolie impression de votre expression régulière pourrait vous aider.
Ou vous pouvez essayer de définir des constantes pour faire référence à vos groupes capturés.
Les commentaires peuvent également aider à montrer aux autres qui ont lu votre code ce que vous avez fait.
Pour le reste, je dois être d'accord avec la réponse de Tims.
Il existe une bibliothèque node.js appelée named-regexp que vous pouvez utiliser dans vos projets node.js (dans le navigateur en empaquetant la bibliothèque avec browserify ou d'autres scripts d'empaquetage). Cependant, la bibliothèque ne peut pas être utilisée avec des expressions régulières qui contiennent des groupes de capture non nommés.
Si vous comptez les accolades de capture d'ouverture dans votre expression régulière, vous pouvez créer un mappage entre les groupes de capture nommés et les groupes de capture numérotés dans votre expression régulière et pouvez mélanger et assortir librement. Il vous suffit de supprimer les noms de groupe avant d'utiliser l'expression régulière. J'ai écrit trois fonctions qui le démontrent. Voir cet essentiel: https://gist.github.com/gbirke/2cc2370135b665eee3ef
Comme l'a dit Tim Pietzcker , ECMAScript 2018 introduit des groupes de capture nommés dans les expressions rationnelles JavaScript. Mais ce que je n'ai pas trouvé dans les réponses ci-dessus, c'est comment utiliser le groupe capturé nommé dans l'expression régulière elle-même.
vous pouvez utiliser le groupe capturé nommé avec cette syntaxe: \k<name>
. par exemple
var regexObj = /(?<year>\d{4})-(?<day>\d{2})-(?<month>\d{2}) year is \k<year>/
et comme Forivin l'a dit, vous pouvez utiliser le groupe capturé dans le résultat de l'objet comme suit:
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>
Bien que vous ne puissiez pas le faire avec JavaScript vanilla, vous pouvez peut-être utiliser une Array.prototype
fonction telle que Array.prototype.reduce
transformer les correspondances indexées en correspondances nommées en utilisant de la magie .
De toute évidence, la solution suivante devra que les correspondances se produisent dans l'ordre:
// @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
objet en ajoutant une fonction à son prototype.
Vous n'avez pas ECMAScript 2018?
Mon objectif était de faire en sorte que cela fonctionne le plus possible avec ce à quoi nous sommes habitués avec les groupes nommés. Alors que dans ECMAScript 2018, vous pouvez placer ?<groupname>
à l'intérieur du groupe pour indiquer un groupe nommé, dans ma solution pour les anciens javascript, vous pouvez placer (?!=<groupname>)
à l'intérieur du groupe pour faire la même chose. C'est donc un ensemble supplémentaire de parenthèses et un extra !=
. Assez proche!
J'ai tout enveloppé dans une fonction de prototype de chaîne
Caractéristiques
Instructions
(?!={groupname})
à l'intérieur de chaque groupe que vous souhaitez nommer()
en les mettant ?:
au début de ce groupe. Ceux-ci ne seront pas nommés.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;
}, {});
};
usage
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
}
résultat de o
{
"house number": "123",
"street name": "Main",
"street type": "St"
}