Cours n° 16, 21 février 2013

Jean-François Perrot

Expressions régulières en Perl : Compléments

  1. Variables locales supplémentaires

  2. Look-ahead et look-behind
    1. Idée générale
    2. (?=expression) look-ahead positif
    3. (?!expression) look-ahead négatif (ne pas être suivi de...)
    4. (?<=expression) look-behind positif
    5. (?<!expression) look-behind négatif
    6. Autres possibilités

  3. Quantifieurs "non-gourmands"

  4. Un modificateur (flag) supplémentaire

  5. Exemples de mise en œuvre
    1. Nettoyer les fichiers du corpus Frantext
    2. Récupération de fichiers contenant du sanskrit

  1. Variables locales supplémentaires

    Sylvain Lhuillier dit
    Je vous déconseille en effet l'usage de ces trois variables spéciales car leur présence dans un script active pour tout le script des mécanismes particuliers dans le moteur d'expressions régulières, qui ont pour effet secondaire de ralentir fortement sa vitesse d'exécution. Si vous avez besoin de ces variables dans un petit script qui ne sert qu'à cela, pas de problème pour les utiliser, mais évitez leur usage dans un projet de plusieurs milliers de lignes ou dans un script CGI appelé 10 fois par seconde.

  2. Look-ahead et look-behind

    1. Idée générale

      La notion de look-ahead apparaît en algorithmique pour traiter les situations suivantes.
      Lorsqu'on lit une chaîne (caractère par caractère), l'action standard de lecture d'un caractère consiste à effectuer en même temps
      deux opérations bien différentes :
      1. prendre connaissance du prochain caractère à lire
        en pratique, affecter sa valeur à une variable du programme
      2. retirer ce caractère de la chaîne à lire.

      Or, il arrive qu'on ait à faire un choix en fonction de la valeur lue, et que ce choix s'accommode mal de la disparition du caractère
      de la chaîne à lire : par exemple, si on veut arrêter la lecture...
      L'exemple typique est "lire jusqu'à la prochaine occurrence d'un caractère donné, exclue".

      La lecture standard avec consommation du caractère lu oblige en ce cas à gérer une variable supplémentaire contenant le caractère marquant la fin, et qu'on a indûment consommé - il eût mieux valu le laisser en place !
      Pour cela il eût fallu être capable de
      1. regarder quel est le prochain caractère à lire sans pour autant l'ôter de la chaîne,
      2. et, dans un second temps, décider de consommer ou non ce caractère sur la base de l'information ainsi acquise.

      C'est cette capacité de regarder sans consommer que désigne l'expression anglaise look-ahead (regarder en avant).

      Dans la pratique des exp. reg. il arrive fréquemment qu'une partie de l'expression serve à spécifier un motif marquant la fin de la zone visée.
      Si on effecture une substitution, il est alors nécessire de capturer ce motif de manière à le restituer à l'identique.

      Exemple : Dans un texte ordinaire, une majuscule n'apparaît qu'à l'initiale d'un mot, à moins que ce mot ne soit tout entier en majuscules.
      La présence d'une minuscule suivie d'une majuscule dénote donc une faute de frappe qu'on peut souhaiter corriger :

      $txt =~ s/(\p{Ll})(\p{Lu})/uc($1).$2/ge; # 'uc' comme 'upperCase'

      Dans l'exp. reg., la partie '(\p{Lu}' filtre la majuscule devant laquelle on doit trouver une autre majuscule, et qui doit être laissée en place.
      D'où la paire de parenthèses permettant de la retrouver comme valeur de $2.

      On peut reformuler le processus en disant que toute minuscule doit passer en majuscule lorsqu'on voit une majuscule qui la suit.
      Je dis bien lorsqu'on voit (sans la consommer : look-ahead) ...

      Dans ce qui suit, expression désigne une expression régulière quelconque.

    2. (?=expression) look-ahead positif

      For example, /\w+(?=\t)/ matches a word followed by a tab, without including the tab in $&.

      Mise en œuvre élémentaire : fichier
      look1.pl

      my $er= "\\w+(?=\t)"; # ne pas oublier de doubler le '\' !
      my $chn = "il    est    vraiment    zozo";
      my @res = $chn =~ m/$er/g;

      foreach my $r (@res) {
          print "$r\n";
      }


      jfp% perl look1.pl
      il
      est
      vraiment
      jfp%



      Explication détaillée :  comme la tabulation n'est pas directement visible, prenons une chaîne de look-ahead plus manifeste
      - mais moins utile en pratique !
      D'abord, sans utiliser le look-ahead : fichier look2.pl
      my $er= "\\w+tata"; #SANS look-ahead
      my $chn = "il    esttata    vraiment    zozotata";
      my @res = $chn =~ m/$er/g;

      foreach my $r (@res) {
          print "$r\n";
      }


      jfp% perl look2.pl
      esttata
      zozotata
      jfp%

      Et avec le look-ahead : fichier look3.pl
      my $er= "\\w+(?=tata)"; #AVEC look-ahead
      my $chn = "il    esttata    vraiment    zozotata";
      my @res = $chn =~ m/$er/g;

      foreach my $r (@res) {
          print "$r\n";
      }


      jfp% perl look3.pl
      est
      zozo
      jfp%


      Vu ?

      Revenons à notre exemple correctif : on obtient le même résultat avec

      $txt =~ s/(\p{Ll})(?=\p{Lu})/uc($1)/ge;

      avec le double avantage que
      1. il y a une variable de moins
      2. et surtout, l'expression traduit exactement la pensée du programmeur.

      Autre exemple du même tonneau : dans un texte français, "coeur" et "oeil" sont des fautes d'orthographe fréquentes pour "cœur" et "œil".

      $txt =~ s/oe(?=[ui])/\x{0153}/g; # minuscule
      $txt =~ s/O[eE](?=[uUiI])/\x{0152}/g; # majuscule

    3. (?!expression) look-ahead négatif (ne pas être suivi de...)

      For example /foo(?!bar)/ matches any occurrence of ``foo'' that isn't followed by ``bar''.

      Exemple : on reprend look3.pl où l'exp. reg. devient : my $er= "\\w+(?!tata)";

      il
      esttata
      vraiment
      zozotata


      On note avec intérêt que les chaînes trouvées sont les plus longues qui
      • ne contiennent que des lettres ou chiffres (\w)
      • ne soient pas suivies de "tata"
      Cf. plus loin les questions de gourmandise.

    4. (?<=expression) look-behind positif

      La notion de look-behind, forgée par symétrie sur look-ahead, est spécifique aux exp. reg. de Perl.
      Le look-behind est la partie de l'expression qui sert à spécifier un motif marquant le début de la zone visée.
      Si on effecture une substitution, il est alors nécessire de capturer ce motif de manière à le restituer à l'identique.

      Exemple (suite) : Si dans un mot l'initiale et la seconde occurrence sont des majuscules, alors le mot tout entier doit être en majuscules.

      $txt =~ s/(\p{Lu}\p{Lu})(\p{Ll}\p{L}*)/$1.uc($2)/ge;

      La partie initiale '(\p{Lu}\p{Lu})' ne sert qu'à marquer le motif après lequel on doit passer en majuscules,
      on ne le capture par la première paire de parenthèses que pour le replacer illico dans la chaîne...

      On écrira donc avec look-behind :
      $txt =~ s/(?<=\p{Lu}{2})(\p{Ll}\p{L}*)/uc($1)/ge;

      Contrairement à la précédente, cette notion ne joue aucun rôle en algorithmique. En effet, du point de vue algorithmique,
      la flèche du temps ne peut se renverser : avant n'est pas symétrique de après.
      Les exp. reg. énoncent des relations spatiales et non temporelles : devant est symétrique de derrière.

      Toutefois, dans les coulisses de l'implémentation, l'algorithmique reprend ses droits !
      La longueur de la chaîne look-behind doit être connue à l'avance.

      Une tentative avec
      $txt =~ s/(?<=\p{Lu}+)(\p{Ll}\p{L}*)/uc($1)/ge;
      soulève une protestation indignée :

      Variable length lookbehind not implemented in regex m/(?<=\p{Lu}+)(\p{Ll}\p{L}*)/ at newlook.pl line 26.


      Autre exemple trouvé sur le réseau

      For example, /(?<=\t)\w+/ matches a word following a tab, without including the tab in $&.
      Works only for fixed-width lookbehind.

      Variation : idem avec pour exp. reg. : my $er= "(?<=(?:est|zoz))\\w+";
      noter la "parenthèse syntaxique "(?:'', cf. cours n° 12 in fine, sans quoi la construction est inopérante.

      tata
      otata



    5. (?<!expression) look-behind négatif

      For example, /(?<!bar)foo/ matches any occurrence of ``foo'' that isn't following ``bar''.
      Works only for fixed-width lookbehind.

      Essayez vous-mêmes !

    6. Autres possibilités

      Notamment, la technique du look-around.
      Voir la page
      Using Look-ahead and Look-behind où on trouve des indications précises comme celle-ci.

      Note [however] that lookahead and lookbehind are not the same thing. You cannot use b. for lookbehind.
      If you are looking for a ``bar'' that isn't preceded by a ``foo'', /(?!foo)bar/ will not do what you want.
      That's because the (?!foo) is just saying that the next thing cannot be ``foo''--and it's not, it's a ``bar'', so ``foobar'' will match.
      You would have to do something like /(?!foo)...bar/ for that. We say ``like'' because there's the case of your ``bar'' not having three characters before it.
      You could cover that this way: /(?:(?!foo)...|^.{0,2})bar/.
      Sometimes it's still easier just to say:
          if (/bar/ && $` !~ /foo$/)

  3. Quantifieurs "non-gourmands"

    La sémantique des e.r. est "gourmande" (greedy) en ce sens qu'elles filtrent les sous-chaînes "les plus longues possibles".
    Exemple élémentaire : l'e.r.  /ab+/ confrontée à la chaîne "babbbc" filtre "abbb" et non pas "ab" ni "abb" qui seraient aussi possibles.
    Cette circonstance rend malaisée l'écriture d'e.r. destinées à filtrer à partir de XXX jusqu'à YYY,
    où on veut s'arrêter à la première occurrence de la marque YYY.

    Nous avons rencontré ce problème sur l'exemple des commentaires :
    On imagine bien que, si la marque finale YYY est une chaîne un peu longue, l'e.r. va se compliquer sérieusement.
    D'où l'idée d'une sémantique "sobre" (parsimonious) qui ferait filtrer la chaîne à partir de... jusqu'à... la plus courte.
    Comment indiquer ce changement de sémantique ?
    Comme l'incertitude sur la longueur de la chaîne filtrée vient le plus souvent d'opérateurs d'itération (quantificateurs),
    c'est eux qui seront chargés d'annoncer la sémantique sobre, grâce à une forme spéciale : un '?' suffixé :
    ainsi '*?' et '+?', mais aussi '{n,}?' et '{m,n}?'.
    Ces formes sont couramment appelées quantificateurs non-gourmands.

    De cette manière :
    Exemple de mise en œuvre : supprToutComm2.pl

    Autres exemples chez Sylvain Lhuillier.

  4. Un modificateur (flag) supplémentaire

    x permet de commenter les regexp [commentaires encadrés par (#...)]

  5. Exemples de mise en œuvre