Comment puis-je concaténer des littéraux regex en JavaScript?

145

Est-il possible de faire quelque chose comme ça?

var pattern = /some regex segment/ + /* comment here */
    /another segment/;

Ou dois-je utiliser une nouvelle RegExp()syntaxe et concaténer une chaîne? Je préférerais utiliser le littéral car le code est à la fois plus évident et concis.

paupière
la source
2
Il est plus facile de gérer les caractères regex échappés si vous utilisez String.raw ():let regexSegment1 = String.raw`\s*hello\s*`
iono

Réponses:

190

Voici comment créer une expression régulière sans utiliser la syntaxe littérale d'expression régulière. Cela vous permet de faire une manipulation de chaîne arbitraire avant qu'elle ne devienne un objet d'expression régulière:

var segment_part = "some bit of the regexp";
var pattern = new RegExp("some regex segment" + /*comment here */
              segment_part + /* that was defined just now */
              "another segment");

Si vous avez deux littéraux d'expression régulière, vous pouvez en fait les concaténer en utilisant cette technique:

var regex1 = /foo/g;
var regex2 = /bar/y;
var flags = (regex1.flags + regex2.flags).split("").sort().join("").replace(/(.)(?=.*\1)/g, "");
var regex3 = new RegExp(expression_one.source + expression_two.source, flags);
// regex3 is now /foobar/gy

C'est juste plus verbeux que d'avoir les expressions un et deux qui sont des chaînes littérales au lieu d'expressions régulières littérales.

Jerub
la source
2
Gardez à l'esprit que chaque segment doit être une expression régulière valide lorsque vous utilisez cette approche. Construire une expression telle que new RegExp(/(/.source + /.*/.source + /)?/.source);ne semble pas fonctionner.
Sam
Cette solution ne fonctionne pas dans le cas de groupes de correspondance arrière. Voir ma réponse pour une solution de travail dans ce cas.
Mikaël Mayer
Si vous avez besoin d'échapper à un caractère, utilisez des doubles barres obliques inverses: new Regexp ('\\ $' + "flum")
Jeff Lowery
Vous pouvez accéder aux drapeaux si vous le devez avec "<regexp> .flags", donc théoriquement vous pouvez aussi les combiner.
bnunamak le
D'où venez-vous expression_one? Voulez-vous dire regex1?
TallOrderDev
30

La simple concaténation aléatoire d' objets d' expressions régulières peut avoir des effets secondaires indésirables. Utilisez plutôt RegExp.source :

var r1 = /abc/g;
var r2 = /def/;
var r3 = new RegExp(r1.source + r2.source, 
                   (r1.global ? 'g' : '') 
                   + (r1.ignoreCase ? 'i' : '') + 
                   (r1.multiline ? 'm' : ''));
console.log(r3);
var m = 'test that abcdef and abcdef has a match?'.match(r3);
console.log(m);
// m should contain 2 matches

Cela vous donnera également la possibilité de conserver les indicateurs d'expressions régulières d'un précédent RegExp en utilisant les indicateurs standard de RegExp.

jsFiddle

Japhet Salva
la source
Cela peut être amélioré en utilisantRegExp.prototype.flags
Dmitry Parzhitsky
19

Je ne suis pas tout à fait d'accord avec l'option "eval".

var xxx = /abcd/;
var yyy = /efgh/;
var zzz = new RegExp(eval(xxx)+eval(yyy));

donnera "// abcd // efgh //" qui n'est pas le résultat attendu.

Utiliser une source comme

var zzz = new RegExp(xxx.source+yyy.source);

donnera "/ abcdefgh /" et c'est correct.

Logiquement il n'y a pas besoin d'EVALUER, vous connaissez votre EXPRESSION. Vous avez juste besoin de sa SOURCE ou de la façon dont elle est écrite pas nécessairement de sa valeur. En ce qui concerne les drapeaux, il vous suffit d'utiliser l'argument optionnel de RegExp.

Dans ma situation, je lance le problème de ^ et $ étant utilisé dans plusieurs expressions que j'essaye de concaténer ensemble! Ces expressions sont des filtres de grammaire utilisés dans le programme. Maintenant, je ne veux pas utiliser certains d'entre eux ensemble pour gérer le cas des PRÉPOSITIONS. Je devrai peut-être "découper" les sources pour supprimer le début et la fin ^ (et / ou) $ :) Cheers, Alex.

Alex
la source
J'aime l'utilisation de la propriété source. Si vous - comme moi - utilisez jslint, cela vous ennuiera si vous faites quelque chose comme ça:var regex = "\.\..*"
Nils-o-mat
7

Problème Si l'expression rationnelle contient des groupes de correspondance arrière tels que \ 1.

var r = /(a|b)\1/  // Matches aa, bb but nothing else.
var p = /(c|d)\1/   // Matches cc, dd but nothing else.

Ensuite, simplement contaténer les sources ne fonctionnera pas. En effet, la combinaison des deux est:

var rp = /(a|b)\1(c|d)\1/
rp.test("aadd") // Returns false

La solution: nous comptons d'abord le nombre de groupes correspondants dans la première expression régulière, puis pour chaque jeton de correspondance arrière dans la seconde, nous l'incrémentons du nombre de groupes correspondants.

