Expressions régulières avec Free Pascal

Expressions régulières avec l'unité RegExpr de Free Pascal

Roland Chastain

Vous souhaitez savoir ce que sont les expressions régulières, et apprendre à les utiliser avec Free Pascal ? Ce tutoriel est fait pour vous. Les deux premières parties sont consacrées à la syntaxe des expressions régulières, la troisième partie à l'unité RegExpr de Free Pascal.

Commentez

- Titre : Expressions régulières avec Free Pascal

- Auteur : Roland Chastain

- Parution : 4 juin 2016

- Mise à jour : 4 juin 2016

- Licence : 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 ® 2016 Roland Chastain. Aucune reproduction, même partielle, ne peut être faite de ce site et 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.

Sommaire

- I. Introduction à la syntaxe des expressions régulières

- I-A. Définition

- I-B. Classes de caractères prédéfinies

- I-C. Indicateurs de quantité

- I-D. Classes de caractères non prédéfinies

- I-E. Caractères spéciaux

- II. Complément sur la syntaxe des expressions régulières

- II-A. Classes prédéfinies

- II-B. Indicateurs de quantité

- III. Usage des expressions régulières

- III-A. Fonction ExecRegExpr

- III-B. Méthode Exec

- III-C. Méthode ExecNext

- III-D. Fonction ReplaceRegExpr

- III-E. Méthode Replace

- III-F. Fonction SplitRegExpr

- IV. Conclusion

I - Introduction à la syntaxe des expressions régulières

I-A - Définition

Une expression régulière (d'aucuns disent expression rationnelle) est une chaîne de caractères qui représente un ensemble de chaînes de caractères.

Quelques exemples parleront aussi bien ou mieux qu'un long discours.

I-B - Classes de caractères prédéfinies

La chaîne '2016', pourvu qu'on la considère comme une expression régulière, représente un ensemble ne contenant qu'un unique élément, la chaîne '2016'. En revanche, la chaîne '\d\d\d\d', considérée elle aussi comme expression régulière, représente l'ensemble de toutes les chaînes de quatre caractères composées exclusivement de chiffres. Par exemple : '9999', '4789', '7462'...

La barre oblique inversée, associée au caractère 'd', représente une classe prédéfinie de caractères, la classe des chiffres.

I-C - Indicateurs de quantité

Toujours pour représenter l'ensemble des chaînes de quatre caractères composées exclusivement de chiffres, on peut adjoindre à la classe chiffre un indicateur de quantité :

1. '\d{4}'

Les accolades, comme la barre oblique inversée, ont une signification particulière. Elles confèrent aussi une signification particulière au caractère '4', qui dans ce cas représente le nombre de caractères attendus.

I-D - Classes de caractères non prédéfinies

On peut définir ses propres classes de caractères. On peut par exemple s'amuser à redéfinir la classe chiffres. N'est-ce pas la classe des caractères de '0' à '9' ?

1. '[0-9]{4}'

Mais c'est aussi, si l'on veut, la classe des caractères '0', '1', '2', '3', '4', '5', '6', '7', '8', '9' :

1. '[0123456789]{4}'

Les crochets et le trait d'union ont une signification particulière qui est évidente.

Nous avons donc fait connaissance avec les classes de caractères (prédéfinies ou non) ainsi qu'avec les indicateurs de quantité. Du moins nous avons compris le principe. Pour le reste voyez les tableaux des classes prédéfinies et des indicateurs de quantité.

I-E - Caractères spéciaux

Nous avons aussi fait connaissance avec certains caractères spéciaux, par exemple la barre oblique inversée, qui sert comme nous l'avons vu à former la classe des chiffres ('\d').

La barre oblique inversée a encore d'autres fonctions. Suivie de certaines lettres, elle sert à désigner soit des classes prédéfinies soit certains caractères, comme la tabulation ('\t'), la nouvelle ligne ('\n') ou le retour chariot (\r'). Suivie d'un 'x' et de deux chiffres ou lettres de 'A' à 'F', elle sert à désigner un caractère par son code ANSI. Ainsi la tabulation peut être notée '\x09', la nouvelle ligne '\x0a'.

Enfin, la barre oblique inversée sert aussi à forcer l'interprétation littérale des caractères spéciaux. Par exemple, pour chercher une barre oblique inversée dans une chaîne, on écrira '\\'.

Il y a d'autres caractères spéciaux ou caractères magiques ou métacaractères (comme il vous plaira de les nommer) dont je vous parlerai un peu plus loin, lorsque l'occasion se présentera. Pour le moment il suffit que vous ayez compris le principe.

