IdentifiantMot de passe
Loading...
Mot de passe oublié ?Je m'inscris ! (gratuit)

EXPRESSIONS RÉGULIÈRES AVEC DELPHI

Utilisation des expressions régulières avec l'unité RegularExpressions

En lisant cet article vous apprendrez à vous servir des expressions régulières avec l'unité RegularExpressions de Delphi, unité apparue avec la version XE. Nous commencerons par quelques rappels généraux sur les expressions régulières. À la fin de chaque partie, des exercices corrigés vous seront proposés.

7 commentaires Donner une note à l´article (5)

Article lu   fois.

L'auteur

Profil Pro

Liens sociaux

Viadeo Twitter Facebook Share on Google+   

I. Introduction

On appelle expression régulière une chaîne de caractères représentant un ensemble de chaînes de caractères.

Ainsi la chaîne 'abc', considérée comme expression régulière, représente un ensemble contenant un seul élément, la chaîne 'abc'. Chacun des trois caractères se représente lui-même.

I-A. Classes de caractères

Plus intéressante, l'expression régulière '[abc]' représente l'ensemble des chaînes constituées d'un seul caractère appartenant à l'ensemble ['a', 'b', 'c']. Ajoutons que les trois caractères 'a', 'b' et 'c' étant consécutifs dans l'alphabet, notre expression régulière aurait pu s'écrire '[a-c]', de même qu'en Pascal, on pourrait écrire ['a'..'c'] au lieu de ['a', 'b', 'c'].

À la différence des lettres de l'alphabet qui se représentent simplement elles-mêmes, les crochets et le trait d'union sont donc des caractères affectés d'une signification spéciale, que nous venons de voir. Pour que ces caractères spéciaux (qu'on appelle aussi métacaractères) soient traités comme des caractères ordinaires, c'est-à-dire interprétés littéralement, il faut les « échapper », comme dit l'anglais, en les faisant précéder d'une barre oblique inversée : '\[', '\]', '\-'. La barre oblique inversée est donc elle aussi un métacaractère.

Un autre caractère spécial dont il faut parler à cet endroit, c'est l'accent circonflexe. Lorsqu'il est placé immédiatement après un crochet ouvrant, il signifie la négation ou l'exclusion. Par exemple la classe '[^0-9]' contient tous les caractères qui ne sont pas des chiffres.

I-B. Classes prédéfinies

La barre oblique inversée sert aussi à donner une signification spéciale à des caractères qui par défaut sont interprétés littéralement.

Par exemple la lettre 'd', précédée d'une barre oblique inversée, représente la classe des chiffres (digits en anglais). Les trois expressions suivantes sont donc synonymes : '[0123456789]', '[0-9]', '\d'.

Un utilisateur averti des expressions régulières en Perl me faisait remarquer que dans les versions récentes de Perl, ces trois expressions ne sont plus synonymes, car la classe '\d' comprend désormais les chiffres de tous les alphabets, et non pas seulement ceux de l'alphabet latin.

Voici un tableau contenant toutes les classes prédéfinies.

Notation

Sens

Notation équivalente

\a

Cloche

\x07

\d

Chiffre

[0-9]

\e

Échappement

\x1B

\f

Nouvelle feuille

\x0C

\n

Nouvelle ligne

\0A

\r

Retour chariot

\0D

\s

Espace (au sens large)

[ \t]

\t

Tabulation

\x09

\v

Tabulation verticale

\x0B

\w

Caractère alphanumérique (y compris '_')

[A-Za-z0-9_]

.

