ABAP Regular Expressions: Buscar y Reemplazar Patrones

Kategorie
ABAP-Statements
Veröffentlicht
Autor
Johannes

Las Regular Expressions (Regex) permiten busqueda y reemplazo de patrones complejos en strings. ABAP soporta expresiones regulares compatibles con POSIX a traves de sentencias y clases.

Posibilidades de Uso

  1. FIND … REGEX - Buscar patron en string
  2. REPLACE … REGEX - Reemplazar patron
  3. matches() - Verificar si string corresponde al patron
  4. cl_abap_regex / cl_abap_matcher - API orientada a objetos

Resumen de Sintaxis Regex

CaracterSignificadoEjemplo
.Cualquier caractera.c -> abc, aXc
*0 o masab*c -> ac, abc, abbc
+1 o masab+c -> abc, abbc
?0 o 1ab?c -> ac, abc
^Inicio^Hello
$FinWorld$
[abc]Clase de caracteres[aeiou] -> Vocales
[^abc]Clase negada[^0-9] -> No-digitos
[a-z]Rango[A-Za-z] -> Letras
\dDigito [0-9]\d{4} -> 4 digitos
\wCaracter de palabra [a-zA-Z0-9_]\w+
\sEspacio en blanco\s+ -> Espacios
\bLimite de palabra\bword\b
{n}Exactamente n vecesa{3} -> aaa
{n,m}n a m vecesa{2,4} -> aa, aaa, aaaa
(...)Grupo(ab)+ -> ab, abab
|Ocat|dog

Ejemplos

1. FIND con REGEX

DATA: lv_text TYPE string VALUE 'Pedido 12345 del 2024-11-15'.
" Encontrar numero
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: / 'Encontrado:', lv_number. " 12345
ENDIF.

2. Encontrar Todas las Coincidencias (FIND ALL OCCURRENCES)

DATA: lv_text TYPE string VALUE 'Tel: 030-12345, Fax: 040-67890, Movil: 0170-9876543'.
" Encontrar todos los numeros de telefono
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: / 'Telefono:', lv_phone.
ENDLOOP.
" Salida:
" Telefono: 030-12345
" Telefono: 040-67890
" Telefono: 0170-9876543

3. Extraer Grupos (Submatches)

DATA: lv_date TYPE string VALUE 'Fecha: 2024-11-15'.
" Extraer fecha con grupos
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: / 'Ano:', lv_year. " 2024
WRITE: / 'Mes:', lv_month. " 11
WRITE: / 'Dia:', lv_day. " 15
ENDIF.

4. REPLACE con REGEX

DATA: lv_text TYPE string VALUE 'Precio: 123.45 EUR, Descuento: 10.00 EUR'.
" Reemplazar numeros con XXX
REPLACE ALL OCCURRENCES OF REGEX '\d+\.?\d*'
IN lv_text WITH 'XXX'.
WRITE: / lv_text. " Precio: XXX EUR, Descuento: XXX EUR

5. Referencias Hacia Atras

DATA: lv_text TYPE string VALUE 'El el gato esta sobre sobre el tejado.'.
" Eliminar palabras duplicadas (referencia hacia atras \1)
REPLACE ALL OCCURRENCES OF REGEX '\b(\w+)\s+\1\b'
IN lv_text WITH '$1'.
WRITE: / lv_text. " El gato esta sobre el tejado.

6. matches() - Verificar si Pattern Coincide

DATA: lv_email TYPE string VALUE '[email protected]'.
" Validacion simple de E-Mail
IF matches( val = lv_email regex = '^\w+@\w+\.\w+$' ).
WRITE: / 'E-Mail valido'.
ELSE.
WRITE: / 'E-Mail invalido'.
ENDIF.
" Multiples verificaciones
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() con Regex

DATA: lv_text TYPE string VALUE 'Pedido Nr. 12345 fue enviado'.
" Contiene numero?
IF contains( val = lv_text regex = '\d+' ).
WRITE: / 'Texto contiene numeros'.
ENDIF.
" Comienza con patron
IF contains( val = lv_text regex = '^Pedido' ).
WRITE: / 'Es un pedido'.
ENDIF.

8. count() con Regex

DATA: lv_text TYPE string VALUE 'a1b2c3d4e5'.
" Cantidad de digitos
DATA(lv_digit_count) = count( val = lv_text regex = '\d' ).
WRITE: / 'Cantidad de digitos:', lv_digit_count. " 5
" Cantidad de palabras
DATA: lv_sentence TYPE string VALUE 'Esta es una oracion de ejemplo con palabras'.
DATA(lv_word_count) = count( val = lv_sentence regex = '\b\w+\b' ).
WRITE: / 'Cantidad de palabras:', lv_word_count. " 8

9. cl_abap_regex - Orientado a Objetos

