Expresiones regulares: Novedad en XE (Parte 2)

Hace algo mas de un mes, compartíamos la entrada Expresiones regulares: Novedad en XE (Parte I), en la que intentabamos -con mas o menos fortuna- acercarnos al concepto de «Expresión Regular». Y para ello, tras unas primeras pinceladas sobre el hecho de la disponibilidad  en XE, pasamos gran parte de la misma, centrados en la optimización de nuestras busquedas en el IDE. Yo creo que puede ser interesante y os puede ayudar releer esa primera parte. Yo también he tenido que releerla porque había perdido el hilo de lo escrito. 🙂

No comenté en ese momento, y quizás hubiera sido lo primero que debía haber dicho, que la incorporación a nuestro entorno se hace creando un envolvente de la librería llamada Perl Compatible Regular Expressions (comunmente PCRE), que es una librería de licencia Open Source creada por Phillip Hazel. Me pareció en aquel momento más oportuno desglosar el qué, y dejar para estas lineas el quíen y el cómo. Así que, para ampliar este tema, deberías acudir a la fuente original http://www.regular-expressions.info/pcre.html y leer con detenimiento algunos apartados, que sin duda van a ser de gran interés. Destacaría dos dentro de la misma página (http://www.regular-expressions.info/tools.html): Specialized Tools and Utilities for Working with Regular Expressions, donde podéis conocer algunas herramientas y utilizarlas para crear Expresiones Regulares complejas, lo cual no es una tarea compleja sino mas bien enrevesada y farragosa. Y un segundo apartado,  Programming Languages and Libraries, en el que vais a descubrir una gran lista de lenguajes de programación y entornos de desarrollo, lista muy variada y amplia, que soportan la librería, en algunos casos con extensiones de terceros. Lo cual, al final de toda esta historia, es sinónimo de un ahorro de tiempo considerable (por cuanto estamos usando una librería que nos libera de algunas tareas de evaluación de datos, búsquedas complejas con o sin reemplazo, así como reutilización y portabilidad, dado que estas expresiones van a poder ser usadas por ese abanico de posibles.

¡Quien te dice y puede asegurar que una parte de tu proyecto, hoy ceñido a delphi no pueda mañana ampliarse al mundo de Java, php, net o cualquiera de los lenguages o entornos citados en esa lista!. Ciertamente van a requerir un aprendizaje y un conocimiento familiar de la simbología, sin el cual nos van a parecer signos caprichosamente entrelazados y bastante crípticos.

La lectura de http://www.regular-expressions.info/delphi.html no debería omitirse en ningún caso, si finalmente os decidís por su uso. De esta lectura, se desprende que vais a poder utlizarlas no sólo ya en Delphi XE, del que hemos comentado que trae el envolvente que permite importar e invocar las funciones de la librería, sino que existen disponibles componentes, bajo la licencia citada al principio de texto, para la instalación en versiones anteriores de nuestro IDE. Yo, por poner el ejemplo mas cercano a mi mismo :-), he hecho la instalación en Delphi 2007, que es la versión de Delphi que uso con mas frecuencia y francamente, reconozco que me hubiera sido de gran ayuda tiempo atrás. Así que está mejora no solo es aplicable a Delphi XE sino que si la incorporáis ya, a través de estos componentes, cuando llegue a migración a XE va a ser facilmente hecho el cambio sin que esto afecte al contenido de las expresiones manejadas en el proyecto.

Estos son los dos enlaces para la descarga.

(Version compilada de PCRE: 7.9)

El primero de ellos contiene el wrapper o envolvente VCL para Delphi XE, compatible con el modulo RegularExpresionsCore, incluido en Delphi que importa la librería. En el caso de Delphi XE, la importación se hace linkando los obj compilados en la unidad RegularExpresionsApi. Estas son, por ejemplo, las lineas citadas, donde se produce el enlazado, en el area de implementación de dicho módulo:

