Comment diviser une longue expression régulière en plusieurs lignes en JavaScript?

138

J'ai une très longue expression régulière, que je souhaite diviser en plusieurs lignes dans mon code JavaScript pour garder chaque ligne de 80 caractères selon les règles JSLint. C'est juste mieux pour lire, je pense. Voici un exemple de modèle:

var pattern = /^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$/;
Nik Sumeiko
la source
4
Il semble que vous (essayez de) valider les adresses e-mail. Pourquoi ne pas simplement faire /\S+@\S+\.\S+/?
Bart Kiers
1
Vous devriez probablement chercher un moyen de le faire sans expression régulière ou avec plusieurs expressions régulières plus petites. Ce serait beaucoup plus lisible qu'une expression régulière aussi longue. Si votre expression régulière comporte plus de 20 caractères environ, il existe probablement une meilleure façon de le faire.
ForbesLindesay
2
Les 80 caractères ne sont-ils pas obsolètes de nos jours avec des moniteurs larges?
Oleg V. Volkov
7
@ OlegV.Volkov Non. Une personne pourrait utiliser des fenêtres fractionnées dans vim, un terminal virtuel dans une salle de serveurs. Il est faux de supposer que tout le monde codera dans la même fenêtre que vous. De plus, limiter vos lignes à 80 caractères vous oblige à diviser votre code en fonctions plus petites.
synic
Eh bien, je vois certainement votre motivation pour vouloir faire cela ici - une fois que cette expression régulière est divisée sur plusieurs lignes, comme l'a démontré Koolilnc, elle devient immédiatement un exemple parfait de code lisible et auto-documenté. ¬_¬
Mark Amery

Réponses:

115

Vous pouvez le convertir en chaîne et créer l'expression en appelant new RegExp():

var myRE = new RegExp (['^(([^<>()[\]\\.,;:\\s@\"]+(\\.[^<>(),[\]\\.,;:\\s@\"]+)*)',
                        '|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                        '[0-9]{1,3}\])|(([a-zA-Z\-0-9]+\\.)+',
                        '[a-zA-Z]{2,}))$'].join(''));

Remarques:

  1. lors de la conversion du littéral d'expression en chaîne, vous devez échapper à toutes les barres obliques inverses car des barres obliques inverses sont consommées lors de l'évaluation d'un littéral de chaîne . (Voir le commentaire de Kayo pour plus de détails.)
  2. RegExp accepte les modificateurs comme deuxième paramètre

    /regex/g => new RegExp('regex', 'g')

[ Ajout ES20xx (modèle balisé)]

Dans ES20xx, vous pouvez utiliser des modèles balisés . Voir l'extrait.

Remarque:

  • Inconvénient est que vous ne pouvez pas utiliser des espaces plaine dans la chaîne d'expression régulière (toujours utiliser \s, \s+, \s{1,x}, \t, \netc.).

(() => {
  const createRegExp = (str, opts) => 
    new RegExp(str.raw[0].replace(/\s/gm, ""), opts || "");
  const yourRE = createRegExp`
    ^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|
    (\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|
    (([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$`;
  console.log(yourRE);
  const anotherLongRE = createRegExp`
    (\byyyy\b)|(\bm\b)|(\bd\b)|(\bh\b)|(\bmi\b)|(\bs\b)|(\bms\b)|
    (\bwd\b)|(\bmm\b)|(\bdd\b)|(\bhh\b)|(\bMI\b)|(\bS\b)|(\bMS\b)|
    (\bM\b)|(\bMM\b)|(\bdow\b)|(\bDOW\b)
    ${"gi"}`;
  console.log(anotherLongRE);
})();