DATA: lv_text TYPE string VALUE 'Nombre: Max Mustermann, Edad: 30'.
" Crear objeto Regex
DATA(lo_regex) = cl_abap_regex=>create_pcre( pattern = '(\w+):\s*(\S+)' ).
" Crear Matcher
DATA(lo_matcher) = lo_regex->create_matcher( text = lv_text ).
" Recorrer todas las coincidencias
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: / 'Match:', lv_full.
WRITE: / ' Clave:', lv_key, 'Valor:', lv_value.
ENDWHILE.
" Salida:
" Match: Nombre: Max
" Clave: Nombre Valor: Max
" Match: Edad: 30
" Clave: Edad Valor: 30

10. Practico: Extraer E-Mail

DATA: lv_text TYPE string VALUE
DATA: lt_emails TYPE string_table.
" Encontrar todos los E-Mails
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.
" Salida:

11. Practico: Limpiar Datos

" Normalizar numero de telefono
DATA: lv_phone TYPE string VALUE '+49 (0) 170 / 123 45 67'.
" Eliminar todos los no-digitos excepto +
REPLACE ALL OCCURRENCES OF REGEX '[^\d+]' IN lv_phone WITH ''.
WRITE: / lv_phone. " +491701234567
" Reducir multiples espacios
DATA: lv_text TYPE string VALUE 'Demasiados espacios aqui'.
REPLACE ALL OCCURRENCES OF REGEX '\s{2,}' IN lv_text WITH ' '.
WRITE: / lv_text. " Demasiados espacios aqui

12. Practico: Validaciones

" Validar IBAN (simplificado)
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: / 'Formato IBAN valido'.
ENDIF.
" Validar codigo postal (Espana)
DATA: lv_cp TYPE string VALUE '28001'.
IF matches( val = lv_cp regex = '^\d{5}$' ).
WRITE: / 'Codigo postal espanol valido'.
ENDIF.
" Validar fecha (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: / 'Formato de fecha valido'.
ENDIF.

13. Practico: Parsing

" Parsear linea 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: / 'Fecha:', lv_date.
WRITE: / 'Hora:', lv_time.
WRITE: / 'Nivel:', lv_level.
WRITE: / 'Mensaje:', lv_message.
ENDIF.

14. Practico: Parsing CSV

DATA: lv_csv TYPE string VALUE 'Max;Mustermann;30;Berlin'.
DATA: lt_fields TYPE string_table.
" Split por punto y coma (alternativa a 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.
" O mas simple con SPLIT:
SPLIT lv_csv AT ';' INTO TABLE lt_fields.

15. Busqueda Case-Insensitive

DATA: lv_text TYPE string VALUE 'ABAP es genial, abap es fantastico'.
" Case-insensitive con (?i)
FIND ALL OCCURRENCES OF REGEX '(?i)abap'
IN lv_text
MATCH COUNT DATA(lv_count).
WRITE: / 'Encontrado:', lv_count, 'veces'. " 2

16. Funcion Escape

DATA: lv_search TYPE string VALUE 'a.b*c?'.
" Escapar caracteres especiales para busqueda literal
DATA(lv_escaped) = escape( val = lv_search format = cl_abap_format=>e_regex ).
WRITE: / 'Escaped:', lv_escaped. " a\.b\*c\?
" Ahora lv_escaped puede usarse en REGEX
DATA: lv_text TYPE string VALUE 'Test a.b*c? Final'.
FIND REGEX lv_escaped IN lv_text.
IF sy-subrc = 0.
WRITE: / 'Encontrado!'.
ENDIF.

Patrones Regex Comunes

PropositoPattern
Numero\d+ o [0-9]+
Numero decimal\d+\.?\d*
Palabra\w+ o [A-Za-z]+
E-Mail (simple)[\w.+-]+@[\w.-]+\.\w{2,}
URLhttps?://[\w./%-]+
Fecha (YYYY-MM-DD)\d{4}-\d{2}-\d{2}
CP (ES)\d{5}
Telefono (ES)(\+34|0)\d{2,4}[-/]?\d+
Direccion IP\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}
Eliminar espacios\s+ -> “
Tags HTML<[^>]+>

Opciones FIND/REPLACE

FIND REGEX pattern IN text
[ IGNORING CASE ] " Ignorar mayusculas/minusculas
[ MATCH OFFSET off ] " Posicion inicial de la coincidencia
[ MATCH LENGTH len ] " Longitud de la coincidencia
[ MATCH COUNT cnt ] " Cantidad de coincidencias
[ SUBMATCHES s1 s2 ... ] " Contenidos de grupos
[ RESULTS result_tab ]. " Todas las coincidencias como tabla

Notas Importantes / Mejores Practicas

  • Sintaxis PCRE (compatible con Perl) con cl_abap_regex=>create_pcre().
  • Regex ABAP estandar es compatible con POSIX.
  • Usar escape() para escapar caracteres especiales.
  • Submatches con (...) para extraccion de grupos.
  • (?i) al inicio para busqueda case-insensitive.
  • Performance: Regex compilado (cl_abap_regex) para uso multiple.
  • matches() verifica si el string completo corresponde al patron.
  • contains( ... regex = ...) verifica si el patron esta contenido.
  • \d, \w, \s son formas cortas para clases de caracteres.
  • Pruebe Regex con herramientas online (regex101.com) antes de la implementacion.