II - Complément sur la syntaxe des expressions régulières

II-A - Classes prédéfinies

Nous avons fait connaissance à l'instant avec la classe des chiffres, notée '\d'. Voici un tableau contenant toutes les classes de caractères prédéfinies. Par commodité (et aussi à cause de la similitude de la notation), on a mis dans le même tableau les expressions désignant un caractère unique.

Notation

Signification

Notation équivalente

.

Caractère quelconque

[\x00-\xFF]

\w

Caractère alphanumérique

[A-Za-z0-9_]

\W

Caractère non alphanumérique

[^A-Za-z0-9_]

\d

Caractère numérique

[0-9]

\D

Caractère non numérique

[^0-9]

\s

Espace au sens large

[ \t\n\r\f]

\S

Caractère qui n'est pas un espace au sens large

[^ \t\n\r\f]

\a

Alarme (cloche)

\x07

\t

Tabulation

\x09

\n

Nouvelle ligne

\x0a

\f

Nouvelle page

\x0c

\r

Retour chariot

\x0d

\e

Échappement

\x1b

Vous avez remarqué que l'accent circonflexe, lorsqu'il est placé après le crochet ouvrant, sert à définir une classe négativement, c'est-à-dire par les caractères qu'elle ne contient pas.

II-B - Indicateurs de quantité

Nous avons vu plus haut un exemple d'indicateur de quantité, dans l'expression '\d{4}'. Nous avons vu aussi que la quantité par défaut est un. Ainsi l'expression '\d' signifie "un chiffre".

Voici un tableau contenant tous les indicateurs de quantité :

Notation

Signification

Notation équivalente

*

Zéro ou plus (gourmand)

{0,}

+

Un ou plus (gourmand)

{1,}

?

Zéro ou un (gourmand)

{n}

Exactement n fois (gourmand)

{n,}

Au moins n fois (gourmand)

{n,m}

Au moins n fois mais pas plus de m fois (gourmand)

*?

Zéro ou plus (non gourmand)

{0,}?

+?

Un ou plus (non gourmand)

{1,}?

??

Zéro ou un (non gourmand)

{0,1}?

{n}?

Exactement n fois (non gourmand)

{n,}?

Au moins n fois (non gourmand)

{n,m}?

Au moins n fois mais pas plus de m fois (non gourmand)

Un mot s'impose sur la différence entre les modes gourmand et non gourmand.

Imaginez que vous vouliez extraire d'un texte toutes les parties entre guillemets. Vous êtes tenté d'utiliser l'expression '".+"', qui signifie "un guillemet, puis n'importe quel caractère, une fois ou plus, puis un autre guillemet". Mais à l'exécution de votre programme, vous obtenez une seule correspondance, qui va du premier guillemet du texte jusqu'au dernier ! Tout ça parce que la recherche s'est faite en mode gourmand, c'est-à-dire en cherchant le plus long groupe de caractères correspondant au motif défini par l'expression régulière. En revanche, l'expression '".+?"' donnera le résultat attendu, parce que la recherche se fera en mode non gourmand.

Soit dit en passant, une autre façon (peut-être plus recommandable) de résoudre ce problème serait de remplacer le point, qui signifie "un caractère quelconque", par une classe de caractères plus restreinte, par exemple la classe de tous les caractères sauf le guillemet :

1. '"[^"]+"'

III - Usage des expressions régulières

Il est temps de faire connaissance avec l'unité RegExpr de Free Pascal. Cette unité met à notre disposition des fonctions permettant d'utiliser indirectement la classe TRegExpr. Cependant, pour réaliser certaines opérations, nous aurons besoin de faire directement appel aux méthodes de la classe TRegExpr.

III-A - Fonction ExecRegExpr

Le plus simple usage qu'on puisse faire d'une expression régulière est de vérifier qu'une chaîne de caractères donnée ou l'une de ses parties appartient à l'ensemble décrit par l'expression régulière.

C'est ce que fait la fonction ExecRegExpr.

1. program ExecRegExpr1;

3. {$ASSERTIONS ON}

5. uses

6. SysUtils, RegExpr;

8. const

9. S = '14/05/2016';

11. begin

12. Assert(ExecRegExpr('\d+', S));

13. Assert(ExecRegExpr('^\d+$', S) = FALSE);

14. Assert(ExecRegExpr('^\d+$', Copy(S, 1, 2)));

15. end.

C'est le lieu de vous parler du rôle des caractères '^' et '$'.

