Expressions régulières ABAP : Rechercher et remplacer des motifs

Catégorie
ABAP-Statements
Publié
Auteur
Johannes

Les expressions régulières (Regex) permettent une recherche et un remplacement de motifs complexes dans les chaînes. ABAP supporte les expressions régulières compatibles POSIX via des instructions et des classes.

Possibilités d’utilisation

  1. FIND … REGEX – Rechercher un motif dans une chaîne
  2. REPLACE … REGEX – Remplacer un motif
  3. matches() – Vérifier si une chaîne correspond au motif
  4. cl_abap_regex / cl_abap_matcher – API orientée objet

Aperçu de la syntaxe Regex

CaractèreSignificationExemple
.N’importe quel caractèrea.c → abc, aXc
*0 ou plusab*c → ac, abc, abbc
+1 ou plusab+c → abc, abbc
?0 ou 1ab?c → ac, abc
^Début^Hello
$FinWorld$
[abc]Classe de caractères[aeiou] → Voyelles
[^abc]Classe niée[^0-9] → Non-chiffres
[a-z]Plage[A-Za-z] → Lettres
\dChiffre [0-9]\d{4} → 4 chiffres
\wCaractère de mot [a-zA-Z0-9_]\w+
\sEspace blanc\s+ → Espaces
\bLimite de mot\bword\b
{n}Exactement n foisa{3} → aaa
{n,m}n à m foisa{2,4} → aa, aaa, aaaa
(...)Groupe(ab)+ → ab, abab
|Oucat|dog

Exemples

1. FIND avec REGEX

DATA: lv_text TYPE string VALUE 'Bestellung 12345 vom 2024-11-15'.
" Trouver un nombre
FIND REGEX '\d+' IN lv_text MATCH OFFSET DATA(lv_offset)
MATCH LENGTH DATA(lv_length).
IF sy-subrc = 0.
DATA(lv_number) = substring( val = lv_text off = lv_offset len = lv_length ).
WRITE: / 'Trouvé:', lv_number. " 12345
ENDIF.

2. Trouver toutes les occurrences (FIND ALL OCCURRENCES)

DATA: lv_text TYPE string VALUE 'Tel: 030-12345, Fax: 040-67890, Mobil: 0170-9876543'.
" Trouver tous les numéros de téléphone
FIND ALL OCCURRENCES OF REGEX '\d{3,4}-\d+"
IN lv_text
RESULTS DATA(lt_results).
LOOP AT lt_results INTO DATA(ls_result).
DATA(lv_phone) = substring( val = lv_text
off = ls_result-offset
len = ls_result-length ).
WRITE: / 'Téléphone:', lv_phone.
ENDLOOP.
" Sortie:
" Téléphone: 030-12345
" Téléphone: 040-67890
" Téléphone: 0170-9876543

3. Extraire des groupes (Submatches)

DATA: lv_date TYPE string VALUE 'Datum: 2024-11-15'.
" Extraire la date avec des groupes
FIND REGEX '(\d{4})-(\d{2})-(\d{2})"
IN lv_date
SUBMATCHES DATA(lv_year) DATA(lv_month) DATA(lv_day).
IF sy-subrc = 0.
WRITE: / 'Année:', lv_year. " 2024
WRITE: / 'Mois:', lv_month. " 11
WRITE: / 'Jour:', lv_day. " 15
ENDIF.

4. REPLACE avec REGEX

DATA: lv_text TYPE string VALUE 'Preis: 123.45 EUR, Rabatt: 10.00 EUR'.
" Remplacer les nombres par XXX
REPLACE ALL OCCURRENCES OF REGEX '\d+\.?\d*"
IN lv_text WITH 'XXX'.
WRITE: / lv_text. " Preis: XXX EUR, Rabatt: XXX EUR

5. Références arrière

DATA: lv_text TYPE string VALUE 'Die die Katze sitzt auf auf dem Dach.'.
" Supprimer les mots en double (référence arrière \1)
REPLACE ALL OCCURRENCES OF REGEX '\b(\w+)\s+\1\b"
IN lv_text WITH '$1'.
WRITE: / lv_text. " Die Katze sitzt auf dem Dach.

6. matches() – Vérifier si le motif correspond