Tous les caractères (sauf #13 et #10)

[^\r\n]

Notons que si nous remplaçons 'a', 'd', 'e' ou l'une des autres lettres par une majuscule, l'expression désigne alors l'ensemble des caractères n'étant pas la cloche, un chiffre, etc. Ainsi la classe '\D' est équivalente à la classe '[^0-9]' que nous avons vue tout à l'heure.

Le point est une classe de caractères à utiliser avec précaution : elle contient tous les caractères, à l'exception des caractères Retour chariot (#13) et Nouvelle ligne (#10).

La barre oblique inversée sert encore à former l'expression '\b' qui désigne, non pas une classe de caractères, mais le début ou la fin d'un mot, entendu comme séquence de caractères alphanumériques. C'est ce qu'on appelle une ancre.

I-C. Quantificateurs

Puisque nous savons maintenant comment désigner des ensembles de caractères, voyons comment noter le nombre de caractères attendus. Nous avons vu, sans le remarquer jusqu'ici, que par défaut c'est le nombre un.

Pour indiquer un autre nombre, ou même un intervalle, on peut se servir des accolades, mais aussi des caractères '+', '*', et '?'. Les accolades permettent d'indiquer un nombre exact de caractères, ou un intervalle exact. Le symbole '+' veut dire « une fois ou plus ». L'étoile veut dire « zéro fois ou plus ». Le point d'interrogation veut dire « zéro ou une fois ».

Par exemple, l'expression '\d{2}' veut dire « deux chiffres » ; '\d{2,4}' veut dire « de deux à quatre chiffres » ; '\d{2,}' veut dire « deux chiffres ou plus ». L'expression '\d+' veut dire « un chiffre ou plus » ; '\d*' veut dire « zéro chiffre ou plus » ; '\d?' veut dire « zéro chiffre ou un ».

Par défaut, les opérateurs '+', '*', et '?' sont gourmands (greedy en anglais), c'est-à-dire que la recherche renvoie non pas la première correspondance trouvée, mais la plus longue. Cela a souvent son importance. Pour changer ce comportement, autrement dit pour rendre les opérateurs paresseux (lazy), il faut les faire suivre d'un point d'interrogation : '+?', '*?', '??'.

I-D. Autres opérateurs

Les parenthèses sont également des caractères spéciaux : elles servent à délimiter des groupes de caractères, par exemple pour y appliquer des quantificateurs. Ainsi l'expression '(abc)?def' désigne un ensemble contenant deux éléments, les chaînes 'abcdef' et 'def'. Le point d'interrogation rend le groupe 'abc' facultatif.

Les parenthèses peuvent aussi être utilisées en combinaison avec la barre verticale, qui signifie « ou ». Par exemple l'expression 'a(b|c)d' désigne un ensemble contenant les chaînes 'abd' et 'acd'.

Enfin, les caractères '^' et '$' signifient respectivement le début et la fin de la chaîne. Nous allons voir dans un instant quelle est l'utilité de ces caractères. Mais oui, vous avez raison, cela fait deux significations différentes pour l'accent circonflexe. Suivant l'endroit où on le place, il ne sera pas interprété de la même façon, et cela vaut aussi pour les autres caractères.

I-E. Exercices

On veut s'assurer qu'une chaîne de caractères donnée représente une année comprise entre '1900' et '2999'. Proposez une expression régulière appropriée.

On veut s'assurer qu'une chaîne de caractères donnée est un nombre à six chiffres hexadécimaux précédé du symbole '$'. Proposez une expression régulière appropriée.

On veut extraire d'une chaîne tout ce qui précède la première virgule. Proposez une expression régulière appropriée.

 
Cacher/Afficher le codeSélectionnez
 
Cacher/Afficher le codeSélectionnez
 
Cacher/Afficher le codeSélectionnez

II. Vérifier qu'une chaîne appartient à un ensemble

Il est temps à présent de faire connaissance avec l'unité RegularExpressions. Cette unité, apparue avec Delphi XE, va nous permettre d'effectuer diverses opérations sur des chaînes de caractères.

La plus simple de ces opérations est celle qui consiste à déterminer si telle chaîne appartient à tel ensemble.

II-A. La fonction IsMatch

La fonction dont nous avons besoin pour cela est la fonction IsMatch. Elle peut être appelée directement, avec deux chaînes de caractères comme arguments : le sujet (la chaîne à étudier) et l'expression régulière.

 
Sélectionnez
program IsMatch1;

{$APPTYPE CONSOLE}

uses
  RegularExpressions;

begin
  WriteLn(TRegEx.IsMatch('bonjour', '\w'));       // un caractère alphanumérique
  WriteLn(TRegEx.IsMatch('bonjour', '\w+'));      // un ou plusieurs caractères alphanumériques
  WriteLn(TRegEx.IsMatch('bonjour', '\w*'));      // zéro ou plus
  WriteLn(TRegEx.IsMatch('bonjour', '\w{7}'));    // sept
  WriteLn(TRegEx.IsMatch('bonjour', '[a-z]{7}')); // sept minuscules
  WriteLn(not TRegEx.IsMatch('bonjour', '\d'));   // un chiffre
  WriteLn(not TRegEx.IsMatch('bonjour', '\s'));   // un espace au sens large (équivalent à '[0-9]')
  WriteLn(TRegEx.IsMatch('bonjour', '\D'));       // un caractère qui n'est pas un chiffre
  ReadLn;
end.
Image non disponible

Comme vous l'avez remarqué si vous avez regardé de près le code ci-dessus, la fonction IsMatch vérifie seulement qu'une partie au moins de la chaîne passée comme premier argument appartient à l'ensemble représenté par l'expression régulière.

Si l'on veut s'assurer que la chaîne entière appartient à l'ensemble, il faut utiliser les opérateurs '^' et '$', qui signifient respectivement le début de chaîne et sa fin.

 
Sélectionnez
  WriteLn(TRegEx.IsMatch('bonjour', '\w'));       // TRUE
  WriteLn(not TRegEx.IsMatch('bonjour', '^\w$')); // TRUE

Dans 'bonjour', il y a plusieurs caractères alphanumériques, mais il n'y a pas qu'un seul caractère entre le début et la fin de la chaîne.

II-B. Exercice

Écrire trois programmes vérifiant vos réponses aux trois exercices de la première partie.

 
Cacher/Afficher le codeSélectionnez
 
Cacher/Afficher le codeSélectionnez
 
Cacher/Afficher le codeSélectionnez

III. Extraire d'une chaîne des groupes de caractères

III-A. Exemple

Imaginons que nous voulions, non seulement vérifier qu'une chaîne donnée appartient à un ensemble, mais aussi extraire de cette chaîne certains groupes de caractères. Disons par exemple que nous voulons extraire les groupes de chiffres d'une chaîne contenant une date, comme '26/09/2015'.

 
Sélectionnez
program Group1;

{$APPTYPE CONSOLE}

uses
  SysUtils, RegularExpressions;

const
  SUBJECT = '26/09/2015';
  PATTERN = '(\d{2})/(\d{2})/(\d{4})';

var
  expr: TRegEx;
  match: TMatch;
  group: TGroup;

begin
  expr := TRegEx.Create(PATTERN);

  match := expr.Match(SUBJECT);

  if match.Success then
    for group in match.Groups do
      WriteLn(Format('TGroup.Index=%d TGroup.Value="%s"', [group.Index, group.Value]));

  ReadLn;
end.
Image non disponible

Nous avons utilisé dans notre expression régulière des parenthèses pour délimiter les groupes de caractères à extraire, puis nous avons utilisé la fonction Match. Soit dit en passant, nous n'avons pas vérifié que les chiffres extraits formaient en effet une date valide. L'expression utilisée ne tient pas compte du fait qu'il n'y a que 31 jours au plus dans le mois, 12 mois dans l'année, etc. Une vérification complète devrait même tenir compte des années bissextiles, mais il est peu probable que cela soit faisable par les expressions régulières !

III-B. Groupes nommés

Nous pouvons également nommer les groupes. Pour nommer un groupe, il faut insérer juste après la parenthèse ouvrante les caractères '?<nom>'.

 
Sélectionnez
program Group2;

{$APPTYPE CONSOLE}

uses
  SysUtils, RegularExpressions;

const
  SUBJECT = '26/09/2015';
  PATTERN = '(' + '?<day>' + '\d{2})/(' + '?<month>' + '\d{2})/(' + '?<year>' + '\d{4})';

var
  group: TGroup;
  match: TMatch;
  regEx: TRegEx;

begin
  regEx := TRegEx.Create(PATTERN, []);

  match := regEx.Match(SUBJECT);

  if match.Success then
  begin
    group := match.Groups['year'];
    WriteLn(group.Value);
  end;

  ReadLn;
end.
Image non disponible

III-C. Parenthèses non capturantes

Quelquefois on a besoin de délimiter un groupe de caractères, mais on n'a pas besoin de capturer les caractères correspondants. Dans ce cas, pour ne pas donner de travail inutile au programme, on aura recours à des parenthèses non capturantes.

Imaginons par exemple qu'on veuille détecter dans un code source en Pascal les Write et les WriteLn, sans faire de différence entre les deux. On pourrait utiliser l'expression 'Write(Ln)?'. Mais de cette façon on capturerait à chaque fois ou une chaîne vide ou la chaîne 'Ln'. Cependant nous avons supposé qu'on ne voulait pas tenir compte de la différence entre Write et WriteLn. On utilisera donc l'expression suivante :

 
Sélectionnez
'Write(?:Ln)?'

Les caractères '?:' ont été ajoutés immédiatement après la parenthèse ouvrante pour rendre les parenthèses non capturantes.

III-D. Exercice

Écrire un programme qui extrait les parties numériques d'une heure donnée sous la forme hh:mm, par exemple '10:29'. Le programme devra vérifier de la façon la plus stricte la validité de l'heure.

 
Cacher/Afficher le codeSélectionnez

IV. Détecter des correspondances multiples

Il est possible de détecter les multiples groupes de caractères qui, dans une chaîne donnée, appartiennent à un même ensemble. Pour cela nous aurons besoin des fonctions Match, Success et NextMatch.

IV-A. La fonction NextMatch

L'exemple suivant détecte les groupes de caractères représentant des nombres.

 
Sélectionnez
program Match1a;

{$APPTYPE CONSOLE}

uses
  SysUtils, RegularExpressions;

var
  match: TMatch;

begin
  match := TRegEx.Match('10 +10 0.5 .5', '\s*[-+]?[0-9]*\.?[0-9]+\s*');

  while match.Success do
  begin
    WriteLn(match.Value);

    match := match.NextMatch;
  end;

  ReadLn;
end.
Image non disponible

IV-B. Le type TMatchCollection

La même opération peut être effectuée d'une autre façon : au moyen de la fonction Matches, laquelle renvoie un résultat de type TMatchCollection.

 
Sélectionnez
program MatchCollection1;

{$APPTYPE CONSOLE}

uses
  SysUtils,
  RegularExpressions;

var
  expr: TRegEx;
  collection: TMatchCollection;
  i: Integer;

begin
  expr.Create('\w');

  collection := expr.Matches('abc');

  for i := 0 to collection.Count - 1 do
    with collection[i] do
      WriteLn(Format('%d %d %d %s', [i, Index, Length, Value]));

  ReadLn;
end.
Image non disponible

IV-C. Exercice

Écrire deux versions d'un programme qui extrait tous les chemins de la variable d'environnement PATH. La première version du programme utilisera la méthode NextMatch, la seconde version utilisera la méthode Matches.

 
Cacher/Afficher le codeSélectionnez
 
Cacher/Afficher le codeSélectionnez

V. Éclatement d'une chaîne

Voyons à présent comment éclater une chaîne au moyen d'une expression régulière, c'est-à-dire la découper selon une certaine règle et disposer les morceaux découpés dans un tableau.

V-A. Exemple

L'expression représente un groupe de caractères à traiter comme délimiteurs. Dans le cas le plus simple, c'est un seul caractère :

 
Sélectionnez
program Split1;

{$APPTYPE CONSOLE}

uses
  SysUtils, RegularExpressions;

var
  a: TArray<string>;
  s: string;
  i: integer;

begin
  a := TRegEx.Split(GetEnvironmentVariable('PATH'), ';');
  
  for s in a do
    WriteLn(s);

  a := TRegEx.Split('a b,c-d', '[ ,-]');

  for i := 0 to High(a) do
    WriteLn(a[i]);

  ReadLn;
end.
Image non disponible

V-B. Exercice

Écrire un programme qui découpe le contenu de la variable d'environnement PATH en traitant comme délimiteurs les caractères ';' et '\'.

 
Cacher/Afficher le codeSélectionnez

VI. Remplacement de groupes de caractères

L'opération suivante ressemble un peu à la précédente : au lieu de traiter certains groupes de caractères comme délimiteurs, nous les remplacerons, au moyen de l'expression Replace.

VI-A. Remplacement par une chaîne constante

 
Sélectionnez
program Replace1;

{$APPTYPE CONSOLE}

uses
  RegularExpressions;

begin
  WriteLn(TRegEx.Replace('WRITELN writeln', 'writeln', 'WriteLn', [roIgnoreCase]));
  ReadLn;
end.
Image non disponible

Comme vous le voyez, la fonction Replace admet quatre paramètres : la chaîne à traiter, l'expression régulière représentant les groupes à remplacer, la chaîne par laquelle ces groupes seront remplacés et enfin un ensemble d'options. Dans l'exemple ci-dessus, on a utilisé (sans aucune utilité d'ailleurs) l'option permettant d'obtenir un remplacement qui ne tienne pas compte de la casse des groupes à remplacer.

