Créer une Expression régulière (REGEX) de détection des jurisprudences dans un texte

Sommaire

    Pour mon application REGLEX d’extraction de jurisprudence, j’avais dans un premier temps commencé à travailler sur un projet écrit en Python et utilisant la librairie spaCy de traitement automatique des langues (abrégé en TAL ou NLP en anglais). Toutefois, pour être efficace, la création d’un modèle spécifique de traitement NLP est très long et consommateur en ressources informatiques. C’est pourquoi, je me suis rabattu dans un premier temps sur les Regex (mais je n’ai pas encore dit mon dernier mot sur spaCy). C’était également un exercice intéressant pour déterminer s’il était possible de créer cette application avec des ressources plus limitées.

    Avant d’entrer dans le vif du sujet de la création d’une Regex pour identifier des décisions de justice présentes dans un texte, commençons par une petite introduction sur les expressions régulières pour celles et ceux qui ne connaitraient pas cet outil.

    Image par Garabato Kid

    Les expressions régulières (Regex)

    Les « expressions régulières » (abrégées en Regex) sont un outil mathématique développé après la Seconde guerre mondiale mais toujours très performant, même à l’heure des processus dits d’intelligence artificielle. C’est une forme de syntaxe qui permet de décrire des ensembles de caractères possibles (des motifs). En PCRE2 (l’un des standards d’écriture de Regex), l’expression \d{1,3} permet par exemple de décrire une suite de 1 à 3 chiffres (le \d correspondant à n’importe quel chiffre de 0 à 9). De même, un numéro de téléphone au format français peut être décrit de la manière suivante :

    /\b(?:\d{2}[-.\s]?){5}\b/

    • Les slashs / au début et à la fin indiquent le début et la fin d’une expression régulière. D’autres caractères sont également utilisés pour marquer l’existence d’une expression régulière, par exemple le hashtag « # » ;
    • Le {5} indique que le motif entre parenthèses qui le précède (?:\d{2}[-.\s]?) doit être répété 5 fois ;
    • Les parenthèses complétées par un point d’interrogation et deux points (?: ) renvoient à ce que l’on appelle un « groupement non capturant », c’est-à-dire qu’il identifie un motif inclus entre les parenthèses sur lequel on souhaite appliquer une action (ici la répétition par 5), sans toutefois vouloir extraire les données pour une utilisation ultérieure. Tout l’intérêt des regex est en effet de pouvoir extraire certaines données pour les utiliser ensuite, et dans ce cadre-là on utilisera alors des parenthèses simples ( ), dite « groupement capturant » ;
    • Le \d{2} décrit une suite de deux chiffres exactement ;
    • Les crochets [ ] décrivent une alternative, c’est-à-dire la présence de l’un des caractères compris entre les crochets, ici un tiret , un point . ou un espace \s ;
    • Le point d’interrogation ? qui suit les crochets indique que le caractère alternatif peut être trouvé entre 0 et 1 fois pour valider l’expression ;
    • Les \b correspondent à une frontière de mot, c’est-à-dire que notre suite de caractères ne peut ici être précédée (\b au début) ou suivie (\b à la fin) par un caractère alphanumérique. Pour valider l’expression, on ne doit pas trouver une lettre avant (ou après).

    Ainsi cette expression permet d’identifier les numéros aux formats suivants :

    • 0231000000
    • 02 31 00 00 00
    • 02.31.00.00.00
    • 02-31-00-00-00

    En revanche, elle ne détectera pas une suite de chiffres inférieure ou supérieur à 10 chiffres, ou un autre caractère de séparation entre les nombres du numéro de téléphone (par exemple « : »). Vous pouvez la tester à cette adresse : https://regex101.com/r/To68sh/1

    Si vous souhaitez en savoir plus sur les Regex, différents cours et tutoriels sont facilement accessibles sur internet (par exemple sur les sites Expreg, RIP Tutorial ou Zeste de Savoir).

    Une Regex pour les dominer toutes (les décisions de justice) ?

    Avant de se lancer dans l’écriture d’une regex il est important de déterminer notre objectif et ce que l’on cherche à trouver, en incluant toutes les variations dans le motif de cet objet de nos recherches.

    Objectif et approche de la regex

    L’objectif de l’application REGLEX est d’identifier dans un texte les décisions de justice qui sont présentes pour ensuite pouvoir renvoyer vers le lien de la décision (un lien trouvé via l’utilisation des API des cours de justice).

    Une première approche possible serait donc de trouver dans le texte tous les numéros de requête, de pourvoi, de registre, de rôle (pour simplifier, je parlerais ensuite de numéro d’affaire), identifiant par là-même des décisions de justice. A priori la plus simple, cette approche présente toutefois trois problèmes pour l’application que je souhaitais développer :

    1. Certaines juridictions différentes ont des identifiants de décision similaires. Or pour pouvoir interroger la bonne base de données pour récupérer les liens, il faut pouvoir les différencier de manière efficace ;
    2. Les textes analysés présentent parfois des erreurs sur les numéros, et il serait bon de pouvoir valider le résultat à partir, par exemple, de la date de la décision ;
    3. Je veux pouvoir afficher à l’internaute les décisions de justice pour lesquelles l’application n’a pas trouvé de lien vers le texte. N’afficher alors que le numéro d’affaire n’est à mon sens pas très « user friendly ».

    J’ai donc opté pour une seconde approche, un peu plus complexe (voire beaucoup plus complexe) qui est celle de trouver avec une regex l’ensemble des éléments de la décision (a minima : juridiction, date, numéro d’affaire). On verra en fin de post qu’elle présente toutefois des limites, et qu’une troisième approche est peut-être plus pertinente.

    Les objets à identifier

    Si l’on se réfère au REFLEX, le guide de rédaction des références juridiques du Syndicat national de l’édition, les décisions et avis rendus par les juridictions françaises devraient être référencées de la manière suivante :

    Nom de la juridiction abrégé | ville [le cas échéant], formation [chambre, section, pôle…], nature de la décision [avis, ordonnance, arrêt, QPC…]date [jour | mois abrégé | année], n° discriminant [n° X], codes de publication, nom des parties, ECLI

    Sont ensuite présentés plusieurs exemples :

    • Cons. const., 8 août 1985, n° 85-196 DC
    • Cass. crim., QPC, 9 nov. 2013, n° 13-84.909
    • CA Paris, 4-1, 11 mars 2016, n° 13/167377
    • CE, ass., 17 juin 2015, n° 384826, Société La Chaîne Info (LCI)

    Est ensuite précisé que

    il est admis d’utiliser des abréviations plus courtes pour les chambres de la Cour de cassation et, ainsi, de ne pas faire figurer l’abréviation « Cass. » en début de référence. Par exemple, la première chambre civile de la Cour de cassation pourra être abrégée « Civ. 1re » au lieu de « Cass.  1re civ. ».

    Si toute personne suivait à la lettre ce référentiel, le travail de création d’une regex en serait grandement facilité. En effet, plus le mode de citation est normé, moins la regex est complexe à créer, et plus le taux d’identification est proche de 100%. Toutefois, la réalité est bien différente, comprenant presque autant de variations dans le référencement que de personnes qui citent des décisions (je n’exagère qu’à moitié).

    Intéressons nous ainsi au premier objet à identifier qu’est la juridiction. Rien que pour les juridictions françaises les plus communes, il faut pouvoir identifier les éléments suivants, et leur variations :

    • Cour de cassation, Cass (avec ou sans point), Ccass, 1e civ, Crim, Soc…
    • Cour d’appel de Paris (Lyon, Nantes…), CA Paris, C.A. Paris, CA de Paris…
    • Tribunal judiciaire de Nanterre, T.J. Nanterre, Trib. Jud. Nanterre…
    • Tribunal de grande instance de Caen, TGI Caen, T.G.I. de Caen…
    • Conseil d’Etat, CE, C.E., C E, Cons. Etat
    • Cour administrative d’appel de Lille, CAA de Lille, C.A.A. Lille…
    • Tribunal administratif de Nice, TA Nice, T A de Nice…
    • Conseil constitutionnel, Cons. constit., CC, C.constit…

    Et il faut pouvoir les identifier de manière discriminante, c’est-à-dire en évitant au maximum les faux-amis que pourraient être par exemple « ce » dans la phrase « ce sont les chats les coupables » et qui ne renvoie pas du tout au Conseil d’Etat (mais que l’on peut tout-à-fait trouver dans des conclusions ou un cours… non ?). De plus, il ne serait pas efficace de faire une regex qui utiliserait de manière alternative tous les noms possibles. Au-delà du fait que l’on pourrait oublier une variation, cela entraînerait beaucoup d’étapes dans l’analyse du texte, voire une erreur.

    Après plusieurs heures de réflexion et de tests, nous obtenons la regex suivante (que nous allons utiliser avec le drapeau regex « i », c’est-à-dire une regex non sensible à la distinction majuscule/minuscule) :

    (?<juri>(?<![éèêëàäâöùüû])\b(?:(?:c(?=ed|ons|om|ou|e\b(?!\s[ds])|[achijr.\s]))|(?:t(?=rib|[agip.\s]))|(?:s(?=oc)))(?>\s?[-a-zéè.’’]+){1,6})

    Décodons donc tout cela ensemble.

    • (?<juri>…) : cette expression est un « groupe capturant nommé ». Un groupe capturant est normalement symbolisé par des parenthèses seules (…). Il sert à extraire les données qui seront capturée par la partie de la regex qui figure entre les parenthèses. Par exemple la regex (/d) appliquée sur la phrases « Une table pour 2 personnes » renverra au programme une variable avec la valeur 2. Pour faciliter l’utilisation de ces captures, on peut « nommer » le groupe. La variable renvoyée aura alors le nom du groupe, et sera donc plus facile à manipuler. C’est à cela que sert l’ajout de ?<juri> après la première parenthèse. La variable renvoyée au programme aura donc le nom « juri » (pour juridiction).
    • (?<![éèêëàäâöùüû])\b : cet élément comprend deux motifs distincts, mais qui doivent être expliqués ensemble :
      • Le \b correspond comme nous l’avons déjà vu à une frontière de mot, c’est-à-dire que pour valider l’expression qu’il précède (ou qu’il suit), cette expression ne doit pas être précédée (ou suivie) par une lettre. En ce sens, la regex \bmets identifiera le mot « mets » dans la phrase « je vous en mets combien », mais pas dans celle « je vous en remets combien », puisque dans cette dernière l’ensemble de caractère mets est précédé par la lettre e. Problème : les regex ont été créée à partir des travaux de Stephen Cole Kleene, un mathématicien américain, qui n’utilisait donc pas un alphabet avec accents (ou autres caractères spéciaux)… En ce sens, la regex \bconseille identifiera le terme « conseille » tant dans la phrase « je vous le conseille », que dans « je vous le déconseille ». Le é n’est pas considéré comme une lettre, c’est donc une frontière de mot pour l’ordinateur. Pour éviter cela, il faut donc ajouter avant le \b un autre élément.
      • (?<!…) est ce que l’on appelle une « Assertion arrière négative » (Negative Lookbehind en anglais), c’est-à-dire que pour valider l’expression qui la suit, on ne doit pas trouver avant cette expression ce qui est contenu entre les parenthèse (après le ?<!). En fait, les parenthèses ( ) indiquent un groupement, le point d’interrogation ? nous renseigne que c’est un groupement avec une fonction spécifique, le signe inférieur < nous dit qu’il faut regarder en arrière, et le point d’exclamation ! nous indique une négation (ce signe de ponctuation est en effet utilisé pour manifester la négation dans beaucoup de langages de programmation).
      • [éèêëàäâöùüû] correspond à une alternative (manifestée par les crochets [ ]) pour laquelle doit être présente l’un des caractère qu’elle comprend. Associée à l’assertion arrière négative, cela nous indique donc que pour identifier le motif qui suivra les parenthèses, il ne doit pas être précédé par un caractère accentué. Dès lors, la regex (?<![éèêëàäâöùüû])\bconseille n’identifiera plus le terme « conseille » dans la phrase « je vous le déconseille ».
    • (?:(?:c(?=ed|ons|om|ou|e\b(?!\s[ds])|[achijpr.\s]))|(?:t(?=rib|[agip.\s]))|(?:s(?=oc))) : ce long morceau pourrait faire peur, mais il n’est en fait pas très compliqué.
      • (?: ) : comme nous l’avons vu, ces caractères renvoient à une groupement non capturant. Au sein de ce groupement, nous avons trois motifs qui doivent être distingués, et qui sont séparés par une barre verticale |, qui veut dire « ou » et chacun enfermés dans un groupement non capturant (la regex va donc identifier une expression qui correspond à l’un des trois motifs).
      • c(?=ed|ons|om|ou|e\b(?!\s[ds])|[achijr.\s]) : ce premier motif est celui qui permet d’identifier toutes les juridictions dont le nom commence par un « c ». Ensuite le groupement (?= ) est une « assertion avant positive », c’est-à-dire que la regex n’identifiera les « c » que s’ils sont suivis par les éléments présents entre les parenthèses. Cet élément est important pour éviter que la regex n’extrait tout est n’importe quoi dès lors que cela commence par un « c ». Dans cette parenthèse, nous pouvons voir 6 possibilités, toutes séparées par une barre verticale (le caractère « ou »).
        • Les quatre premières, ed|ons|om|ou, sont là pour permettre l’identification de « cedh », « cons« , « conseil », « comm » et « cour » (incluant toutes les variations avec ou sans majuscule, puisque nous utilisons le drapeau « i » indiquant une expression non sensible à la casse). Je les ai séparé du reste car le « o » et le « e » sont les voyelles les plus utilisées avec le « c ». Ajouter une deuxième (voire une troisième) lettre ensuite permet ainsi de réduire les faux amis (la regex n’identifiera donc pas des termes comme « cet », « contre » ou « certificat ») ;
        • La suivante e\b(?!\s[ds]) vise l’abréviation « ce » (ou CE, Ce, cE) de Conseil d’Etat. Comme déjà vu, le \b après le e indique une frontière de mot : la regex n’identifiera donc pas « ce » dans le mot « certains », mais seulement les « ce » non suivis par une lettre. Ensuite, nous avons une « assertion avant négative » (?! ) indiquant que le « ce » pour être identifié ne doit pas non plus être suivi par un espace, le \s, et la lettre d ou la lettre s (on se souvient que les crochets [ ] représentent une alternative). Le « d » et le « s » sont en effet les deux lettres qui suivent généralement l’utilisation de « ce » comme pronom démonstratif (par exemple dans : « ce sont », « ce dernier »…). Nous évitons alors la détection de certains faux-amis.
        • La dernière [achijr.\s] représente une alternative indiquant toutes les autres lettres qui peuvent suivre le « c » pour que la regex soit contente. Elle vise ainsi à détecter « cass », « ca« , « caa », « cc« , « ch« , « chambre », « civ », « cjce », « credh », « crim », et tous les « c » suivis d’un point ou d’un espace (\s), afin de trouver les abréviation du type « c j c e » ou « c.a.a. ». On pourrait utiliser un espace dans une regex (par exemple [achijr. ] au lieu de [achijr.\s]), mais cet espace n’est pas toujours facile à identifier à la lecture, c’est pour cela que je préfère utiliser le \s.
      • t(?=rib|[agip.\s]) : ce deuxième motif est assez simple à comprendre maintenant que l’on a explicité le premier. Il vise donc à identifier tous les « t » obligatoirement suivis par « rib », « a », « g », « i », « p », un point ou un espace. Le premier renvoie à « trib » et « tribunal », les autres aux abréviations « ta », « tgi », « ti », « tp », « t.g.i » ou encore « t p ».
      • s(?=oc) : ce troisième motif ne vise qu’une seule juridiction, la chambre sociale de la Cour de cassation, lorsqu’abrégée en « soc » ou « sociale ».
    • (?>\s?[-a-zéè.’’]+){1,6} : ce dernier élément de la regex est celui qui permet d’extraire la suite du nom de la juridiction. En effet, pour le moment, nous n’avons identifié que les « c », « t », et « s » que nous souhaitons voir extraire. Les « assertion avant positive » ne sont là que pour dire parmi ces lettres, pour lesquelles il faut regarder la suite.
      • (?> ) : le point d’interrogation suivi du signe plus grand que indique que le groupement (les parenthèses) est un groupement non capturant « atomique ». En langage regex, cela veut dire qu’il empêche l’ordinateur de remonter avant ce groupe pour trouver des alternatives. Il faut comprendre qu’une regex « lit » les textes de gauche à droite, un caractère à la fois. Tant que le caractère entre dans le motif de la regex, elle avance d’un cran. A chaque fois que le caractère ne rentre pas dans le motif, elle va revenir en arrière d’un ou plusieurs crans, pour vérifier s’il n’existe pas une alternative. Utiliser un groupement atomique permet de réduire le nombre de « pas » que fait une regex et donc de réduire l’impact sur les performances.
      • \s? renvoie à la recherche d’un espace suivi du quantificateur ?, qui veut dire qu’on peut le trouver entre 0 et 1 fois (c’est-à-dire qu’il peut ne pas y avoir d’espace).
      • [-a-zéè.’’]+ : ici c’est une alternative pour trouver le caractère « -« , toutes les lettres de a à z (intervalle a-z), le « e » accentué aigu ou grave, un point ou une apostrophe (dans les deux formes que l’on trouve en informatique). Le + indique que ce caractère peut être trouvé entre 1 fois et l’infini. La regex passe au motif suivant dès qu’un caractère non présent dans cette alternative est trouvé.
      • {1,6} : ce motif est un quantificateur qui indique que tout ce qui est entre les parenthèse qui le précède peut être trouvé de 1 à 6 fois. Ce nombre permet de trouver toutes les juridictions, même celles avec un nom un peu long.

    Ensuite il faut faire le même travail pour la date et le numéro d’affaire, ce qui donne :

    (?<date>\b(?:\d{1,2}[er]{0,2}\s[jfmasond][a-zéû.]+\s\d{2,4})|(?:\d{1,2}[-\/]\d{1,2}[-\/]\d{2,4})\b)

    (?<affaire>\b(?:c-|t-)?\d+[-.a-z0-9\/]*\d\b)

    Je vous fais grâce du détail de ces deux expressions, ce post étant déjà très long, et n’étant toutefois pas encore fini. Mais n’hésitez pas à utiliser l’outil https://regex101.com/ qui présente dans l’encart en haut à droite une explication des regex que vous entrez.

    Combiner les objets regex

    Dans le meilleur des mondes, où toutes les personnes intéressées citent leur références selon la norme en vigueur, il suffirait de juxtaposer les différentes regex ici présentées en une seule pour obtenir la regex ultime d’identification des décisions de justice. Ce monde n’existe pas plus qu’existe une manière unique d’écrire ses références. Un même arrêt du Conseil d’Etat pourra par exemple être cité comme :

    • Conseil d’État, Section du Contentieux, 22/02/2007, 264541, APREI
    • CE, APREI, 22 fév. 2007, req. 264541
    • Cons. Etat, Sect., 22 février 2007, Association du personnel relevant des établissements pour inadaptés (A.P.R.E.I.), n° 264541

    Pour un humain, toutes ces références sont équivalentes et facilement identifiables, mais, en l’état, notre regex « ultime » ne trouverait aucune de ces références. Il faut donc ajouter d’autres motifs pour prendre en compte les variations possibles (et notamment, les ajouts potentiels du nom de l’affaire à différents endroits, de la chambre de la juridiction ou encore de différentes lettres avant le numéro d’affaire). Ce qui nous donne au final la regex suivante (pour le moment) :

    (?<juri>(?<![éèêëàäâöùüû])\b(?:(?:c(?=ed|ons|om|ou|e\b(?!\s[ds])|[achijr.\s]))|(?:t(?=rib|[agip.\s]))|(?:s(?=oc)))(?>\s?[-a-zéè.’’]+){1,6})(?:[\(\[][0-9a-zêéàâè]+[\)\]])?,?(?:(?:\s[0-9a-z êéàâèùûôöëäç.]+)?,)?\s?(?<date>(?:\d{1,2}[er]{0,2}\s[jfmasond][a-zéû.]+\s\d{2,4})|(?:\d{1,2}[-\/]\d{1,2}[-\/]\d{2,4})),?\s(?:[-a-zÀ-ÖÙ-öù-ÿĀ-žḀ-ỿ\/. ‘’]{3,100}[, ]?){0,1}\s?[a-zé.° ,]{0,14}(?<affaire>(?:c-|t-)?\d+[-.a-z0-9\/]*\d)

    Limites et nouvelles perspectives

    Si l’idée d’une regex unique pour trouver toutes les jurisprudences semblait être au départ une bonne idée (ma précieuse), cette option se révèle au fur et à mesure des erreurs et exceptions de plus en plus complexe, voire inefficace, voire inutilement consommatrice de ressources. En effet, plus une regex est longue et complexe, plus l’ordinateur va mettre de temps à analyser le texte en cause (et donc consommer des ressources).

    Une nouvelle approche que je souhaite mettre en œuvre pour la prochaine version de REGLEX est de revenir à une regex par objet à identifier (juridiction, date et numéro d’affaire) et d’utiliser leurs positionnements respectifs dans le texte pour calculer des probabilités de lien entre eux. Ainsi, si l’ordinateur identifie une juridiction, suivie d’une date, suivie d’un numéro d’affaire, avec entre ces objets un nombre limité de caractères, il pourra inférer un lien entre ces trois éléments et les considérer comme une référence unique à une décision de justice. D’autres options d’ordre seront possibles, et cela permettra même parfois d’identifier des décisions où le nom de la juridiction est omis (pour éviter une répétition), voire celles où le numéro d’affaire a été malencontreusement oublié…

    Cette approche me semble beaucoup plus simple et efficace… et je me demande pourquoi je n’y ai pas pensé plus tôt… La suite au prochain épisode donc…

    * * *

    Voilà, on en finit avec ce cheminement dans le monde des regex, tout aussi merveilleux que celui des API. J’espère que la lecture de ce post vous aura intéressé, et peut-être donné envie (ou complètement démotivé) de vous lancer dans la découverte de cet outil.

    Tous les éléments présentés dans cette page sont sous licence Creative Commons BY-NC 4.0.

    Commentaires

    Habett

    Très bon article. Merci.
    Juste à préciser que chaque language a un peu sa conception des regex, avec des variations de syntaxe, et des facilités d’usage différentes. Il faut parfois passer par des compilateurs de regex dans des modules qui ne sont intégrés/importés de base. Moi, j’utilise du perl et le support des regex y est magnifique.

    Réponse

    Laisser un commentaire

    Votre adresse e-mail ne sera pas publiée. Les champs obligatoires sont indiqués avec *

    Ce site utilise Akismet pour réduire les indésirables. En savoir plus sur comment les données de vos commentaires sont utilisées.