web-dev-qa-db-fra.com

Des expressions régulières lisibles sans perdre leur puissance?

De nombreux programmeurs connaissent la joie de créer une expression régulière rapide, ces jours-ci souvent avec l'aide d'un service Web, ou plus traditionnellement à l'invite interactive, ou peut-être en écrivant un petit script qui a l'expression régulière en cours de développement, et une collection de cas de test . Dans les deux cas, le processus est itératif et assez rapide: continuez de pirater la chaîne d'apparence cryptique jusqu'à ce qu'elle corresponde et capture ce que vous voulez et rejettera ce que vous ne voulez pas.

Pour un cas simple, le résultat pourrait être quelque chose comme ceci, comme un Java regexp:

Pattern re = Pattern.compile(
  "^\\s*(?:(?:([\\d]+)\\s*:\\s*)?(?:([\\d]+)\\s*:\\s*))?([\\d]+)(?:\\s*[.,]\\s*([0-9]+))?\\s*$"
);

De nombreux programmeurs connaissent également la peine de devoir modifier une expression régulière, ou simplement coder autour d'une expression régulière dans une base de code héritée. Avec un peu d'édition pour le diviser, l'expression rationnelle ci-dessus est toujours très facile à comprendre pour toute personne raisonnablement familière avec les expressions rationnelles, et un vétéran de l'expression rationnelle devrait voir tout de suite ce qu'il fait (réponse à la fin du message, au cas où quelqu'un voudrait faire l'exercice). de le découvrir eux-mêmes).

Cependant, les choses n'ont pas besoin d'être beaucoup plus complexes pour qu'une expression rationnelle devienne vraiment une chose en écriture seule, et même avec une documentation diligente (que tout le monde bien sûr le fait pour tous les regexps complexes qu'ils écrivent ...), la modification des regexps devient une tâche intimidante. Cela peut aussi être une tâche très dangereuse, si l'expression rationnelle n'est pas testée avec soin (mais tout le monde bien sûr a des tests unitaires complets pour toutes leurs expressions rationnelles complexes, les deux positif et négatif...).

Donc, pour faire court, existe-t-il une solution/alternative d'écriture-lecture pour les expressions régulières sans perdre leur puissance? À quoi ressemblerait l'expression rationnelle ci-dessus avec une approche alternative? N'importe quelle langue convient, même si une solution multilingue serait préférable, dans la mesure où les expressions rationnelles sont multilingues.


Et puis, ce que fait la regexp précédente est la suivante: analyser une chaîne de nombres au format 1:2:3.4, capturant chaque numéro, où les espaces sont autorisés et uniquement 3 est requis.

79
hyde

Un certain nombre de personnes ont mentionné la composition à partir de parties plus petites, mais personne n'a encore fourni d'exemple, alors voici le mien:

string number = "(\\d+)";
string unit = "(?:" + number + "\\s*:\\s*)";
string optionalDecimal = "(?:\\s*[.,]\\s*" + number + ")?";

Pattern re = Pattern.compile(
  "^\\s*(?:" + unit + "?" + unit + ")?" + number + optionalDecimal + "\\s*$"
);

Pas le plus lisible, mais j'ai l'impression que c'est plus clair que l'original.

De plus, C # a l'opérateur @ Qui peut être ajouté à une chaîne afin d'indiquer qu'il doit être pris littéralement (pas de caractères d'échappement), donc number serait @"([\d]+)";

81
Bobson

La clé pour documenter l'expression régulière est de la documenter. Trop souvent, les gens jettent ce qui semble être du bruit de ligne et en restent là.

Dans Perl l'opérateur /x À la fin de l'expression régulière supprime les espaces permettant de documenter l'expression régulière.

L'expression régulière ci-dessus deviendrait alors:

$re = qr/
  ^\s*
  (?:
    (?:       
      ([\d]+)\s*:\s*
    )?
    (?:
      ([\d]+)\s*:\s*
    )
  )?
  ([\d]+)
  (?:
    \s*[.,]\s*([\d]+)
  )?
  \s*$
/x;

Oui, il consomme un peu d'espace blanc vertical, bien que l'on puisse le raccourcir sans sacrifier trop de lisibilité.

Et puis, ce que fait l'expression rationnelle précédente, c'est ceci: analyser une chaîne de nombres au format 1: 2: 3.4, capturant chaque nombre, où les espaces sont autorisés et seulement 3 sont requis.

En regardant cette expression régulière, on peut voir comment cela fonctionne (et ne fonctionne pas). Dans ce cas, cette expression régulière correspondra à la chaîne 1.

Des approches similaires peuvent être adoptées dans une autre langue. L'option python re.VERBOSE fonctionne là-bas.