{$IFDEF MSWINDOWS}
{$LINK regexpcre_compile.obj}
{$LINK regexpcre_config.obj}
{$LINK regexpcre_dfa_exec.obj}
{$LINK regexpcre_exec.obj}
{$LINK regexpcre_fullinfo.obj}
{$LINK regexpcre_get.obj}
{$LINK regexpcre_globals.obj}
{$LINK regexpcre_info.obj}
{$LINK regexpcre_maketables.obj}
{$LINK regexpcre_newline.obj}
{$LINK regexpcre_ord2utf8.obj}
{$LINK regexpcre_refcount.obj}
{$LINK regexpcre_study.obj}
{$LINK regexpcre_tables.obj}
{$LINK regexpcre_try_flipped.obj}
{$LINK regexpcre_ucd.obj}
{$LINK regexpcre_valid_utf8.obj}
{$LINK regexpcre_version.obj}
{$LINK regexpcre_xclass.obj}
{$LINK regexpcre_default_tables.obj}
{$ENDIF MSWINDOWS}

Ahora bién, también podríamos optar por enlazar la librería directamente desde nuestras aplicaciones, ahorrando espacio (su fuera el caso de que la libreria se utizara en multiples aplicaciones). Es una opción.

En el caso de la segunda descarga, que incluye los componentes para versiones anteriores a XE, su implementación no utiliza el enlazado de los obj, por lo que hace necesario que exista la librería y que sea incluida conjuntamente con nuestro desarrollo.

Pasemos a la práctica…

En la parte primera de esta serie, aportamos varios enlaces, entre ellos los que hacían referencia dentro de la ayuda de Delphi a distintos ejemplos o extractos de código, la mayoría relacionados con las clases y métodos expuestos en la unidad RegularExpresionsCore. Sobre la capa superior, RegularExpresions hay un tanto menos pero sí que existe un ejemplo muy util y sencillo para familiarizarse con el uso de expresiones.

Lo podéis encontrar en:

C:Program FilesEmbarcaderoRAD Studio8.0sourcertlcommonregex

sample

Yo creo que es interesante que lo comentemos.

En dicho ejemplo se pretende evaluar el contenido de una casilla de edición respecto de una expresion regular, que puede ser cualesquiera de las expuestas por la selección del  componente TListBox. Al seleccionar una alternativa, el componente TMemo carga en sus lineas la expresión regular y el usuario puede introducir un texto cualquiera para ver si dicho texto la cumple. Para quienes no están habituados al uso de expresiones regulares puede ser de gran ayuda ese primer contacto donde conoce cómo evaluar una direccion de correo, una fecha, un intervalo de fechas o una ip (que son las expresiones que recoge el ejemplo).

Evaluar una expresión es tan sencillo, que ha bastado una linea de código:

 if TRegEx.IsMatch(EditText.Text, MemoRegEx.Lines.Text) then else … 

Lo cual os permite centraros en la corrección de la construcción de la expresión y obviar las lineas de código necesarias para hacer lo mismo, muy en la linea de eso tan conocido de no reinventar la rueda o el carro  🙂

Si os parece, podemos tirar un poco del ovillo y desentrañar que está pasando cuando invocamos la función, porque ésto nos va a descubrir los actores del escenario.

La función IsMatch es una de las tantas que implementa TRegEx en RegularExpresions. TRegEx, como podéis imaginar es el actor que representa el uso de la expresión regular. No es una clase sino un registro y la función IsMatch( ) está marcada en su interfaz como método de clase (class function) por lo que podemos invocarla sin la existencia de la instacia.

Veamos que pasa entre bastidores:

class function TRegEx.IsMatch(const Input, Pattern: string): Boolean;
var
  LRegEx: TRegEx;
  Match: TMatch;
begin
  LRegEx := TRegEx.Create(Pattern);
  Match := LRegEx.Match(Input);
  Result := Match.Success;
end;

