Expresiones regulares con la unit RegularExpressions


I. Introducción


Se llama
expresión regular a una cadena de caracteres que representa un conjunto de
cadenas de caracteres.


Así la
cadena 'abc', considerado como una expresión regular, representa un conjunto
que contiene un solo elemento, la cadena 'abc'. Cada uno de los tres
caracteres está representando a sí mismo.


I - A. Clases de caracteres


La expresión regular '[abc]' representa el conjunto de las cadenas que forman un solo carácter en el conjunto ['a', 'b', 'c']. Me gustaría
añadir que los tres caracteres 'a', 'b' y 'c' deben ser consecutivos en el
alfabeto, se podría escribir nuestra expresión regular '[a-c]', así como en
Pascal, podría escribir['a'..'c'] en vez de ['a', 'b', 'c'].



A diferencia
de las letras del alfabeto que se representan simplemente a ellas mismas, los corchetes y el
guión son unos caracteres con un significado especial, que nosotros veremos posteriormente. Para que estos caracteres especiales (llamados también metacaracteres) sean tratados como caracteres normales, es decir interpretados literalmente, deben ser caracteres de "escape", prefijando una barra invertida: '\[', '\]', '\-'. La
barra invertida por lo tanto también es un metacarácter.





Otro
carácter especial del que es necesario hablar en este lugar, es el acento
circunflejo. Cuando se coloca inmediatamente después de un corchete abierto, significa la negación o exclusión. Por ejemplo la clase '[^0-9]' contiene todos los caracteres que no son números.

I B. Clases predefinidas





La barra oblicua invertida se usa también para dar un significado especial a caracteres que
por defecto se interpretan literalmente.


Por ejemplo
la letra 'd', precedida por una barra invertida, representa la clase de dígitos . Las tres expresiones son por lo
tanto similares: '[0123456789]', '[0-9]', '\d'.


Un usuario
advertido sobre las expresiones regulares en Perl me señaló me que en recientes
versiones de Perl, estas tres expresiones no serán sinónimas, porque la clase
'\d' incluye ahora las figuras de todos los alfabetos y no sólo los del
alfabeto latino.


Aquí hay una
tabla que contiene todas las clases predefinidas.










































































Valorar


Significado


Calificación
equivalente


\a


Campana


\x07


\d


Figura


[0-9]


\e


De escape


\x1B


\f


Nueva hoja de
cálculo


\x0C


\n


Nueva línea
de


\0A


\r


Retorno de
carro


\0D


\s


Espacio (en
un sentido amplio)


[ \t]


\t


Ficha


\x09


\v


Pestaña
vertical


\x0B


\w


Caracteres
alfanuméricos (incluyendo '_')


[A-Za-z0-9_]


.