DATA: lv_email TYPE string VALUE '[email protected]'.
" Validation simple d'email
IF matches( val = lv_email regex = '^\w+@\w+\.\w+$' ).
WRITE: / 'Email valide'.
ELSE.
WRITE: / 'Email invalide'.
ENDIF.
" Plusieurs vérifications
DATA: lv_phone TYPE string VALUE '+49-170-1234567'.
DATA(lv_valid_phone) = xsdbool(
matches( val = lv_phone regex = '^\+?\d{2,3}-\d{2,4}-\d{4,}$' )
).

7. contains() avec Regex

DATA: lv_text TYPE string VALUE 'Bestellung Nr. 12345 wurde versandt'.
" Contient un nombre ?
IF contains( val = lv_text regex = '\d+' ).
WRITE: / 'Le texte contient des nombres'.
ENDIF.
" Commence par un motif
IF contains( val = lv_text regex = '^Bestellung' ).
WRITE: / 'C''est une commande'.
ENDIF.

8. count() avec Regex

DATA: lv_text TYPE string VALUE 'a1b2c3d4e5'.
" Nombre de chiffres
DATA(lv_digit_count) = count( val = lv_text regex = '\d' ).
WRITE: / 'Nombre de chiffres:', lv_digit_count. " 5
" Nombre de mots
DATA: lv_sentence TYPE string VALUE 'Dies ist ein Beispielsatz mit Wörtern'.
DATA(lv_word_count) = count( val = lv_sentence regex = '\b\w+\b' ).
WRITE: / 'Nombre de mots:', lv_word_count. " 6

9. cl_abap_regex – Orienté objet

DATA: lv_text TYPE string VALUE 'Name: Max Mustermann, Alter: 30'.
" Créer un objet Regex
DATA(lo_regex) = cl_abap_regex=>create_pcre( pattern = '(\w+):\s*(\S+)' ).
" Créer un matcher
DATA(lo_matcher) = lo_regex->create_matcher( text = lv_text ).
" Parcourir toutes les correspondances
WHILE lo_matcher->find_next( ).
DATA(lv_full) = lo_matcher->get_match( ).
DATA(lv_key) = lo_matcher->get_submatch( 1 ).
DATA(lv_value) = lo_matcher->get_submatch( 2 ).
WRITE: / 'Correspondance:', lv_full.
WRITE: / ' Clé:', lv_key, 'Valeur:', lv_value.
ENDWHILE.
" Sortie:
" Correspondance: Name: Max
" Clé: Name Valeur: Max
" Correspondance: Alter: 30
" Clé: Alter Valeur: 30

10. Pratique : Extraire des emails

DATA: lv_text TYPE string VALUE
DATA: lt_emails TYPE string_table.
" Trouver tous les emails
FIND ALL OCCURRENCES OF REGEX '[\w.+-]+@[\w.-]+\.\w{2,}"
IN lv_text
RESULTS DATA(lt_results).
LOOP AT lt_results INTO DATA(ls_result).
APPEND substring( val = lv_text
off = ls_result-offset
len = ls_result-length ) TO lt_emails.
ENDLOOP.
LOOP AT lt_emails INTO DATA(lv_email).
WRITE: / lv_email.
ENDLOOP.
" Sortie:

11. Pratique : Nettoyer les données

" Normaliser un numéro de téléphone
DATA: lv_phone TYPE string VALUE '+49 (0) 170 / 123 45 67'.
" Supprimer tous les non-chiffres sauf +
REPLACE ALL OCCURRENCES OF REGEX '[^\d+]' IN lv_phone WITH ''.
WRITE: / lv_phone. " +491701234567
" Réduire les espaces multiples
DATA: lv_text TYPE string VALUE 'Zu viele Leerzeichen hier'.
REPLACE ALL OCCURRENCES OF REGEX '\s{2,}' IN lv_text WITH ' '.
WRITE: / lv_text. " Zu viele Leerzeichen hier

12. Pratique : Validations

" Valider un IBAN (simplifié)
DATA: lv_iban TYPE string VALUE 'DE89370400440532013000'.
IF matches( val = lv_iban regex = '^[A-Z]{2}\d{2}[A-Z0-9]{4}\d{7}([A-Z0-9]?){0,16}$' ).
WRITE: / 'Format IBAN valide'.
ENDIF.
" Valider un code postal (Allemagne)
DATA: lv_plz TYPE string VALUE '12345'.
IF matches( val = lv_plz regex = '^\d{5}$' ).
WRITE: / 'Code postal allemand valide'.
ENDIF.
" Valider une date (YYYY-MM-DD)
DATA: lv_date TYPE string VALUE '2024-11-15'.
IF matches( val = lv_date regex = '^\d{4}-(0[1-9]|1[0-2])-(0[1-9]|[12]\d|3[01])$' ).
WRITE: / 'Format de date valide'.
ENDIF.