La función IsMatch( ) ha hecho el trabajo duro 🙂 , consistente en declarar la variable del registro TRegEx e invocar al contructor entregando como parametro nuestra expresión. Seguidamente, la invocación de Match desde la instancia creada, que recibe como parametro el texto a evaluar nos devuelve la concidencia en el también registro TMatch. Y finalmente, el retorno de Success, ¡voila! nos entrega si ha tenido éxito o no.

Ahora la pregunta del millón que muchos pueden estar haciendose… (¿no se destruye lo creado?)

Recordad que estamos manejando registros y además, existe una pequeña artimaña para forzar la liberación de los recursos. Vincent Parrett, autor de la unidad RegularExpresions, ha declarado en la parte privada de TRegEx el campo de tipo IInterface.

FNotifier: IInterface;

FNotifier permitirá que sea destruida la memoria asociada a la creación de LRegEx, ya que su constructor guardará la referencia a la instancia de la clase TPerlRegEx (declarada en ExpresionRegularsCore) que es la que realmente hace el trabajo entre bastidores y reserva memoria. Veamoslo. Su constructor finaliza con la linea

FNotifier := MakeScopeExitNotifier(FRegEx);

Y esta llamada crea una instancia de la clase TScopeExitNotifier, descendiente de TInterfacedObject, y que usa el recurso del recuento de referencias propio de las interfaces, eliminando en ultimo extremo la memoria asociada a todo este proceso.

destructor TScopeExitNotifier.Destroy;
begin
  if Assigned(FRegEx) then
    FreeAndNil(FRegEx);
  inherited;
end;
destructor TPerlRegEx.Destroy;
begin
  pcre_dispose(FPattern, FHints, FCharTable);
  inherited Destroy;
end;

Es decir, en teoría debería garantizar que fuera liberada la memoria asociada a la creación de la instancia de TPerlRegEx desde TRegEx.

 Hay un pequeño punto de discrepancia que teneis que tener en cuenta aunque no afecta a este ejemplo, y que he podido leer rebuscando en Internet. Parece ser que existe un caso abierto en QualityCentral sobre este punto, en referencia a la clase TMatch. Lo podeis leer en:

TRegEx frees FRegEx while TMatch et al still have a reference to it

También existe una referencia en:

Bug in Delphi XE RegularExpressions Unit

Nuestro ejemplo…

Ahora que hemos compartido el ejemplo que trae nuestro entorno, podemos finalizar la entrada con uno propio, también muy sencillo, en donde nos hemos propuesto localizar en un texto cualquiera, una expresión y reemplazar todas las palabras que la cumplen.

Se me ocurrió que esto de alguna forma complementaba los posibles usos mas comunes de esta libreria: evaluar, buscar y reemplazar (ebr para quien no sepa vivir sin abreviaturas ;-D), dado que nuestro ejemplo anterior aborda la utilidad de la evaluación, en éste podríamos buscar y finalmente reemplazar unas palabras en un texto.

Necesitamos un texto y para ello, seleccionaré algo muy hispano (un fragmento del Quijote de Cervantes)

«En un lugar de la Mancha, de cuyo nombre no quiero acordarme, no ha mucho tiempo que vivía un hidalgo de los de lanza en astillero, adarga antigua, rocín flaco y galgo corredor. Una olla de algo más vaca que carnero, salpicón las más noches, duelos y quebrantos los sábados, lantejas los viernes, algún palomino de añadidura los domingos, consumían las tres partes de su hacienda. El resto della concluían sayo de velarte, calzas de velludo para las fiestas, con sus pantuflos de lo mesmo, y los días de entresemana se honraba con su vellorí de lo más fino. Tenía en su casa una ama que pasaba de los cuarenta y una sobrina que no llegaba a los veinte, y un mozo de campo y plaza que así ensillaba el rocín como tomaba la podadera. Frisaba la edad de nuestro hidalgo con los cincuenta años. Era de complexión recia, seco de carnes, enjuto de rostro, gran madrugador y amigo de la caza. Quieren decir que tenía el sobrenombre de «Quijada», o «Quesada», que en esto hay alguna diferencia en los autores que deste caso escribe, aunque por conjeturas verisímiles se deja entender que se llamaba «Quijana». Pero esto importa poco a nuestro cuento: basta que en la narración dél no se salga un punto de la verdad…»

