Cours n° 13, 12 janvier 2012

Jean-François Perrot

Expressions régulières en Perl - 3


  1. Retour sur les "quantificateurs"

  2. Classes de caractères
    1. Principales notations prédéfinies
    2. Classes de caractères définies par des propriétés dérivées du standard Unicode.
    3. Classes définies par le programmeur

  3. Ancres
    1. Exemple d'application au contrôle de données :


Retour sur les "quantificateurs"

Dans la documentation relative aux expressions régulières, l'opérateur unaire postfixé étoile "*" et son dérivé "+" sont souvent appelés quantificateurs
(probablement par référence aux symboles "∀" et "∃" utilisés en logique, bien que l'analogie ne saute pas aux yeux).
Voici le tableau complet des notations ainsi désignées :

opérateur  nombre de répétitions du motif exemple de filtrage mots filtrés
* 0 fois ou plus m/a*/ mot vide, a, aa, aaa ...
+ 1 fois ou plus m/a+/ a, aa, aaa ...
? 0 ou 1 fois m/a?/ mot vide ou a
{n} n fois exactement m/a{4}/ aaaa
{n,} au moins n fois m/a{2,}/ aa, aaa, aaaa ...
{,n} au plus n fois m/a{,3}/ mot vide, a, aa ou aaa
{n,m} entre n et m fois m/a{2,5}/ aa, aaa, aaaa ou aaaaa

Ainsi, '?' est équivalent à '{0,1}'.

Classes de caractères

Comme on l'imagine facilement, en écrivant une e.r. on a souvent besoin de spécifier quel genre de caractères peut se trouver
à une certaine position. Le problème est alors de décrire un ensemble, ou une classe, de caractères.
Nous connaissons une première technique avec la notation des ensembles de caractères entre crochets, assortie de l'opérateur "|"
de réunion ensembliste.
Il y a au moins deux autres approches à ce problème, celle des notations prédéfinies et celle des propriétés Unicode.
  1. Principales notations prédéfinies

    Elles sont de la forme "\une_lettre_minuscule" :


    Si on remplace la minuscule par la majuscule correspondante, on obtient les ensembles complémentaires :


    Ces notations traditionnelles sont en principe inféodées au code ASCII, comme l'indiquent les notations équivalentes.
    Toutefois, leur interprétation (notamment celle de \w) dépend du jeu de caractères employé.
    Nous en verrons d'autres dans la suite du cours.

  2. Classes de caractères définies par des propriétés dérivées du standard Unicode.

    La nomenclature issue d'Unicode est fort abondante ! L'idée est d'utiliser cette nomenclature dans les e.r. avec la notation
    \p{nom_de_la_propriété} avec \p minuscule (et \P{nom_de_la_propriété} avec \P majuscule pour désigner la négation).

    Exemple :
    l'e.r. '\p{Lu}\p{Ll}+' va donc décrire les noms dont l'initiale est majuscule et le reste (non-vide) en minuscules,
    quel que soit l'alphabet employé (à condition qu'il distingue majuscules et minuscules, comme le grec, le cyrillique ou l'arménien).

    Les propriétés utilisables sont principalement de trois sortes :

    1. Les catégories générales, comme Lu et Ll illustrées ci-dessus.
      Ce sont celles qui apparaissent dans le champ n° 3 du fichier UnicodeData.txt
      auxquelles on peut ajouter les questions de directionnalité, par exemple \p{BidiClass:R} désignant les caractères qui s'écrivent de droite à gauche,
      ainsi que les "propriétés étendues" comme WhiteSpace et leurs dérivés comme Alphabetic ou ASCII.

    2. Les écritures (en anglais scripts) : Greek, Latin, Han...

    3. Les blocs Unicode, dont le nom est préfixé par 'In'.
      Ces blocs recouvrent souvent des écritures (scripts) : InHiragana (bloc) est fonctionnellement identique à Hiragana (script),
      mais certaines écritures se répartissent à travers plusieurs blocs (notamment Latin).
      La distinction n'est donc pas superflue.
      Exemple : Greek recouvre les deux blocs Greek and Coptic (le grec moderne, monotonique, U+0370 - U+03FF)
      et Greek Extended (les lettres accentuées, polytoniques, du grec ancien, U+1F00 - U+1FFF).
      Voyez plus loin un exemple où on distingue les classes \p{Greek} et \p{In_Greek_and_Coptic}.

      N.B. Les noms des blocs sont souvent composés, d'où une inquiétude quant au libellé exact des noms de classes.
      Sur ce point, Perl fait preuve d'une grande largeur d'esprit (voyez la documentation).
      Comme règle opérationneelle, je vous propose de reproduire le nom, précédé par 'In',
      en séparant les parties par des soulignements, p. ex. In_Greek_and_Coptic.

    Pour une liste complète, avec références au standard Unicode, voyez l'excellente documentation perlunicode - Unicode support in Perl,
    section Unicode Character Properties.

  3. Classes définies par le programmeur

    La grammaire des e.r. de Perl ne prévoit pas d'opérateur de conjoncton logique (intersection ensembliste), contrairement à celle des e.r. de Java.
    Nous sommes donc en difficulté pour préciser que les noms (en fait, des prénoms) comme Paul-Émile, décrits par
    \p{Lu}\p{Ll}+(-\p{Lu}\p{Ll}+)?
    c'est-à-dire de la forme initiale majuscule suivie de minuscules, avec éventuellement une seconde partie de même forme précédée d'un tiret,
    doivent en outre être écrits en alphabet latin !

    Pour cela Perl nous offre la possibilité de programmer nous-mêmes la définition des classes qui nous intéressent,
    en écrivant des fonctions spéciales dont le nom pourra paraître dans les notations \p{ma_déf} et \P{ma_déf}.
    Exemple :
    sub MajLat {
    return("+utf8::Lu\n" . "&utf8::Latin\n");
    }

    sub MinLat {
    return("+utf8::Ll\n" . "&utf8::Latin\n");
    }
    et \p{MajLat}\p{MinLat}+(-\p{MajLat}\p{MinLat}+)?

    Pour comprendre comment nos deux fonctions sont rédigées, lisons l'excellente documentation de Perl :
    http://perldoc.perl.org/perlunicode.html#User-Defined-Character-Properties
    Outre les détails de syntaxe, quelque peu saugrenus, l'essentiel est la possibilité à ce niveau de demander l'intersection de classes de caractères,
    grâce à l'opérateur '&' que l'on voit dans les exemples ci-dessus.
    Mais attention !
    Comme il arrive souvent en informatique, le processus de calcul n'est pas exactement conforme à l'intuition naïve...

    It's important to remember not to use "&" for the first set; that would be intersecting with nothing (resulting in an empty set).

Ancres

parfois appelées assertions :
il s'agit de marques qui indiquent où doit se trouver la chaîne filtrée dans le texte-candidat, sans désigner de caractères.
Elles sont donc analogues aux suffixes (flags) mais elles figurent à l'intérieur de l'e.r.

Les deux principales sont '^' et '$', qui doivent apparaître en tête (resp. en queue) de l'e.r. :
Exemple : 'Paul-Émile' =~ m/^\p{MajLat}\p{MinLat}+(-\p{MajLat}\p{MinLat}+)?$/  est vrai, mais
'le père de Paul-Émile' =~ m/^\p{MajLat}\p{MinLat}+(-\p{MajLat}\p{MinLat}+)?$/  est faux.


N.B. En raison de sa signification même, le symbole '^' (resp. '$') ne peut être précédé (resp. suivi) d'un autre caractère dans l'e.r.
En revanche, on peut très bien exprimet des disjonctions, du genre "commencer par..." ou bien "finir par..." :
'(^début... ) | (...fin$)', voire un choix entre deux structures '(^struct1$) | struct2'.

Une troisième ancre mérite d'être signalée : '\b', qui signale une frontière de mot (b comme boundary).
Exemple : 'le père de Paul-Émile' =~ m/\b\p{MajLat}\p{MinLat}+(-\p{MajLat}\p{MinLat}+)?\b/  est vrai, mais
'le père dePaul-Émile' =~ m/\b\p{MajLat}\p{MinLat}+(-\p{MajLat}\p{MinLat}+)?\b/  est faux.

Exemple d'application au contrôle de données :

Le format du fichier d'entrée est  "nom - espaces ou tabulations - note" 
et il est supposé codé en UTF-8 (voir par exemple NomsNotes.txt).

Paul-Émile     12
Marie-Hélène   05
Éléonore       14
etc....

fichier TestNotes.pl

# Construction d'un tableau (hash) à partir de texte

use strict;
use warnings;

sub construire ($) { # nom de fichier -> hash
    my ($fich) = @_;
    open(ENTREE, "<utf8", $fich);

    my $formatNote = '^[01]\d|20$';
    my $formatNom = '^\p{Lu}\p{Ll}+(-\p{Lu}\p{Ll}+)?$';

    my %res;
    my $k = 0;
    my @tablignes = <ENTREE>;
    foreach my $ligne ( @tablignes ){
        $k++;
        chomp($ligne); # supprime le "\n" final
        my ($le_nom, $la_note) = split( /\s+/, $ligne);
        if( $le_nom !~ m/$formatNom/ ){
            die("Erreur sur $le_nom, ligne $k");
        }
        if( $la_note !~ m/$formatNote/ ){
            die("Erreur sur la note de $le_nom, ligne $k");
        }
        $res{$le_nom} = $la_note;
    }
    return %res;
}# construire

sub TxtVersHash($){
    my ($fichIn) = @_;
    my %res = construire($fichIn);
    print %res;
}#TxtVersHash

binmode(STDOUT, ":utf8");
TxtVersHash($ARGV[0]);


Versions plus perfectionnées du même script, avec fichier-exemple :