Perl6 (l'exemple ci-dessus était pour Perl5) va plus loin avec le concept de règles qui conduit à des structures encore plus puissantes que le PCRE (il donne accès à d'autres grammaires (sans contexte et sensibles au contexte) que juste régulières et étendues régulières).

Dans Java (d'où cet exemple puise), on peut utiliser la concaténation de chaînes pour former l'expression régulière.

Pattern re = Pattern.compile(
  "^\\s*"+
  "(?:"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #1
    ")?"+
    "(?:"+
      "([\\d]+)\\s*:\\s*"+  // Capture group #2
    ")"+
  ")?"+ // First groups match 0 or 1 times
  "([\\d]+)"+ // Capture group #3
  "(?:\\s*[.,]\\s*([0-9]+))?"+ // Capture group #4 (0 or 1 times)
  "\\s*$"
);

Certes, cela crée beaucoup plus de " Dans la chaîne, ce qui peut entraîner une certaine confusion, peut être plus facilement lu (en particulier avec la coloration syntaxique sur la plupart des IDE) et documenté.

La clé est de reconnaître le pouvoir et la nature "d'écrire une fois" dans lesquels les expressions régulières tombent souvent. L'écriture du code pour éviter cela défensivement afin que l'expression régulière reste claire et compréhensible est la clé. Nous formaterons Java code pour plus de clarté - les expressions régulières ne sont pas différentes lorsque le langage vous donne la possibilité de le faire.

43
user40980

Le mode "verbeux" proposé par certaines langues et bibliothèques est l'une des réponses à ces préoccupations. Dans ce mode, les espaces dans la chaîne d'expression régulière sont supprimés (vous devez donc utiliser \s) et des commentaires sont possibles. Voici un court exemple dans Python qui prend en charge cela par défaut:

email_regex = re.compile(r"""
    ([\w\.\+]+) # username (captured)
    @
    \w+         # minimal viable domain part
    (?:\.w+)    # rest of the domain, after first dot
""", re.VERBOSE)

Dans toutes les langues qui ne le font pas, l'implémentation d'un traducteur du mode verbeux au mode "normal" devrait être une tâche simple. Si vous êtes préoccupé par la lisibilité de vos expressions régulières, vous justifierez probablement cet investissement en temps assez facilement.

27
Xion

Chaque langue qui utilise des expressions rationnelles vous permet de les composer à partir de blocs plus simples pour faciliter la lecture, et avec quelque chose de plus compliqué (ou aussi compliqué que) votre exemple, vous devriez certainement profiter de cette option. Le problème particulier avec Java et beaucoup d'autres langages est qu'ils ne traitent pas les expressions régulières comme des citoyens de "première classe", les obligeant à se faufiler dans le langage via des littéraux de chaîne. Cela signifie que beaucoup les guillemets et les barres obliques inverses qui ne font pas réellement partie de la syntaxe des expressions rationnelles et rendent les choses difficiles à lire, et cela signifie également que vous ne pouvez pas être beaucoup plus lisible que cela sans définir efficacement votre propre mini-langage et interprète.

Le meilleur moyen prototypique d'intégrer des expressions régulières était bien sûr Perl, avec son option d'espaces blancs et ses opérateurs de cotes regex. Perl 6 étend le concept de construction de regex à partir de parties aux grammaires récursives réelles, ce qui est tellement mieux à utiliser que ce n'est vraiment aucune comparaison. La langue a peut-être manqué le bateau de l'actualité, mais son soutien regex était The Good Stuff (tm).

16
Kilian Foth

J'aime utiliser Expresso: http://www.ultrapico.com/Expresso.htm

Cette application gratuite présente les fonctionnalités suivantes que je trouve utiles au fil du temps:

  • Vous pouvez simplement copier et coller votre regex et l'application le analysera pour vous
  • Une fois votre regex écrit, vous pouvez le tester directement depuis l'application (l'application vous donnera la liste des captures, remplacements ...)
  • Une fois que vous l'avez testé, il générera le code C # pour l'implémenter (notez que le code contiendra les explications sur votre regex).

Par exemple, avec l'expression régulière que vous venez de soumettre, cela ressemblerait à: Sample screen with the initially given regex

Bien sûr, l'essayer vaut mille mots pour le décrire. Veuillez également noter que je suis lié de quelque façon avec l'éditeur de cette application.

12
E. Jaep

Pour certaines choses, il peut être utile d'utiliser simplement une grammaire comme BNF. Celles-ci peuvent être beaucoup plus faciles à lire que les expressions régulières. Un outil tel que GoldParser Builder peut ensuite convertir la grammaire en un analyseur qui fait le gros du travail pour vous.

Les grammaires BNF, EBNF, etc. peuvent être beaucoup plus faciles à lire et à créer qu'une expression régulière compliquée. L'OR est un outil pour de telles choses.