Dans l'exemple de code ci-dessus, la fonction ExecRegExpr renvoie, la première fois et aussi la troisième fois qu'elle est appelée, la valeur TRUE. Pourquoi ? Parce qu'il y a quelque part dans la chaîne S (à savoir au début) une séquence de caractères correspondant à l'expression '\d+' qui signifie "un chiffre ou plus". Si l'on veut s'assurer que la chaîne S en entier correspond à ce motif, il faut entourer l'expression des caractères '^' et '$' qui signifient respectivement "début de la chaîne" et "fin de la chaîne". Et dans ce cas, on peut constater que la chaîne S ne correspond pas au motif. Mais les deux premiers caractères de la chaîne, en revanche, y correspondent.

Au fait, si vous n'êtes pas familier de la procédure Assert, il faut que je vous en dise un mot. La procédure Assert reçoit comme unique argument une expression booléenne qui doit être vraie, sans quoi une exception est déclenchée et le programme s'arrête. Donc le programme ci-dessus, comme la plupart des suivants, n'affiche aucun résultat : si le programme se termine normalement, c'est que toutes les assertions ont réussi (ce qui devrait être le cas). La directive {$ASSERTIONS ON} sert à activer les assertions (autrement les lignes en question seraient ignorées par le compilateur).

III-B - Méthode Exec

Voyons maintenant comment réaliser la même opération (vérifier qu'une chaîne de caractères donnée ou l'une de ses parties appartient à l'ensemble décrit par l'expression régulière) en faisant directement appel aux méthodes de la classe TRegExpr, et notamment à la méthode Exec.

1. program Exec1;

3. {$ASSERTIONS ON}

5. uses

6. SysUtils, RegExpr;

8. var

9. expr: TRegExpr;

11. begin

12. expr := TRegExpr.Create;

13. expr.Expression := '^(abc|def)$';

14. Assert(expr.Exec('abc'));

15. expr.Free;

16. end.

Arrêtons-nous un instant sur l'expression '^(abc|def)$'. Nous connaissons déjà les caractères '^' et '$', qui signifient respectivement "début de la chaîne" et "fin de la chaîne". La barre verticale représente une alternative. L'expression '(abc|def)' signifie "ou les caractères abc, ou les caractères def". Les parenthèses servent à indiquer jusqu'où s'étend l'alternative.

L'exemple suivant montre l'intérêt de faire appel à la méthode Exec, plutôt qu'à la fonction ExecRegExpr.

1. program Match1;

3. {$ASSERTIONS ON}

5. uses

6. SysUtils, RegExpr;

8. var

9. expr: TRegExpr;

11. begin

12. expr := TRegExpr.Create('\d{2}/\d{2}/\d{4}');

14. if expr.Exec('_15/05/2016_') then

15. begin

16. Assert(expr.Match[0] = '15/05/2016');

17. Assert(expr.MatchPos[0] = 2);

18. Assert(expr.MatchLen[0] = 10);

19. end;

21. expr.Free;

22. end.

Non seulement nous avons pu savoir que la chaîne examinée contient une date au format spécifié, mais nous avons pu savoir quelle est cette date.

L'exemple suivant capture les parties numériques de la date.

1. program Match2;

3. {$ASSERTIONS ON}

5. uses

6. SysUtils, RegExpr;

8. var

9. expr: TRegExpr;

11. begin

12. expr := TRegExpr.Create('(\d{2})/(\d{2})/(\d{4})');

14. if expr.Exec('_15/05/2016_') then

15. begin

16. Assert(expr.SubExprMatchCount = 3);

17. Assert(expr.Match[1] = '15');

18. Assert(expr.Match[2] = '05');

19. Assert(expr.Match[3] = '2016');

20. end;

22. expr.Free;

23. end.

Comme vous le voyez, ce sont les parenthèses qui indiquent les parties à capturer.

III-C - Méthode ExecNext

Dans tous les exemples précédents, la chaîne à examiner ne contenait qu'une seule fois le motif recherché.

L'exemple suivant montre comment détecter les multiples occurrences d'un motif. C'est à cela que sert la fonction ExecNext.

1. program ExecNext1;

3. uses

4. SysUtils, RegExpr;

6. begin

7. with TRegExpr.Create('\s') do

8. try