VI-B. Remplacement par une chaîne composée

Au lieu de remplacer les groupes par une chaîne constante, on peut les remplacer par une chaîne variable, composée d'éléments des groupes détectés.

 
Sélectionnez
program Replace2;

{$APPTYPE CONSOLE}

uses
  RegularExpressions;

var
  expr: TRegEx;

begin
  expr := TRegEx.Create('(\w+)\s(\w+)');
  WriteLn(expr.Replace('abc def', '\2 \1'));
  ReadLn;
end.
Image non disponible

Dans la chaîne '\2 \1', la barre oblique inversée suivie d'un chiffre représente l'élément capturé qui devra être inséré à cette place dans la chaîne de remplacement. Ainsi, '\1' signifie « le premier élément capturé ».

Pratique, non ? Mais ce n'est pas tout. La chaîne de remplacement peut même être le résultat d'une fonction recevant comme arguments les éléments capturés.

VI-C. Remplacement par le résultat d'une fonction

Supposons que nous voulions valider une chaîne représentant une position donnée d'une partie d'échecs. La chaîne devra être conforme à la notation standard, à savoir la notation FEN (Forsyth-Edwards Notation). Voici la chaîne FEN correspondant à la position initiale d'une partie de jeu d'échecs :

 
Sélectionnez
'rnbqkbnr/pppppppp/8/8/8/8/PPPPPPPP/RNBQKBNR w KQkq - 0 1'