13. Pratique : Parsing

" Parser une ligne de log
DATA: lv_log TYPE string VALUE '2024-11-15 10:30:45 [ERROR] Database connection failed'.
FIND REGEX '^(\d{4}-\d{2}-\d{2})\s+(\d{2}:\d{2}:\d{2})\s+\[(\w+)\]\s+(.+)$"
IN lv_log
SUBMATCHES DATA(lv_date) DATA(lv_time) DATA(lv_level) DATA(lv_message).
IF sy-subrc = 0.
WRITE: / 'Date:', lv_date.
WRITE: / 'Heure:', lv_time.
WRITE: / 'Niveau:', lv_level.
WRITE: / 'Message:', lv_message.
ENDIF.

14. Pratique : Parsing CSV

DATA: lv_csv TYPE string VALUE 'Max;Mustermann;30;Berlin'.
DATA: lt_fields TYPE string_table.
" Split par point-virgule (alternative à SPLIT)
FIND ALL OCCURRENCES OF REGEX '[^;]+' IN lv_csv RESULTS DATA(lt_matches).
LOOP AT lt_matches INTO DATA(ls_match).
APPEND substring( val = lv_csv off = ls_match-offset len = ls_match-length )
TO lt_fields.
ENDLOOP.
" Ou plus simple avec SPLIT:
SPLIT lv_csv AT ';' INTO TABLE lt_fields.

15. Recherche insensible à la casse

DATA: lv_text TYPE string VALUE 'ABAP ist super, abap ist toll'.
" Insensible à la casse avec (?i)
FIND ALL OCCURRENCES OF REGEX '(?i)abap"
IN lv_text
MATCH COUNT DATA(lv_count).
WRITE: / 'Trouvé:', lv_count, 'fois'. " 2

16. Fonction d’échappement

DATA: lv_search TYPE string VALUE 'a.b*c?'.
" Échapper les caractères spéciaux pour une recherche littérale
DATA(lv_escaped) = escape( val = lv_search format = cl_abap_format=>e_regex ).
WRITE: / 'Échappé:', lv_escaped. " a\.b\*c\?
" Maintenant lv_escaped peut être utilisé dans REGEX
DATA: lv_text TYPE string VALUE 'Test a.b*c? Ende'.
FIND REGEX lv_escaped IN lv_text.
IF sy-subrc = 0.
WRITE: / 'Trouvé!'.
ENDIF.

Motifs Regex courants

ObjectifMotif
Nombre\d+ ou [0-9]+
Nombre décimal\d+\.?\d*
Mot\w+ ou [A-Za-z]+
Email (simple)[\w.+-]+@[\w.-]+\.\w{2,}
URLhttps?://[\w./%-]+
Date (YYYY-MM-DD)\d{4}-\d{2}-\d{2}
Code postal (DE)\d{5}
Téléphone (DE)(\+49|0)\d{2,4}[-/]?\d+
Adresse IP\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
Supprimer espaces\s+ → “
Balises HTML<[^>]+>

Options FIND/REPLACE

FIND REGEX pattern IN text
[ IGNORING CASE ] " Ignorer majuscules/minuscules
[ MATCH OFFSET off ] " Position de début de la correspondance
[ MATCH LENGTH len ] " Longueur de la correspondance
[ MATCH COUNT cnt ] " Nombre de correspondances
[ SUBMATCHES s1 s2 ... ] " Contenus des groupes
[ RESULTS result_tab ]. " Toutes les correspondances sous forme de table

Remarques importantes / Bonnes pratiques

  • Syntaxe PCRE (compatible Perl) avec cl_abap_regex=>create_pcre().
  • Le Regex ABAP standard est compatible POSIX.
  • Utilisez escape() pour échapper les caractères spéciaux.
  • Submatches avec (...) pour l’extraction de groupes.
  • (?i) au début pour une recherche insensible à la casse.
  • Performance: Regex compilée (cl_abap_regex) pour utilisation multiple.
  • matches() vérifie si la chaîne entière correspond au motif.
  • contains( ... regex = ...) vérifie si le motif est contenu.
  • \d, \w, \s sont des formes courtes pour les classes de caractères.
  • Testez les Regex avec des outils en ligne (regex101.com) avant l’implémentation.