Le lien wiki c2 ci-dessous a une liste d'alternatives possibles qui peuvent être googlé, avec une discussion à leur sujet incluse. Il s'agit essentiellement d'un lien "voir aussi" pour compléter ma recommandation de moteur de grammaire:

Alternatives aux expressions régulières

En prenant "alternative" pour signifier "facilité sémantiquement équivalente avec une syntaxe différente", il existe au moins ces alternatives à/avec RegularExpressions:

  • Expressions régulières de base
  • Expressions régulières "étendues"
  • Expressions régulières compatibles Perl
  • ... et bien d'autres variantes ...
  • Syntaxe RE de style SNOBOL (SnobolLanguage, IconLanguage)
  • Syntaxe SRE (RE comme EssExpressions)
  • différentes syntaxes FSM
  • Grammaires d'intersection à états finis (assez expressives)
  • ParsingExpressionGrammars, comme dans OMetaLanguage et LuaLanguage ( http://www.inf.puc-rio.br/~roberto/lpeg/lpeg.html )
  • Le mode d'analyse de RebolLanguage
  • ProbabilityBasedParsing ...
9
Nick P

C'est une vieille question et je n'ai vu aucune mention de Expressions verbales alors j'ai pensé que j'ajouterais cette information ici aussi pour les futurs chercheurs. Les expressions verbales ont été spécifiquement conçues pour rendre les regex humains compréhensibles, sans avoir besoin d'apprendre la signification des symboles des regex. Voir l'exemple suivant. Je pense que cela fait mieux ce que vous demandez.

// Create an example of how to test for correctly formed URLs
var tester = VerEx()
    .startOfLine()
    .then('http')
    .maybe('s')
    .then('://')
    .maybe('www.')
    .anythingBut(' ')
    .endOfLine();

// Create an example URL
var testMe = 'https://www.google.com';

// Use RegExp object's native test() function
if (tester.test(testMe)) {
    alert('We have a correct URL '); // This output will fire}
} else {
    alert('The URL is incorrect');
}

console.log(tester); // Outputs the actual expression used: /^(http)(s)?(\:\/\/)(www\.)?([^\ ]*)$/

Cet exemple est pour javascript, vous pouvez trouver cette bibliothèque maintenant pour de nombreux les langages de programmation.

6
Parivar Saraff

Le moyen le plus simple serait de toujours utiliser l'expression régulière mais de construire votre expression à partir de la composition d'expressions plus simples avec des noms descriptifs, par exemple http://www.martinfowler.com/bliki/ComposedRegex.html (et oui, cela provient de la chaîne concat)

cependant, vous pouvez également utiliser une bibliothèque de combinateurs d'analyseurs, par exemple http://jparsec.codehaus.org/ qui vous donnera un analyseur décent récursif complet. là encore, le véritable pouvoir vient de la composition (cette fois la composition fonctionnelle).

3
jk.

J'ai pensé qu'il valait la peine de mentionner les expressions grok de logstash. Grok s'appuie sur l'idée de composer des expressions d'analyse longues à partir d'expressions plus courtes. Il permet de tester facilement ces blocs de construction et est livré préemballé avec plus de 100 modèles couramment utilisés . Outre ces modèles, il permet d'utiliser toutes les syntaxes des expressions régulières.

Le modèle ci-dessus exprimé dans grok est (j'ai testé dans application de débogage mais aurait pu faire une erreur):

"(( *%{NUMBER:a} *:)? *%{NUMBER:b} *:)? *%{NUMBER:c} *(. *%{NUMBER:d} *)?"

Les pièces et les espaces optionnels le rendent un peu plus laid que d'habitude, mais ici et dans d'autres cas, l'utilisation de grok peut rendre la vie beaucoup plus agréable.

3
yoniLavi

En F #, vous avez le module FsVerbalExpressions . Il vous permet de composer des expressions régulières à partir d'expressions verbales, il a également des expressions régulières pré-construites (comme l'URL).

L'un des exemples de cette syntaxe est le suivant:

let groupName =  "GroupNumber"

VerbEx()
|> add "COD"
|> beginCaptureNamed groupName
|> any "0-9"
|> repeatPrevious 3
|> endCapture
|> then' "END"
|> capture "COD123END" groupName
|> printfn "%s"

// 123

Si vous n'êtes pas familier avec la syntaxe F #, groupName est la chaîne "GroupNumber".

Ensuite, ils créent une expression verbale (VerbEx) qu'ils construisent comme "COD (? <GroupNumber> [0-9] {3}) END". Qu'ils testent ensuite sur la chaîne "COD123END", où ils obtiennent le groupe de capture nommé "GroupNumber". Il en résulte 123.

Honnêtement, je trouve le regex normal beaucoup plus facile à comprendre.

2
CodeMonkey