Comme on le voit, l'occupation des lignes de l'échiquier est représentée par des groupes de caractères contenant des lettres (les pièces) et des chiffres (le nombre de cases vides consécutives). Nous devons nous assurer que le nombre total de cases pour chaque ligne de l'échiquier est bien égal à huit, et pour ce faire nous décidons de remplacer les chiffres par des caractères répétés autant de fois qu'il y a de cases vides.

 
Sélectionnez
type
  TFENValidator = class
    function ReplaceWith(const aMatch: TMatch): string;
    function ExpandRow(const aRow: string): string;
    function IsFEN(const aStr: string): boolean;
  end;

function TFENValidator.ReplaceWith(const aMatch: TMatch): string;
const
  EMPTY_SQUARE_SYMBOL = '-';
begin
  result := StringOfChar(EMPTY_SQUARE_SYMBOL, StrToInt(aMatch.Groups.Item[1].Value));
end;

function TFENValidator.ExpandRow(const aRow: string): string;
begin
  with TRegEx.Create('([1-8])') do
    result := Replace(aRow, ReplaceWith);
end;

function TFENValidator.IsFEN(const aStr: string): boolean;
var
  a,
  b: TStringList;
  expr: TRegEx;
  i: integer;
  s: string;
begin
  a := TStringList.Create;
  b := TStringList.Create;

  b.Delimiter := '/';
  b.StrictDelimiter := TRUE;

  a.DelimitedText := aStr;
  result := (a.Count = 6);

  if not result then
    Exit;

  b.DelimitedText := a[0];
  result := result and (b.Count = 8);

  if not result then
    Exit;

  expr.Create('^[1-8BKNPQRbknpqr]+$');

  for i := 0 to b.Count - 1 do
  begin
    s := ExpandRow(b[i]);
   {WriteLn(s);}

    result := result and expr.IsMatch(b[i]) and (Length(s) = 8);
  end;

  result := result and TRegEx.IsMatch(a[1], '^(w|b)$');
  result := result and TRegEx.IsMatch(a[2], '^([KQkq]+|\-)$');
  result := result and TRegEx.IsMatch(a[3], '^([a-h][36]|\-)$');

  expr.Create('^\d+$');

  result := result and expr.IsMatch(a[4]) and (StrToInt(a[4]) >= 0);
  result := result and expr.IsMatch(a[5]) and (StrToInt(a[5]) >= 1);