Y la expresión hidalgo|[Mm]ancha|roc[íi]n , que nos ayudaría a buscar en dicho texto 3 palabras (hidalgo, Mancha y rocín) permitiendo que la segunda admita ‘M’ o ‘m’ y la tercera la vocal, acentuada o no acentuada.

Nuestro interfaz también será muy sencillo, un componente TRichEdit para guardar el texto y mostrar resaltada la palabra, una casilla de edición para la expresión, otra para el reemplazo, de querer utilizarlo, y finalmente,  en la parte inferior un lista TListBox, donde aparecen las palabras encontradas a medida que la busqueda progresa.

Esta es la imagen de nuestro formulario:

nuestro_ejemplo

Para que todo esto fuera un poco mas util, he creado 3 ejemplos (practicamente iguales), en cuanto a la interfaz y al objetivo pero abordados desde una óptica distinta ya que para el primero, pensé que sería interesante usar el componente para D2007, descargado de PCRE, mientras que en los dos restantes se abordan desde Delphi XE pero mientras el primero de ellos utiliza el modulo RegularExpresionsCore, el otro lo aborda con la capa superior, que es RegularExpresion. De esa forma, podíamos ver las diferencias, sobretodo entre estos dos ultimos ejemplos. Hay algunas reflexiones muy interesantes y quisiera dar las gracias expresamente a Marco Cantú, que atendió una pequeña consulta que le hice mientras me documentaba sobre el artículo que finalmente me fue de gran ayuda.

 Estos enlaces os permitirán descargar el código fuente:

Tenéis que tener en cuenta para el ejemplo con Delphi 2007 se presume la instalación del componente, por lo que sería necesaria la descarga del mismo siguiendo el comentario al inicio de la entrada y su instalación. Teneis mas arriba la direccion de descarga de los componentes para versiones anteriores a Dellphi XE.

Las siguientes lineas de código se corresponden con el ejemplo que usa RegularExpresions. En la tercera parte de esta serie podemos comentar el codigo y comparar aspectos interesantes de los mismos. En unos días creo que podré sacar tiempo para escribir la entrada.  🙂

Yo creo que se pueden resaltar puntos que son didácticos, como lo puede ser el que TPerlRegEx en RegularExpresionsCore trabaje con el tipo UTF8String en lugar del habitual String (Unicode), lo cual nos planteará un problema en el ejemplo 2, que nos obligará a corregir los Offset de la selección de palabra en el texto.

Como dicen en mi pueblo, abrimos boca con los entremeses que en un «ratico» llegan las chuletas y la carne…

Bon apetit…

(Modulo existente en prelregex_xe_highlevel.zip)
unit UBusquedasConExpRegulares;

interface

uses
  Windows, Messages, SysUtils, Variants, Classes, Graphics, Controls, Forms,
  Dialogs, StdCtrls, CheckLst, ComCtrls, ActnList, RegularExpressions;

CONST
 NO_ENCONTRADO = 'No se ha encontrado coincidencia...';

type
  TBusquedasConExpRegulares = class(TForm)
    edCadenaBusqueda: TEdit;
    bnIniciar: TButton;
    Label2: TLabel;
    Label1: TLabel;
    chlxOpciones: TCheckListBox;
    lbxResultados: TListBox;
    rchContenedorTexto: TRichEdit;
    Label3: TLabel;
    Label4: TLabel;
    Acciones: TActionList;
    acIniciar: TAction;
    sbBusqueda: TStatusBar;
    Button1: TButton;
    acSeguir: TAction;
    Button2: TButton;
    acReemplazar: TAction;
    Label5: TLabel;
    edCadenaAReemplazar: TEdit;
    procedure FormCreate(Sender: TObject);
    procedure acIniciarExecute(Sender: TObject);
    procedure acIniciarUpdate(Sender: TObject);
    procedure acSeguirExecute(Sender: TObject);
    procedure acSeguirUpdate(Sender: TObject);
    procedure acReemplazarExecute(Sender: TObject);
    procedure acReemplazarUpdate(Sender: TObject);
  private
    { Private declarations }
    FSeguir: Boolean;
    procedure TextoBarraEstado(ALength, AOffset: Integer);
    procedure EvaluarResultado;
    function CrearExpresionRegular: TRegEx;
  public
    { Public declarations }
  end;