KooiInc
la source
4
A new RegExpest un excellent moyen pour les expressions régulières multilignes. Au lieu de joindre des tableaux, vous pouvez simplement utiliser un opérateur de concaténation de chaînes:var reg = new RegExp('^([a-' + 'z]+)$','i');
dakab
43
Attention: un long littéral d'expression régulière peut être divisé en plusieurs lignes en utilisant la réponse ci-dessus. Cependant, il faut faire attention car vous ne pouvez pas simplement copier le littéral d'expression régulière (défini avec //) et le coller comme argument de chaîne dans le constructeur RegExp. Cela est dû au fait que les caractères anti-slash sont consommés lors de l'évaluation du littéral de chaîne . Exemple: /Hey\sthere/ne peut pas être remplacé par new RegExp("Hey\sthere"). Au lieu de cela, il doit être remplacé par new RegExp("Hey\\sthere")Notez la barre oblique inverse supplémentaire! Par conséquent, je préfère simplement laisser une longue expression régulière littérale sur une longue ligne
Kayo
5
Une façon encore plus claire de le faire est de créer des variables nommé tenant sous - sections significatives, et se joindre à ceux sous forme de chaînes ou dans un tableau. Cela vous permet de construire le RegExpd'une manière qui est beaucoup plus facile à comprendre.
Chris Krycho
117

En étendant la réponse @KooiInc, vous pouvez éviter d'échapper manuellement chaque caractère spécial en utilisant la sourcepropriété de l' RegExpobjet.

Exemple:

var urlRegex= new RegExp(''
  + /(?:(?:(https?|ftp):)?\/\/)/.source     // protocol
  + /(?:([^:\n\r]+):([^@\n\r]+)@)?/.source  // user:pass
  + /(?:(?:www\.)?([^\/\n\r]+))/.source     // domain
  + /(\/[^?\n\r]+)?/.source                 // request
  + /(\?[^#\n\r]*)?/.source                 // query
  + /(#?[^\n\r]*)?/.source                  // anchor
);

ou si vous voulez éviter de répéter la .sourcepropriété, vous pouvez le faire en utilisant la Array.map()fonction:

var urlRegex= new RegExp([
  /(?:(?:(https?|ftp):)?\/\/)/      // protocol
  ,/(?:([^:\n\r]+):([^@\n\r]+)@)?/  // user:pass
  ,/(?:(?:www\.)?([^\/\n\r]+))/     // domain
  ,/(\/[^?\n\r]+)?/                 // request
  ,/(\?[^#\n\r]*)?/                 // query
  ,/(#?[^\n\r]*)?/                  // anchor
].map(function(r) {return r.source}).join(''));

Dans ES6, la fonction de carte peut être réduite à: .map(r => r.source)

Korun
la source
3
Exactement ce que je cherchais, super propre. Merci!
Marian Zagoruiko
10
C'est vraiment pratique pour ajouter des commentaires à une longue expression rationnelle. Cependant, il est limité en ayant des parenthèses correspondantes sur la même ligne.
Nathan S. Watson-Haigh
Certainement, ça! Super sympa avec la possibilité de commenter chaque sous-regex.
GaryO
Merci, cela a aidé à mettre la source dans la fonction regex
Code
Très intelligent. Merci, cette idée m'a beaucoup aidé. Juste pour noter: j'ai encapsulé le tout dans une fonction pour le rendre encore plus propre: combineRegex = (...regex) => new RegExp(regex.map(r => r.source).join(""))Utilisation:combineRegex(/regex1/, /regex2/, ...)
Scindix
25

Utilisation de chaînes dans new RegExp est gênante car vous devez échapper toutes les barres obliques inverses. Vous pouvez écrire des expressions régulières plus petites et les concaténer.

Divisons cette expression régulière

/^foo(.*)\bar$/

Nous utiliserons une fonction pour rendre les choses plus belles plus tard

function multilineRegExp(regs, options) {
    return new RegExp(regs.map(
        function(reg){ return reg.source; }
    ).join(''), options);
}

Et maintenant basculons

var r = multilineRegExp([
     /^foo/,  // we can add comments too
     /(.*)/,
     /\bar$/
]);

Comme cela a un coût, essayez de créer la vraie regex une seule fois, puis utilisez-la.

Riccardo Galli
la source
C'est très cool - non seulement vous n'avez pas à faire d'échappements supplémentaires, mais vous gardez également la coloration syntaxique spéciale pour les sous-expressions régulières!
quezak le
une mise en garde cependant: vous devez vous assurer que vos sous-expressions régulières sont autonomes, ou envelopper chacune dans un nouveau groupe de parenthèses. Exemple: multilineRegExp([/a|b/, /c|d])résultats /a|bc|d/, alors que vous vouliez dire (a|b)(c|d).
quezak
6

Il y a de bonnes réponses ici, mais pour être complet, quelqu'un devrait mentionner la caractéristique principale de Javascript de l'héritage avec la chaîne de prototypes . Quelque chose comme ceci illustre l'idée:

RegExp.prototype.append = function(re) {
  return new RegExp(this.source + re.source, this.flags);
};

let regex = /[a-z]/g
.append(/[A-Z]/)
.append(/[0-9]/);

console.log(regex); //=> /[a-z][A-Z][0-9]/g

James Donohue
la source
C'est la meilleure réponse ici.
parttimeturtle
6

Grâce au monde merveilleux des modèles littéraux, vous pouvez désormais écrire de grandes expressions rationnelles multi-lignes, bien commentées et même imbriquées sémantiquement dans ES6.

//build regexes without worrying about
// - double-backslashing
// - adding whitespace for readability
// - adding in comments
let clean = (piece) => (piece
    .replace(/((^|\n)(?:[^\/\\]|\/[^*\/]|\\.)*?)\s*\/\*(?:[^*]|\*[^\/])*(\*\/|)/g, '$1')
    .replace(/((^|\n)(?:[^\/\\]|\/[^\/]|\\.)*?)\s*\/\/[^\n]*/g, '$1')
    .replace(/\n\s*/g, '')
);
window.regex = ({raw}, ...interpolations) => (
    new RegExp(interpolations.reduce(
        (regex, insert, index) => (regex + insert + clean(raw[index + 1])),
        clean(raw[0])
    ))
);

En utilisant cela, vous pouvez maintenant écrire des expressions régulières comme ceci:

let re = regex`I'm a special regex{3} //with a comment!`;

Les sorties

/I'm a special regex{3}/

Ou qu'en est-il de la multiligne?

'123hello'
    .match(regex`
        //so this is a regex

        //here I am matching some numbers
        (\d+)

        //Oh! See how I didn't need to double backslash that \d?
        ([a-z]{1,3}) /*note to self, this is group #2*/
    `)
    [2]

Sorties hel, soignées!
"Et si j'ai besoin de rechercher une nouvelle ligne?", Eh bien, utilisez \nidiot!
Travailler sur mon Firefox et Chrome.


Ok, "que diriez-vous de quelque chose d'un peu plus complexe?"
Bien sûr, voici un morceau d'un analyseur JS de destruction d'objet sur lequel je travaillais :

regex`^\s*
    (
        //closing the object
        (\})|

        //starting from open or comma you can...
        (?:[,{]\s*)(?:
            //have a rest operator
            (\.\.\.)
            |
            //have a property key
            (
                //a non-negative integer
                \b\d+\b
                |
                //any unencapsulated string of the following
                \b[A-Za-z$_][\w$]*\b
                |
                //a quoted string
                //this is #5!
                ("|')(?:
                    //that contains any non-escape, non-quote character
                    (?!\5|\\).
                    |
                    //or any escape sequence
                    (?:\\.)
                //finished by the quote
                )*\5
            )
            //after a property key, we can go inside
            \s*(:|)
      |
      \s*(?={)
        )
    )
    ((?:
        //after closing we expect either
        // - the parent's comma/close,
        // - or the end of the string
        \s*(?:[,}\]=]|$)
        |
        //after the rest operator we expect the close
        \s*\}
        |
        //after diving into a key we expect that object to open
        \s*[{[:]
        |
        //otherwise we saw only a key, we now expect a comma or close
        \s*[,}{]
    ).*)
$`

Il sort /^\s*((\})|(?:[,{]\s*)(?:(\.\.\.)|(\b\d+\b|\b[A-Za-z$_][\w$]*\b|("|')(?:(?!\5|\\).|(?:\\.))*\5)\s*(:|)|\s*(?={)))((?:\s*(?:[,}\]=]|$)|\s*\}|\s*[{[:]|\s*[,}{]).*)$/

Et l'exécuter avec une petite démo?

let input = '{why, hello, there, "you   huge \\"", 17, {big,smelly}}';
for (
    let parsed;
    parsed = input.match(r);
    input = parsed[parsed.length - 1]
) console.log(parsed[1]);

Sorties avec succès

{why
, hello
, there
, "you   huge \""
, 17
,
{big
,smelly
}
}

Notez la capture réussie de la chaîne entre guillemets.
Je l'ai testé sur Chrome et Firefox, ça marche un régal!

Si vous êtes curieux, vous pouvez consulter ce que je faisais et sa démonstration .
Bien que cela ne fonctionne que sur Chrome, car Firefox ne prend pas en charge les références arrière ou les groupes nommés. Notez donc que l'exemple donné dans cette réponse est en fait une version stérilisée et pourrait facilement être amené à accepter des chaînes invalides.

Hashbrown
la source
1
vous devriez penser à exporter ceci sous forme de package NodeJS, c'est merveilleux
rmobis
1
Bien que je ne l'ai jamais fait moi-même, il existe un tutoriel assez complet ici: zellwk.com/blog/publish-to-npm . Je suggère de vérifier np, à la fin de la page. Je ne l'ai jamais utilisé, mais Sindre Sorhus est un magicien avec ces choses, donc je ne le laisserais pas passer.
rmobis
4

Le regex ci-dessus manque des barres obliques noires qui ne fonctionnent pas correctement. Donc, j'ai édité le regex. Veuillez considérer cette expression régulière qui fonctionne à 99,99% pour la validation des e-mails.

let EMAIL_REGEXP = 
new RegExp (['^(([^<>()[\\]\\\.,;:\\s@\"]+(\\.[^<>()\\[\\]\\\.,;:\\s@\"]+)*)',
                    '|(".+"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.',
                    '[0-9]{1,3}\])|(([a-zA-Z\\-0-9]+\\.)+',
                    '[a-zA-Z]{2,}))$'].join(''));
Anvesh Reddy
la source
1

Pour éviter le tableau join, vous pouvez également utiliser la syntaxe suivante:

var pattern = new RegExp('^(([^<>()[\]\\.,;:\s@\"]+' +
  '(\.[^<>()[\]\\.,;:\s@\"]+)*)|(\".+\"))@' +
  '((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|' +
  '(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$');
andreasonny83
la source
0

Personnellement, j'opterais pour une regex moins compliquée:

/\S+@\S+\.\S+/

Bien sûr, c'est moins précis que votre modèle actuel, mais qu'essayez-vous d'accomplir? Essayez-vous de détecter les erreurs accidentelles que vos utilisateurs pourraient saisir ou craignez-vous que vos utilisateurs essaient de saisir des adresses non valides? Si c'est le premier, j'opterais pour un modèle plus simple. Si c'est le dernier cas, une vérification en répondant à un e-mail envoyé à cette adresse pourrait être une meilleure option.

Cependant, si vous souhaitez utiliser votre modèle actuel, il serait (IMO) plus facile à lire (et à maintenir!) En le construisant à partir de sous-modèles plus petits, comme ceci:

var box1 = "([^<>()[\]\\\\.,;:\s@\"]+(\\.[^<>()[\\]\\\\.,;:\s@\"]+)*)";
var box2 = "(\".+\")";

var host1 = "(\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])";
var host2 = "(([a-zA-Z\-0-9]+\\.)+[a-zA-Z]{2,})";

var regex = new RegExp("^(" + box1 + "|" + box2 + ")@(" + host1 + "|" + host2 + ")$");
Bart Kiers
la source
21
Downvoting - Bien que vos commentaires sur la réduction de la complexité des regex soient valides, OP demande spécifiquement comment "diviser les longues expressions régulières sur plusieurs lignes". Donc, bien que votre avis soit valable, il a été donné pour de mauvaises raisons. par exemple, changer la logique métier pour travailler autour d'un langage de programmation. De plus, l'exemple de code que vous avez donné est assez laid.
sleepycal
4
@sleepycal Je pense que Bart a répondu à la question. Voir la dernière section de sa réponse. Il a répondu à la question et proposé une alternative.
Nidhin David
0

Vous pouvez simplement utiliser une opération de chaîne.

var pattenString = "^(([^<>()[\]\\.,;:\s@\"]+(\.[^<>()[\]\\.,;:\s@\"]+)*)|"+
"(\".+\"))@((\[[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\])|"+
"(([a-zA-Z\-0-9]+\.)+[a-zA-Z]{2,}))$";
var patten = new RegExp(pattenString);
Mubeena
la source
0

J'ai essayé d'améliorer la réponse de korun en encapsulant tout et en implémentant la prise en charge de la division des groupes de capture et des jeux de caractères - rendant cette méthode beaucoup plus polyvalente.

Pour utiliser cet extrait, vous devez appeler la fonction variadic combineRegex dont les arguments sont les objets d'expression régulière que vous devez combiner. Sa mise en œuvre se trouve en bas.

Les groupes de capture ne peuvent pas être divisés directement de cette façon, car cela laisserait certaines parties avec une seule parenthèse. Votre navigateur échouerait avec une exception.

Au lieu de cela, je passe simplement le contenu du groupe de capture dans un tableau. Les parenthèses sont automatiquement ajoutées lorsquecombineRegex rencontre d'un tableau.

De plus, les quantificateurs doivent suivre quelque chose. Si, pour une raison quelconque, l'expression régulière doit être divisée devant un quantificateur, vous devez ajouter une paire de parenthèses. Ceux-ci seront supprimés automatiquement. Le fait est qu'un groupe de capture vide est assez inutile et que de cette façon les quantificateurs ont quelque chose à faire référence. La même méthode peut être utilisée pour des choses comme des groupes non capturants ( /(?:abc)/devient[/()?:abc/] ).

Ceci est mieux expliqué en utilisant un exemple simple:

var regex = /abcd(efghi)+jkl/;

deviendrait:

var regex = combineRegex(
    /ab/,
    /cd/,
    [
        /ef/,
        /ghi/
    ],
    /()+jkl/    // Note the added '()' in front of '+'
);

Si vous devez diviser les jeux de caractères, vous pouvez utiliser des objets ( {"":[regex1, regex2, ...]}) au lieu de tableaux ( [regex1, regex2, ...]). Le contenu de la clé peut être n'importe quoi tant que l'objet ne contient qu'une seule clé. Notez qu'au lieu de cela, ()vous devez utiliser ]comme début fictif si le premier caractère peut être interprété comme un quantificateur. C'est à dire/[+?]/ devient{"":[/]+?/]}

Voici l'extrait de code et un exemple plus complet:

function combineRegexStr(dummy, ...regex)
{
    return regex.map(r => {
        if(Array.isArray(r))
            return "("+combineRegexStr(dummy, ...r).replace(dummy, "")+")";
        else if(Object.getPrototypeOf(r) === Object.getPrototypeOf({}))
            return "["+combineRegexStr(/^\]/, ...(Object.entries(r)[0][1]))+"]";
        else 
            return r.source.replace(dummy, "");
    }).join("");
}
function combineRegex(...regex)
{
    return new RegExp(combineRegexStr(/^\(\)/, ...regex));
}

//Usage:
//Original:
console.log(/abcd(?:ef[+A-Z0-9]gh)+$/.source);
//Same as:
console.log(
  combineRegex(
    /ab/,
    /cd/,
    [
      /()?:ef/,
      {"": [/]+A-Z/, /0-9/]},
      /gh/
    ],
    /()+$/
  ).source
);

Scindix
la source
0

La grande réponse de @ Hashbrown m'a mis sur la bonne voie. Voici ma version, également inspirée de ce blog .

function regexp(...args) {
  function cleanup(string) {
    // remove whitespace, single and multi-line comments
    return string.replace(/\s+|\/\/.*|\/\*[\s\S]*?\*\//g, '');
  }

  function escape(string) {
    // escape regular expression
    return string.replace(/[-.*+?^${}()|[\]\\]/g, '\\$&');
  }

  function create(flags, strings, ...values) {
    let pattern = '';
    for (let i = 0; i < values.length; ++i) {
      pattern += cleanup(strings.raw[i]);  // strings are cleaned up
      pattern += escape(values[i]);        // values are escaped
    }
    pattern += cleanup(strings.raw[values.length]);
    return RegExp(pattern, flags);
  }

  if (Array.isArray(args[0])) {
    // used as a template tag (no flags)
    return create('', ...args);
  }

  // used as a function (with flags)
  return create.bind(void 0, args[0]);
}

Utilisez-le comme ceci:

regexp('i')`
  //so this is a regex

  //here I am matching some numbers
  (\d+)

  //Oh! See how I didn't need to double backslash that \d?
  ([a-z]{1,3}) /*note to self, this is group #2*/
`

Pour créer cet RegExpobjet:

/(\d+)([a-z]{1,3})/i
Nuno Cruces
la source