end;

Voici un autre exemple de remplacement par fonction. C'est un programme qui cherche dans une chaîne de caractères des variables à remplacer par leur valeur. Les variables sont notées suivant la syntaxe utilisée pour les variables d'environnement comme %date% ou %username%. La fonction renvoyant la chaîne à substituer tirera ses résultats d'un dictionnaire.

 
Sélectionnez
program Replace3b;

{$APPTYPE CONSOLE}

uses
  System.SysUtils,
  System.Classes,
  System.RegularExpressions;

type
  TExpander = class
    fDictionary: TStrings;
    constructor Create(aDictionary: TStrings);
    function ReplaceWith(const aMatch: TMatch): string;
    function Expand(const s: string): string;
  end;

constructor TExpander.Create(aDictionary: TStrings);
begin
  fDictionary := aDictionary;
end;

function TExpander.ReplaceWith(const aMatch: TMatch): string;
begin
  result := fDictionary.Values[aMatch.Groups.Item[1].Value];
end;

function TExpander.Expand(const s: string): string;
var
  expr: TRegEx;
begin
  expr.Create('%(.+)%');
  result := expr.Replace(s, ReplaceWith);
end;

var
  dictionary: TStrings;
  expander: TExpander;

begin
  dictionary := TStringList.Create;
  expander := TExpander.Create(dictionary);

  dictionary.Values['REPERTOIRE_PROJET'] := ExtractFileDir(ParamStr(0));

  WriteLn(expander.Expand('%REPERTOIRE_PROJET%'#13#10'%REPERTOIRE_PROJET%'));

  dictionary.Free;
  expander.Free;

  ReadLn;
end.
Image non disponible

VI-D. Exercice

Écrire un programme qui remplace les multiples occurrences d'une date sous la forme jj/mm/aaaa par une date de la forme mm/jj/aaaa.

 
Cacher/Afficher le codeSélectionnez

VII. Expressions alternatives

Rassurez-vous, nous avons pratiquement fini notre tour des fonctions de l'unité RegularExpressions. Si vous avez encore une minute, je vous propose un dernier exemple montrant comment on peut utiliser des expressions alternatives, pour savoir si une chaîne donnée appartient à tel ensemble, ou à un tel autre, ou à un troisième, etc.

VII-A. Exemples

Dans le langage des expressions régulières, c'est le caractère '|' qui symbolise cette alternative.

 
Sélectionnez
program Alternative1;

{$APPTYPE CONSOLE}

uses
  SysUtils, RegularExpressions;

const
  SAMPLE: array[0..10] of string = (
    '*ABORG-001360',
    '?CASTELO 1419-002370',
    '*EX/MINOU1424-412380',
    '*RUBAN-003001',
    '1234578011230',
    '3760162052329',
    '1456441001360',
    'AAAAAAA',
    '*AAAAA-001',
    '*AAAAAA001360',
    '123121212'
  );

const
  PATTERN1 = '^[*?]([^-]{1,15})-(\d{3})(\d{3})$';
  PATTERN2 = '^(123\d{10})$';
  PATTERN3 = '^(\d{13})$';
  PATTERN = PATTERN1 + '|' + PATTERN2 + '|' + PATTERN3;

var
  group: TGroup;
  expr: TRegEx;
  match: TMatch;
  i: integer;

begin
  expr := TRegEx.Create(PATTERN);

  for i := Low(SAMPLE) to High(SAMPLE) do
  begin
    match := expr.Match(SAMPLE[i]);
    if match.Success then
    begin
      Write(i);
      for group in match.Groups do
        Write(' "', group.Value, '"');
      WriteLn;
    end;
  end;

  ReadLn;
end.
Image non disponible

Le code ci-dessus affiche tous les résultats sans les analyser. Si l'on veut savoir à laquelle des expressions régulières correspond tel groupe de caractères, on pourra procéder de la façon suivante :

 
Sélectionnez
procedure ShowResult(aGroupName: string);
begin
  WriteLn(Format('  %s = "%s"', [aGroupName, match.Groups[aGroupName].Value]));
end;

begin
  expr := TRegEx.Create(PATTERN);

  for i := Low(SAMPLE) to High(SAMPLE) do
  begin
    WriteLn('SAMPLE[', i, ']');
    match := expr.Match(SAMPLE[i]);
    if match.Success then
    begin
      case match.Groups.Count of
        4: begin
             ShowResult('nom1');
             ShowResult('nom2');
             ShowResult('nom3');
           end;
        5: ShowResult('nom4');
        6: ShowResult('nom5');
      end;
    end;
  end;

  ReadLn;
end.
Image non disponible

On remarque dans l'exemple ci-dessus que lorsque la chaîne étudiée correspond à la troisième expression alternative, la variable match.Groups.Count vaut 6, parce que le groupe capturé correspond à la sixième sous-expression entre parenthèses.

Ce comportement peut être modifié, de telle sorte que le comptage des groupes soit fait séparément pour chacune des expressions alternatives, au lieu d'être un comptage global. Pour cela, il faudra utiliser l'expression suivante :

 
Sélectionnez
PATTERN = '(?|' + PATTERN1 + '|' + PATTERN2 + '|' + PATTERN3 + ')';

Attention, le comptage pour chaque expression alternative commencera à deux, à cause des parenthèses que nous avons ajoutées. Ces parenthèses produiront une capture supplémentaire (et inutile).

VII-B. Exercice

Écrire un programme qui utilise des expressions alternatives pour savoir si une chaîne donnée est une date (de la forme jj/mm/aaaa), une heure (de la forme hh:mm) ou autre chose.

 
Cacher/Afficher le codeSélectionnez

VIII. Unicode

L'unité RegularExpressions supporte l'Unicode. Voici une expression régulière représentant les lettres capitales de l'alphabet grec :

 
Sélectionnez
program Unicode6;

{$APPTYPE CONSOLE}

uses
  System.RegularExpressions,
  Vcl.Dialogs;

var
  s: UnicodeString;
  match: TMatch;

begin
  s := 'ΙΧΘΥΣ';

  match := TRegEx.Match(s, '[Α-Ω]{5}');

  ShowMessage(match.Value);
end.
Image non disponible

IX. Conclusion

L'unité RegularExpressions est basée sur l'unité RegularExpressionsCore qui, sauf le nom, est identique à l'unité PerlRegEx de Jan Goyvaerts.

 
Sélectionnez
uses
{$IF CompilerVersion >= 22.0}
  RegularExpressionsCore;
{$ELSE}
  PerlRegEx; (* http://www.regular-expressions.info/download/TPerlRegEx.zip *)
{$IFEND}

L'unité PerlRegEx est, elle, basée sur la bibliothèque PCRE (Perl Compatible Regular Expressions) de Philip Hazel.

Si vous souhaitez en savoir plus sur la syntaxe des expressions régulières, vous pouvez consulter le tutoriel de Jan Goyvaerts : Regular Expressions. Vous pouvez télécharger les exemples commentés dans cet article (et quelques autres) : Exemples.

La solution des exercices se trouve dans le dossier Solutions. Le dossier RegularExpressionsCore contient des exemples d'utilisation de l'unité RegularExpressionsCore ou PerlRegEx.

Les exemples ont été testés avec Delphi XE2.

Je remercie les membres du forum qui m'ont permis de reproduire leurs exemples, ainsi que les relecteurs de cet article : gvasseur58, Alcatîz, Lolo78, ClaudeLELOUP, pierruel et f-leb.

Vous avez aimé ce tutoriel ? Alors partagez-le en cliquant sur les boutons suivants : Viadeo Twitter Facebook Share on Google+   

Les sources présentées sur cette page sont libres de droits et vous pouvez les utiliser à votre convenance. Par contre, la page de présentation constitue une œuvre intellectuelle protégée par les droits d'auteur. Copyright © 2015 Roland Chastain. Aucune reproduction, même partielle, ne peut être faite de ce site ni de l'ensemble de son contenu : textes, documents, images, etc. sans l'autorisation expresse de l'auteur. Sinon vous encourez selon la loi jusqu'à trois ans de prison et jusqu'à 300 000 € de dommages et intérêts.