web-dev-qa-db-fra.com

Comment concaténer des littéraux de regex en JavaScript?

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

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

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

127
eyelidlessness

Voici comment créer une expression régulière sans utiliser la syntaxe littérale d'expression régulière. Cela vous permet de manipuler arbitrairement une chaîne 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 plus verbeux que d'avoir simplement l'expression un et deux comme étant des chaînes littérales au lieu d'expressions régulières littérales.

175
Jerub

Juste concaténer au hasard des expressions régulières objets 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' : ''));
var m = 'test that abcdef and abcdef has a match?'.match(r3);
// m should contain 2 matches

Cela vous donnera également la possibilité de conserver les indicateurs d'expression régulière d'un RegExp précédent à l'aide des indicateurs RegExp standard.

jsFiddle

22
Japheth Salva

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 souhaité.

Utiliser une source comme

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

va donner "/ abcdefgh /" et c'est correct.

Logiquement, il n’est pas nécessaire d’ÉVALUER, vous connaissez votre EXPRESSION. Vous avez juste besoin de sa source ou comment il est écrit pas nécessairement sa valeur. En ce qui concerne les drapeaux, il vous suffit d’utiliser l’argument optionnel de RegExp.

Dans ma situation, je cours dans la question de ^ et $ étant utilisé dans plusieurs expressions que je tente de concaténer ensemble! Ces expressions sont des filtres de grammaire utilisés dans l’ensemble du programme. Maintenant, je ne voudrais pas utiliser certains d’eux ensemble pour traiter le cas des PRÉPOSITIONS. Je devrai peut-être "trancher" les sources pour supprimer le début et la fin de ^ (et/ou) $ :) À la vôtre, Alex.

15
Alex

Problème Si l'expression rationnelle contient des groupes de back-matching 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, le simple fait de contater 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 rationnelle, puis pour chaque jeton de correspondance 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
7
Mikaël Mayer

À condition que:

  • vous savez ce que vous faites dans votre expression rationnelle;
  • vous avez beaucoup 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 modèle en un tableau;
  • vous voulez aussi pouvoir commenter chaque partie pour le prochain dev ou vous-même plus tard;
  • vous préférez simplifier visuellement votre expression rationnelle comme /this/g plutôt que new RegExp('this', 'g');
  • c'est bien pour vous d'assembler la 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 ressemblant à un code-miroir), il est beaucoup plus facile d’effectuer une grande regex plutôt que beaucoup de remplacements comme suit, car chaque fois que je remplace une balise html pour envelopper une expression, le motif suivant être plus difficile à cibler sans affecter la balise html elle-même (et sans le bon lookbehind qui n'est malheureusement pas supporté 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>')
4
antoni

Vous pourriez faire quelque chose comme:

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

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

2
Neil Strain

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 ni de retours en double. D'après "Patterns Javascript", Stoyan Stefanov 2010.

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

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

2
Jonathan Wright

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

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
2
ph7

Vous devrez utiliser le nouveau RegExp! -)

2
roenving

Non, la manière littérale n'est pas supportée. Vous devrez utiliser RegExp.

2
Aupajo