9. if Exec(#9#10#13#32) then

10. repeat

11. WriteLn(MatchPos[0]);

12. until not ExecNext;

13. finally

14. Free;

15. end;

16. end.

Vous n'avez pas oublié la classe des espaces ? La chaîne examinée dans l'exemple en contient quatre différents.

III-D - Fonction ReplaceRegExpr

Nous avons appris à détecter des motifs et à capturer des groupes de caractères. Nous allons voir maintenant comment remplacer les motifs détectés.

C'est à cela que sert la fonction ReplaceRegExpr.

Dans l'exemple suivant, le motif (un caractère de la classe des espaces) est remplacé par une chaîne constante, en l'occurrence un trait d'union.

1. program ReplaceRegExpr1;

3. {$ASSERTIONS ON}

5. uses

6. SysUtils, RegExpr;

8. begin

9. Assert(ReplaceRegExpr('\s', #9#10#13#32, '-', FALSE) = '----');

10. end.

Mais le motif peut aussi être remplacé par une chaîne incluant les groupes de caractères capturés. Dans l'exemple suivant, on cherche deux mots séparés par un espace, et on les remplace par une chaîne dans laquelle les deux mots sont intervertis (mais toujours séparés par un espace).

1. program ReplaceRegExpr2;

3. {$ASSERTIONS ON}

5. uses

6. SysUtils, RegExpr;

8. begin

9. Assert(ReplaceRegExpr('(\w+) (\w+)', 'Bond James', '$2 $1', TRUE) = 'James Bond');

10. end.

Vous avez remarqué le rôle du caractère '$' suivi d'un chiffre. Vous avez peut-être remarqué aussi que nous avons passé la valeur TRUE comme dernier paramètre : précisément parce que nous voulions que les groupes de caractères capturés soient insérés, dans la chaîne de remplacement, aux endroits marqués par le caractère '$' suivi d'un chiffre. Si nous passons la valeur FALSE, ce caractère sera interprété littéralement.

III-E - Méthode Replace

Il y a encore une troisième sorte de remplacement, où le motif est remplacé par le résultat d'une fonction.

Pour ce faire, nous devons appeler la méthode Replace de la classe TRegExpr.

Dans l'exemple suivant, on recherche des chiffres de '1' à '8' et on les remplace par des traits d'union. Un trait d'union pour le chiffre '1', deux traits d'union pour le chiffre '2', etc.

1. program Replace1;

3. {$MODE DELPHI}

4. {$ASSERTIONS ON}

6. uses

7. Classes, SysUtils, RegExpr;

9. type

10. TDemoClass = class

11. public

12. function ReplaceWith(e: TRegExpr): string;

13. function ReplaceAll(const s: string): string;

14. end;

16. function TDemoClass.ReplaceWith(e: TRegExpr): string;

17. begin

18. result := StringOfChar('-', StrToInt(e.Match[0]));

19. end;

21. function TDemoClass.ReplaceAll(const s: string): string;

22. begin

23. with TRegExpr.Create do

24. begin

25. Expression := '[1-8]';

26. result := Replace(s, ReplaceWith);

27. Free;

28. end;

29. end;

31. const

32. S1 = 'rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R';

33. S2 = 'rnbqkbnr/pp-ppppp/--------/--p-----/----P---/-----N--/PPPP-PPP/RNBQKB-R';

35. begin

36. with TDemoClass.Create do

37. begin

38. Assert(ReplaceAll(S1) = S2);

39. Free;

40. end;

41. end.

L'exemple ci-dessus est écrit pour le mode Delphi. En mode ObjFPC, l'appel de la méthode Replace est légèrement différent :

1. {$MODE OBJFPC}{$H+}

2. { ... }

3. result := Replace(s, @ReplaceWith);

III-F - Fonction SplitRegExpr

Il y a encore une opération que nous pouvons effectuer : elle consiste à découper une chaîne de caractères en fonction d'un motif traité comme séparateur.

Dans l'exemple suivant, on traite comme séparateur une séquence de traits d'union et de barres obliques longue d'un caractère ou plus.

1. program SplitRegExpr1;

3. {$ASSERTIONS ON}

5. uses

6. SysUtils, Classes, RegExpr;

8. var

9. list: TStringList;

11. begin

12. list := TStringList.Create;

14. SplitRegExpr('[-/]+', 'A-B//C', list);

16. Assert(list.Count = 3);

18. Assert(list.Strings[0] = 'A');

19. Assert(list.Strings[1] = 'B');

20. Assert(list.Strings[2] = 'C');

22. list.Free;

23. end.

IV - Conclusion

J'ai essayé de faire tenir dans cet article tout ce qu'on a besoin de savoir pour utiliser les expressions régulières avec l'unité RegExpr. Il reste quelques petites choses dont je n'ai pas cru nécessaire de vous parler, mais que vous trouverez dans les exemples de code qui accompagnent l'article.

Je remercie gvasseur58 et Alcatîz pour la relecture technique, ainsi que genthial pour la relecture orthographique.