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é :
'\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' ?
'[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' :
'[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 :
'"[^"]+"'
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
program
ExecRegExpr1;
{$ASSERTIONS ON}
uses
SysUtils, RegExpr;
const
S = '14/05/2016'
;
begin
Assert(ExecRegExpr('\d+'
, S));
Assert(ExecRegExpr('^\d+$'
, S) = FALSE
);
Assert(ExecRegExpr('^\d+$'
, Copy(S, 1
, 2
)));
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
program
Exec1;
{$ASSERTIONS ON}
uses
SysUtils, RegExpr;
var
expr: TRegExpr;
begin
expr := TRegExpr.Create;
expr.Expression := '^(abc|def)$'
;
Assert(expr.Exec('abc'
));
expr.Free;
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
program
Match1;
{$ASSERTIONS ON}
uses
SysUtils, RegExpr;
var
expr: TRegExpr;
begin
expr := TRegExpr.Create('\d{2}/\d{2}/\d{4}'
);
if
expr.Exec('_15/05/2016_'
) then
begin
Assert(expr.Match[0
] = '15/05/2016'
);
Assert(expr.MatchPos[0
] = 2
);
Assert(expr.MatchLen[0
] = 10
);
end
;
expr.Free;
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
program
Match2;
{$ASSERTIONS ON}
uses
SysUtils, RegExpr;
var
expr: TRegExpr;
begin
expr := TRegExpr.Create('(\d{2})/(\d{2})/(\d{4})'
);
if
expr.Exec('_15/05/2016_'
) then
begin
Assert(expr.SubExprMatchCount = 3
);
Assert(expr.Match[1
] = '15'
);
Assert(expr.Match[2
] = '05'
);
Assert(expr.Match[3
] = '2016'
);
end
;
expr.Free;
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
program
ExecNext1;
uses
SysUtils, RegExpr;
begin
with
TRegExpr.Create('\s'
) do
try
if
Exec(#9#10#13#32
) then
repeat
WriteLn(MatchPos[0
]);
until
not
ExecNext;
finally
Free;
end
;
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
program
ReplaceRegExpr1;
{$ASSERTIONS ON}
uses
SysUtils, RegExpr;
begin
Assert(ReplaceRegExpr('\s'
, #9#10#13#32
, '-'
, FALSE
) = '----'
);
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).
2.
3.
4.
5.
6.
7.
8.
9.
10.
program
ReplaceRegExpr2;
{$ASSERTIONS ON}
uses
SysUtils, RegExpr;
begin
Assert(ReplaceRegExpr('(\w+) (\w+)'
, 'Bond James'
, '$2 $1'
, TRUE
) = 'James Bond'
);
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
24.
25.
26.
27.
28.
29.
30.
31.
32.
33.
34.
35.
36.
37.
38.
39.
40.
41.
program
Replace1;
{$MODE DELPHI}
{$ASSERTIONS ON}
uses
Classes, SysUtils, RegExpr;
type
TDemoClass = class
public
function
ReplaceWith(e: TRegExpr): string
;
function
ReplaceAll(const
s: string
): string
;
end
;
function
TDemoClass.ReplaceWith(e: TRegExpr): string
;
begin
result := StringOfChar('-'
, StrToInt(e.Match[0
]));
end
;
function
TDemoClass.ReplaceAll(const
s: string
): string
;
begin
with
TRegExpr.Create do
begin
Expression := '[1-8]'
;
result := Replace(s, ReplaceWith);
Free;
end
;
end
;
const
S1 = 'rnbqkbnr/pp1ppppp/8/2p5/4P3/5N2/PPPP1PPP/RNBQKB1R'
;
S2 = 'rnbqkbnr/pp-ppppp/--------/--p-----/----P---/-----N--/PPPP-PPP/RNBQKB-R'
;
begin
with
TDemoClass.Create do
begin
Assert(ReplaceAll(S1) = S2);
Free;
end
;
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 :
2.
3.
{$MODE OBJFPC}{$H+}
{ ... }
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.
2.
3.
4.
5.
6.
7.
8.
9.
10.
11.
12.
13.
14.
15.
16.
17.
18.
19.
20.
21.
22.
23.
program
SplitRegExpr1;
{$ASSERTIONS ON}
uses
SysUtils, Classes, RegExpr;
var
list: TStringList;
begin
list := TStringList.Create;
SplitRegExpr('[-/]+'
, 'A-B//C'
, list);
Assert(list.Count = 3
);
Assert(list.Strings[0
] = 'A'
);
Assert(list.Strings[1
] = 'B'
);
Assert(list.Strings[2
] = 'C'
);
list.Free;
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.