var
  BusquedasConExpRegulares: TBusquedasConExpRegulares;

  CollOfMatch: TMatchCollection;
  EnumCollOfMatch: TMatchCollectionEnumerator;

implementation

{$R *.dfm}

procedure TBusquedasConExpRegulares.acIniciarExecute(Sender: TObject);
var
  pre: TRegEx;
begin
   //vaciamos la lista de resultados
   lbxResultados.Items.Clear;
   //inciamos la creación personalizada de la expresión regular
   pre:= CrearExpresionRegular;
   // obteniendo la colección de resultados tras ejecutar la expresión
   CollOfMatch:= pre.Matches(rchContenedorTexto.Text);
   //nos valemos de un enumerador para recorrer la estructura
   EnumCollOfMatch:= CollOfMatch.GetEnumerator;
   //evaluamos el resultado de la busqueda
   EvaluarResultado;
end;

procedure TBusquedasConExpRegulares.acIniciarUpdate(Sender: TObject);
begin
 acIniciar.Enabled:= (not FSeguir);
end;

procedure TBusquedasConExpRegulares.acReemplazarExecute(Sender: TObject);
var
  pre: TRegEx;
begin
  //vaciamos la lista de resultados
  lbxResultados.Items.Clear;
  //inciamos la creación personalizada de la expresión regular
  pre:= CrearExpresionRegular;
  //reemplazamos todas las coincidencias
  rchContenedorTexto.Text:= pre.Replace(rchContenedorTexto.Text,
                                        edCadenaAReemplazar.Text);
end;

procedure TBusquedasConExpRegulares.acReemplazarUpdate(Sender: TObject);
begin
 acReemplazar.Enabled:= (not FSeguir)
end;

procedure TBusquedasConExpRegulares.acSeguirExecute(Sender: TObject);
begin
  EvaluarResultado;
end;

procedure TBusquedasConExpRegulares.acSeguirUpdate(Sender: TObject);
begin
 acSeguir.Enabled:= FSeguir;
end;

function TBusquedasConExpRegulares.CrearExpresionRegular: TRegEx;
var
  i: Integer;
  fOp: TRegExOptions;
begin
   //vaciamos el conjunto de opciones para aplicar
   fOp:= [];    // y procedemos a obtener las validas
   for i := 0 to chlxOpciones.Count - 1 do
   begin
      if chlxOpciones.Checked[i] then
         case i of
           0: fOp:= fOp + [roIgnoreCase];
           1: fOp:= fOp + [roMultiLine];
           2: fOp:= fOp + [roSingleLine];
           3: fOp:= fOp + [roIgnorePatternSpace];
           4,5:;
           //4: fOp:= fOp + [preAnchored];
           //5: fOp:= fOp + [preUnGreedy];
           6: fOp:= fOp + [roExplicitCapture];
         end;
   end;
   // Creamos el registro para manejar la expresión regular
   Result:= TRegEx.Create(edCadenaBusqueda.Text, fOp);
end;

procedure TBusquedasConExpRegulares.EvaluarResultado;
begin
   //evaluamos si podemos seguir o concluye
   FSeguir:= EnumCollOfMatch.MoveNext;
   if FSeguir then
   begin
     //y de ser así, nos situamos sobre la coincidencia
     with EnumCollOfMatch.Current do
     begin
      lbxResultados.Items.Add(Value);         //añadimos la subcadena encontrada
      rchContenedorTexto.SelStart:= Index-1;  //Su posición en el texto inicial
      rchContenedorTexto.SelLength:= Length;  //Su longitud
      TextoBarraEstado(Length, Index-1);      //Y lo mostramos además en la barra
     end;                                     //de estado
   end
   else begin
      lbxResultados.Items.Add(NO_ENCONTRADO); //comunicamos que no ha sido encontrada
   end;