function concatenate(r1, r2) {
  var count = function(r, str) {
    return str.match(r).length;
  }
  var numberGroups = /([^\\]|^)(?=\((?!\?:))/g; // Home-made regexp to count groups.
  var offset = count(numberGroups, r1.source);    
  var escapedMatch = /[\\](?:(\d+)|.)/g;        // Home-made regexp for escaped literals, greedy on numbers.
  var r2newSource = r2.source.replace(escapedMatch, function(match, number) { return number?"\\"+(number-0+offset):match; });
  return new RegExp(r1.source+r2newSource,
      (r1.global ? 'g' : '') 
      + (r1.ignoreCase ? 'i' : '')
      + (r1.multiline ? 'm' : ''));
}

Tester:

var rp = concatenate(r, p) // returns  /(a|b)\1(c|d)\2/
rp.test("aadd") // Returns true
Mikaël Mayer
la source
2
Oui (je ne le modifierai pas ici cependant). Cette fonction est associative, vous pouvez donc utiliser le code suivant:function concatenateList() { var res = arguments[0]; for(var i = 1; i < arguments.length; i++) { res = concatenate(res, arguments[i]); } return res; }
Mikaël Mayer
3

Il serait préférable d'utiliser la syntaxe littérale aussi souvent que possible. Il est plus court, plus lisible et vous n'avez pas besoin de guillemets d'échappement ou de doubles échappements. Tiré de «Javascript Patterns», Stoyan Stefanov 2010.

Mais utiliser New peut être le seul moyen de concaténer.

J'éviterais eval. Ce n'est pas prudent.

Jonathan Wright
la source
1
Je pense que les expressions régulières complexes sont plus lisibles lorsqu'elles sont décomposées et commentées comme dans la question.
Sam
3

À condition que:

  • vous savez ce que vous faites dans votre expression régulière;
  • vous avez de nombreux morceaux de regex pour former un motif et ils utiliseront le même drapeau;
  • vous trouvez qu'il est plus lisible de séparer vos petits morceaux de motif dans un tableau;
  • vous voulez également pouvoir commenter chaque partie pour le prochain développement ou vous-même plus tard;
  • vous préférez simplifier visuellement votre regex comme /this/gplutôt que new RegExp('this', 'g');
  • il est normal que vous assembliez le regex en une étape supplémentaire plutôt que de l'avoir en un seul morceau dès le début;

Ensuite, vous pouvez écrire de cette façon:

var regexParts =
    [
        /\b(\d+|null)\b/,// Some comments.
        /\b(true|false)\b/,
        /\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|length|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/,
        /(\$|jQuery)/,
        /many more patterns/
    ],
    regexString  = regexParts.map(function(x){return x.source}).join('|'),
    regexPattern = new RegExp(regexString, 'g');

vous pouvez alors faire quelque chose comme:

string.replace(regexPattern, function()
{
    var m = arguments,
        Class = '';

    switch(true)
    {
        // Numbers and 'null'.
        case (Boolean)(m[1]):
            m = m[1];
            Class = 'number';
            break;

        // True or False.
        case (Boolean)(m[2]):
            m = m[2];
            Class = 'bool';
            break;

        // True or False.
        case (Boolean)(m[3]):
            m = m[3];
            Class = 'keyword';
            break;

        // $ or 'jQuery'.
        case (Boolean)(m[4]):
            m = m[4];
            Class = 'dollar';
            break;

        // More cases...
    }

    return '<span class="' + Class + '">' + m + '</span>';
})

Dans mon cas particulier (un éditeur de type miroir de code), il est beaucoup plus facile d'exécuter une grande regex, plutôt que beaucoup de remplacements comme suit car chaque fois que je remplace par une balise html pour envelopper une expression, le modèle suivant sera être plus difficile à cibler sans affecter la balise html elle-même (et sans le bon lookbehind qui n'est malheureusement pas pris en charge en javascript):

.replace(/(\b\d+|null\b)/g, '<span class="number">$1</span>')
.replace(/(\btrue|false\b)/g, '<span class="bool">$1</span>')
.replace(/\b(new|getElementsBy(?:Tag|Class|)Name|arguments|getElementById|if|else|do|null|return|case|default|function|typeof|undefined|instanceof|this|document|window|while|for|switch|in|break|continue|var|(?:clear|set)(?:Timeout|Interval))(?=\W)/g, '<span class="keyword">$1</span>')
.replace(/\$/g, '<span class="dollar">$</span>')
.replace(/([\[\](){}.:;,+\-?=])/g, '<span class="ponctuation">$1</span>')
Antoni
la source
2

Vous pouvez faire quelque chose comme:

function concatRegex(...segments) {
  return new RegExp(segments.join(''));
}

Les segments seraient des chaînes (plutôt que des littéraux regex) transmis en tant qu'arguments séparés.

Neil Strain
la source
1

Non, la méthode littérale n'est pas prise en charge. Vous devrez utiliser RegExp.

Aupajo
la source
1

Utilisez le constructeur avec 2 paramètres et évitez le problème avec '/' de fin:

var re_final = new RegExp("\\" + ".", "g");    // constructor can have 2 params!
console.log("...finally".replace(re_final, "!") + "\n" + re_final + 
    " works as expected...");                  // !!!finally works as expected

                         // meanwhile

re_final = new RegExp("\\" + "." + "g");              // appends final '/'
console.log("... finally".replace(re_final, "!"));    // ...finally
console.log(re_final, "does not work!");              // does not work
ph7
la source
1

Vous pouvez concaténer une source d'expression régulière à partir de la classe littérale et RegExp:

var xxx = new RegExp(/abcd/);
var zzz = new RegExp(xxx.source + /efgh/.source);
Jeff Lowery
la source
1

le moyen le plus simple pour moi serait de concaténer les sources, ex .:

a = /\d+/
b = /\w+/
c = new RegExp(a.source + b.source)

la valeur c se traduira par:

/ \ d + \ w + /

Daniel Aragão
la source
-2

Je préfère l'utiliser eval('your expression')car il n'ajoute pas le /à chaque extrémité /qui le ='new RegExp'fait.

Praesagus
la source