Todos los
personajes (excepto #13 y #10)


[^ \r\n]










Tenga en
cuenta que si sustituimos 'a', 'd', 'e' o una de las otras letras por una mayúscula,
la expresión designa entonces juntos los caracteres que no son una cifra, etc.. Por lo tanto la clase '\D' es equivalene a la clase [^
0-9]' que vimos anteriormente.


El punto es una clase de caracteres a utilizar con precaución: contiene todos los caracteres,
a excepción del retorno de carro (#13) y salto
de línea
 (#10).


La barra
invertida se usa todavía para formar la expresión '\b' que significa, no una
clase de caracteres, pero al principio o al final de una palabra, se entiende como
secuencia de caracteres alfanuméricos. Esto es lo que se llama un ancla.





I C. Cuantificadores


Puesto que
ahora sabemos como designar conjuntos de caracteres, vea cómo tener en cuenta
el número de caracteres que se espera. Hemos visto, que por defecto es el número uno.





Para indicar
otro número, o incluso un intervalo, se puede utilizar apoyos, pero también los
caracteres '+', '*',  y '?'. Las llaves permiten indicar un número exacto de
caracteres o un intervalo. El símbolo '+' significa una vez o varias. La
estrella significa "cero veces o más. El signo de interrogación
significa "cero o uno".





{Por
ejemplo, la expresión '\d{2}' significa "dos dígitos";  '\d{2,4}' significa "de dos a cuatro dígitos" ; '\d{2,}' significa " dos o más dígitos". La expresión  '\d+'  significa "una cifra o más."   ; '\d*' significa "cero dígitos o
más" ; '\d?' significa "cero cifras o uno".





Por defecto,
los operadores '+', '*', y '?' son glotones,
es decir, la búsqueda devuelve no la primera coincidencia encontrada, sino la más larga. Esto a menudo es importante. Para cambiar este
comportamiento, es decir, para hacerlos perezosos (lazy), los
operadores debe ser seguidos de un signo de interrogación: '+?', '*?', '??'.





I D. Otros operadores


Los paréntesis
también son caracteres igualmente especiales: sirven para delimitar grupos de caracteres,
por ejemplo para aplicar cuantificadores. Así la expresión '(abc)?def' señala
un conjunto que contiene dos elementos, las cadenas 'abcdef' y 'def'. La
interrogación hace que al grupo 'abc' opcional.


Paréntesis
pueden también ser utilizados en combinación con la barra vertical, que
significa "o". Por ejemplo la expresión 'a(b|c)d' y señala un
conjunto que contiene las cadenas 'abd' y 'acd'.


Por último,
el carácter '^'  y '$' significan respectivamente el principio y el final de la cadena. Vamos a ver en un momento cuál es el propósito de estos
caracteres. Pero sí, tenéis razón, esto hace que tengas dos diferentes
significados para el acento circunflejo. Dependiendo del lugar donde lo
pones, no se interpretará de la misma manera, y esto se aplica también a otros
caractaeres.





II. comprobar que una cadena es parte de un conjunto


Ahora es el
momento para familiarizarse con la unit RegularExpressions. Esta
unit fue creado con Delphi XE, nos permitirá realizar diversas operaciones
sobre cadenas de caracteres.


La más
simple de estas operaciones consiste en determinar si dicha cadena
pertenece a un conjunto.





II - A. Función IsMatch


La función
que necesitamos para esto es el método IsMatch. Puede ser llamada directamente, con dos cadenas de caracteres como argumentos: el sujeto (la cadena de
estudio) y la expresión regular.





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





Como han
notado si vieron de cerca el código anterior, la función IsMatch comprueba
sólo que al menos una parte de la cadena pasada como primer
argumento pertenece al conjunto representado por la expresión regular.


Si se quiere
asegurar de que la cadena entera pertenece al conjunto, se debe utilizar los
operadores '^' y '$', que significan respectivamente el inicio de la secuencia y
su final.











WriteLn(TRegEx.IsMatch('bonjour', '\w')); // TRUE


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







En  'bonjour',
hay varios caracteres alfanuméricos, pero no hay mas que un solo carácter
entre el principio y el fin de la cadena.





III. Extracto de un grupos de cadena de caracteres


III - A. Ejemplo


Imaginemos
que queremos, no sólo verificar que una cadena dada pertenece a un
conjunto, sino también extraer ciertos grupos de caracteres de esta cadena. Decir
por ejemplo queremos extraer grupos de dígitos de una cadena que contiene una
fecha como '26/09/2015'.








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





Hemos
utilizado en nuestra expresión regular unos paréntesis para delimitar los grupos de
caracteres a extraer, después hemos usado la función Match. De cualquier
manera, no hemos verificado que las cifras extraídas eran de hecho una fecha válida. La expresión utilizada no tiene en cuenta de hecho que no hay más que 31 días en el mes, 12 meses
en un año, etc.. Una auditoría completa debería tomar en cuenta los
años bisiestos, pero esto es improbable que esto sea alcanzable a través de
expresiones regulares !





III - B. Grupos con nombre


También
podemos nombrar los grupos. Para nombrar un grupo, debe insertarse justo
después del paréntesis abierto en los caracteres '?'.











program Group2;




{$APPTYPE CONSOLE}

uses

SysUtils, RegularExpressions;

const

SUBJECT = '26/09/2015';

PATTERN = '(' + '?' + '\d{2})/(' + '?' + '\d{2})/(' + '?' + '\d{4})';



var

group: TGroup;

match: TMatch;

begin

regEx: TRegEx;



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

if match.Success then

match := regEx.Match(SUBJECT);



begin

group := match.Groups['year'];


WriteLn(group.Value);


end;


ReadLn;


end.












Image non disponible


III - C. Paréntesis no subcadenas


A veces es
necesario delimitar un grupo de caracteres, pero no es necesario capturar
los caracteres correspondientes. En este caso, para no dar trabajo innecesario
al programa, vamos a utilizar paréntesis no capturanes.


Imaginemos por
ejemplo que desea detectar en un código fuente en Pascal los Write y WriteLn,
sin ninguna diferencia entre los dos. Se podría usar la expresión 'Write(Ln)?'. Pero de esta forma se capturaría cada vez o una cadena vacía o la cadena
'Ln'. Sin embargo suponemos que no se quiera tener en cuenta la diferencia entre Write y WriteLn. Se utilizará
la siguiente expresión:





'Write(?:Ln)?'





Los
caracteres '?:' se añadieron inmediatamente después de la apertura del paréntesis para hacer paréntesis no de captura.





IV. la detección de múltiples partidos


Es posible
detectar múltiples grupos de caracteres que, en una cadena dada, pertenecen al
mismo conjunto.Para ello necesitamos las funciones  Match, Success y NextMatch.





IV - A. La función NextMatch


En el
ejemplo siguiente se detecta grupos de caracteres que representan números.








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. Tipo TMatchCollection


La misma operación se puede hacer de una manera diferente: a través de la función Matches,
que devuelve un resultado de tipo TMatchCollection.








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





V. ruptura de una cadena


Descubre
ahora cómo romper una cadena mediante una expresión regular, es decir, cortar según una cierta regla y disponer los trozos en una tabla.





V. Ejemplo


La expresión
representa a un grupo de personajes que se tratan como delimitadores. En
el caso más simple, se trata de un solo carácter:


 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





VI. sustitución de grupos de caracteres


La siguiente
operación es un poco similar el anterior: en lugar de tratar a ciertos grupos de
caracteres como delimitadores, los reemplazaremos por medio de la expresión Replace.





VI - A. Reemplazo por una cadena constante








program Replace1;




{$APPTYPE CONSOLE}

uses

RegularExpressions;

begin

WriteLn(TRegEx.Replace('WRITELN writeln', 'writeln', 'WriteLn', [roIgnoreCase]));

end.

ReadLn;





Image non disponible





Como se
puede ver, la función Replace acepta cuatro parámetros: la cadena a tratar, la expresión regular que representa a los a grupos de sustituir, la
cadena por la que se sustituirá estos grupos, y finalmente un conjunto de
opciones. En el ejemplo anterior, se utilizó la opción que permite obtener una sustitución que no tiene en cuenta el caso de grupos a sustituir .


VI - B. Reemplazo por una cadena de compuestos


En lugar de reemplazar los grupos de cadena constante, se puede reemplazar por una cadena variable, compuesta de elementos de los grupos detectados.








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








En la cadena
'\2 \1', la barra invertida seguida por un dígito representa el elemento
capturado que deberá insertarse en este lugar en la cadena de reemplazo. Así,
'\1' significa "primer elemento capturado".


Práctico,
¿no? Pero eso no es todo. La cadena de reemplazo puede ser incluso el resultado de una función que recibe como argumentos los elementos
capturados.





VI - C. Reemplazo por el resultado de una función


Supongamos
que queremos validar una cadena que representa una determinada posición de una
partida de ajedrez. La cadena debe ajustarse a la notación estándar, es
decir, notación FEN (Forsyth-Edwards Notation). Aquí la
cadena FEN correspondiente a la posición inicial de una pieza de ajedrez:




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




Como se ve,
la ocupación de las líneas del tablero está representada por unos grupos de
caracteres que contienen letras (las piezas)
y unos números (el número de casillas vacías consecutivas). Debemos asegurarnos de que el número total de
casillas por cada fila del tablero sea igual a ocho, y para ello que
decidimos reemplazar los caracteres numéricos por caracteres repetidos tantas veces como cajas vacías hayan.














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;




He aquí otro ejemplo de sustitución por función. Es un programa que busca en una cadena de variables para reemplazar su valor. Las variables siguieron la sintaxis utilizada para las variables de entorno como  %date%%username%. La función que devuelve la cadena a sustituir obtendrá sus resultados de un diccionario.





program Replace3b;




{$APPTYPE CONSOLE}

uses

System.SysUtils,

System.RegularExpressions;

System.Classes,



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;


end.

ReadLn;





Image non disponible


IX. conclusión


La Unidad RegularExpressions se
basa en la unidad RegularExpressionsCore que, excepto el
nombre, es igual a la unidad PerlRegEx de Jan Goyvaerts.





Seleccione


uses


{$IF CompilerVersion >= 22.0}


  RegularExpressionsCore;


{$ELSE}


  PerlRegEx; (*
http://www.regular-expressions.info/download/TPerlRegEx.zip *)


{$IFEND}


La unidad PerlRegEx se
basa en la biblioteca PCRE (Expresiones regulares compatibles con Perl)
por Philip Hazel.





Los ejemplos
han sido probados con Delphi XE2







No hay comentarios:

Publicar un comentario

Simulación del movimiento de los electrones en un campo electrico

Espectacular simulación realizada con OpenGL del movimiento de los electrones cuando atraviesan un campo eléctrico. Como muestra la image...