end;

procedure TBusquedasConExpRegulares.FormCreate(Sender: TObject);
begin
  rchContenedorTexto.Lines.LoadFromFile('texto.txt', TEncoding.UTF8);
end;

procedure TBusquedasConExpRegulares.TextoBarraEstado(ALength, AOffSet: Integer);
begin
 with sbBusqueda do
 begin
   sbBusqueda.Panels[0].Text:= 'Length:'+ IntToStr(ALength);
   sbBusqueda.Panels[1].Text:= 'OffSet:'+ IntToStr(AOffSet);
 end;
end;

end.

5 respuestas a “Expresiones regulares: Novedad en XE (Parte 2)

Add yours

  1. Hola Eliseo:
    Gracias por el comentario y me alegro mucho que te guste.

    Siempre me pasa lo mismo 🙂 En teoria tendría que haber zanjado el tema con esa segunda parte pero a medida que vas escribiendo y se calientan los dedos me resulta imposible concretar sin que se pierda lo mas bonito de la historia, que es el proceso de aprendizaje. Así que cuando se ha extendido mas de la cuenta, no me queda mas remedio que claudicar y dejar que exista otra parte mas en la serie. Ni siquiera ahora mismo tengo claro que en lugar de ser una sean dos mas… jajajaja

    Yo se que lo que voy a decir a muchos compañeros les puede extrañar pero en mi caso, al igual que en el de otros muchos compañeros, cuando me decido a iniciar una entrada de este tipo es casi siempre porque es un tema que no conozco y me atrae. Y los problemas que me encuentro al escribirlo son los mismos que se encontraria cualquier otro al intentar incorporar las espresiones regulares y dar los primeros pasos. Asi que nos podemos atascar y bloquear si surge algun problema y te das cuenta que no puedes seguir, que tienes que seguir documentandote y preguntando. 🙂

    Pero para eso estan los foros como el vuestro, y los otros muchos que tenemos siempre generando un valor añadido a la Comunidad. 😉

    Así que en ese sentido, si al final sale algo bueno de ello y ayuda a alguien, es algo que te hace sentirte bien.

    Un saludo,

    Salvador

    Me gusta

Deja un comentario

Blog de WordPress.com.

Subir ↑

Marina Casado

Escritora y Doctora en Literatura Española. Periodista cultural. Madrid, España

Sigo aqui

Mi rincon del cuadrilatero, ahi donde al creer que me he rendido, aun sigo peleando.

Recetas y consejos nutricionales

Indicadas para personas con diabetes, recomendadas para todos.

¡Buen camino!

ANÉCDOTAS Y REFLEXIONES SOBRE UN VIAJE A SANTIAGO…

https://lfgonzalez.visiblogs.com/

Algunas reflexiones y comentarios sobre Delphi

It's All About Code!

A blog about Delphi, C++ Builder and related technologies...

The Podcast at Delphi.org

The Podcast about the Delphi programming language, tools, news and community.

Blog de Carlos G

Algunas reflexiones y comentarios sobre Delphi

The Road to Delphi

Delphi - Free Pascal - Oxygene

La web de Seoane

Algunas reflexiones y comentarios sobre Delphi

El blog de cadetill

Cosas de programación....... y de la vida

Delphi-losophy

A Lover of Delphic Wisdom

Delphi en Movimiento

Algunas reflexiones y comentarios sobre Delphi

marcocantu.blog

Algunas reflexiones y comentarios sobre Delphi

Press F9

Algunas reflexiones y comentarios sobre Delphi

El blog de jachguate

Un blog sobre tecnología y la vida en general