2008-10-09 14 views
100

Est-il possible de faire quelque chose comme ça?Comment concaténer des littéraux regex en JavaScript?

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

Ou dois-je utiliser une 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.

+0

Je me sens un badge d'or à venir - – danday74

Répondre

152

Voici comment créer une expression régulière sans utiliser l'expression régulière syntaxe littérale. Cela vous permet de faire la manipulation de chaînes arbitraire avant qu'il 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 expression_one = /foo/; 
var expression_two = /bar/; 
var expression_three = new RegExp(expression_one.source + expression_two.source); 

Il est pas tout à fait une bonne solution, car vous perdez les indicateurs qui ont été définis sur expression_one et expression_two, et est plus verbeux que d'avoir juste l'expression un et deux étant des chaînes littérales au lieu d'expressions régulières littérales.

+13

Pour ne pas être impoli mais, je pense qu'il était clair que je le sais, je demandais spécifiquement si cela peut être fait avec le littéral. – eyelidlessness

+90

Stackoverflow n'est pas juste pour vous et moi, il est conçu pour être recherché et utilisé comme une archive de questions et réponses - j'ai donné un exemple de code approprié afin que quelqu'un avec une question similaire puisse facilement voir une solution appropriée. – Jerub

+1

var regex1 =/asdf /; var regex2 =/qwerty /; var regex3 = nouveau RegExp (regex1 + regex2); ///\/asdf \/\/qwerty \ // – eyelidlessness

0

Vous devez utiliser les nouvelles RegExp -)

0

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

-1

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

10

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 //" ce qui n'est pas le résultat escompté.

Utilisant des sources comme

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

donnera "/ abcdefgh /" et qui est correct.

Logiquement, il n'y a pas besoin d'ÉVALUER, vous connaissez votre EXPRESSION. Vous avez juste besoin de sa source ou de la façon dont il est écrit pas forcément sa valeur. En ce qui concerne les drapeaux, il suffit d'utiliser l'argument optionnel de RegExp.

Dans ma situation, je cours dans 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 à travers le programme. Maintenant, je ne veux pas utiliser certains d'entre eux pour gérer le cas de PREPOSITIONS. Je peux avoir à "découper" les sources pour supprimer le début et la fin^(et/ou) $ :) Cheers, Alex.

+0

J'aime l'utilisation de la propriété-source. Si vous - comme moi - utilisez jslint, cela vous gênera si vous faites quelque chose comme ceci: 'var regex =" \. \ .. * "' –

0

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 backlashes à double échappement. De "Javascript Patterns", Stoyan Stefanov 2010.

Mais l'utilisation de New peut être la seule façon de concaténer.

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

+1

Je pense que les expressions régulières complexes sont plus lisibles lorsqu'elles sont séparées et commentées comme dans la question. – Sam

15

Juste concaténation aléatoire des expressions régulières objets peuvent avoir des effets secondaires indésirables.Utilisez le RegExp.source à la place:

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 drapeaux réguliers d'expression d'un RegExp précédent en utilisant les drapeaux standards RegExp.

jsFiddle

+5

Je vous donne le crédit pour la recombinaison des drapeaux dans ma solution. –

+0

Cela peut être amélioré en utilisant 'RegExp.prototype.flags' –

1

Utilisez le constructeur avec 2 params et éviter le problème avec fuite '/':

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 
6

Problème Si l'expression rationnelle contient des groupes de retour correspondant comme \ 1.

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

Ensuite, la contatation des 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: D'abord, nous comptons le nombre de groupes correspondant à la première regex, puis pour chaque jeton de retour correspondant dans la seconde, nous incrémentons le 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' : '')); 
} 

Test:

var rp = concatenate(r, p) // returns /(a|b)\1(c|d)\2/ 
rp.test("aadd") // Returns true 
+0

La fonction peut-elle être améliorée pour fonctionner avec n'importe quelle arité? Merci! – paulkon

+2

Oui (je ne vais pas le modifier ici cependant). Cette fonction est associative, vous pouvez donc utiliser le code suivant: 'function concatenateList() {var res = arguments [0]; pour (var i = 1; i

2

condition:

  • vous savez ce que vous faites dans votre regexp;
  • vous avez beaucoup de morceaux 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 en un tableau;
  • vous voulez également être en mesure de commenter chaque partie pour le prochain dev ou vous-même plus tard;
  • vous préférez simplifier visuellement votre regex comme /this/g plutôt que new RegExp('this', 'g');
  • vous pouvez assembler la regex en une étape supplémentaire plutôt que de l'avoir en une seule fois depuis le début;

Ensuite, vous pouvez vous écrire ainsi:

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 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-miroir code), Il est beaucoup plus facile d'effectuer une grosse regex, plutôt que de la remplacer comme chaque fois que je remplace par une balise html pour envelopper une expression, le prochain modèle sera plus difficile à cibler sans affecter le tag html lui-même (et sans bien lookbehind qui est malheureusement pas pris en charge 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>') 
0

Vous pouvez faire quelque chose comme:

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

Les segments seraient cordes (plutôt que littéraux regex) adoptée en arguments séparés.

Questions connexes