Con esta última entrada que vamos a compartir, creo que podemos dar por cerrada la serie sobre Expresiones Regulares, al menos en lo que respecta estrictamente al blog. Sin embargo, si que me gustaría anticiparos y compartir con vosotros, de la misma forma que lo he hecho con el grupo que se abrió en Facebook, que voy a intentar mantener vivos y dinámicos algunos de los contenidos relacionados con las entradas, de forma que puedan ser consultados o comentados cuando ya no sean de inmediato interés o las entradas ya no habiliten la posibilidad de comentar porque ha caducado el plazo. Es por eso que existe una nueva opción en el menú «Temas de discusión», inaccesible de momento, pero que se activará si Dios quiere en un breve espacio de tiempo, (que ahora mismo no os puedo confirmar): ¿una semana? ¿un mes? Ni idea. Posiblemente, sea necesario ser miembro del grupo de Delphi Básico en Facebook para acceder al mismo.
De momento he creado cuatro temas de discusión que me parecen interesantes y que están directamente relacionados con entradas de estos últimos meses o del año anterior: el framework del conjunto de entradas de Un día con los mayores, el módulo de control de presencia, el componente TPanelMiniaturas que pudimos compartir con Germán y por último, el que representa a esta última serie sobre Expresiones Regulares.
Todo esto esta un poco en lo alto, madurando y casi diría fermentando… 🙂
Cualquier comenario que deseeis hacer sobre esto, lo podeis escribir en el muro del grupo de facebook (no en mi cuenta de facebook personal por favor).
Y por fin llegaron los postres…
Delphi 2007: PerlRegEx y su componente TPerlRegEx
Si hemos optado por Delphi 2007 o cualquiera de las versiones anteriores incluidas en los componentes que descargamos. ¡Enhorabuena!. Has sido un tipo listo, porque no tienes por que esperar a la renovación del IDE para disfrutar de «algo» que te pueda ahorrar un esfuerzo y un trabajo, con independencia de que este sea más o menos grande. No… no es que ahora sea todo reducido a una expresión regular. No… la idea es mas sencilla, más simple: Si encuentras un motivo para usarla, hazlo, porque posiblemente sea una buena inversión, ya que su uso va a ahorrarte bastantes lineas de código y además no va a ser algo que va a crearte problemas en esa posible actualización del entorno, si decides actualizarte a XE.
Vamos a entender este punto:
Una vez instalado el componente en el IDE podremos usarlo de la forma habitual, como cualquier otro componente de la paleta. En mi caso, y en el primero de los ejemplos, hemos hecho uso en tiempo de diseño de forma que nos despreocupamos tanto de la creación como de la destrucción. Tan solo debemos centrarnos en 3 aspectos claves:
-
Necesitamos asignar la expresión que se va a usar.
-
(pre.RegEx:= edCadenaBusqueda.Text;)
-
-
Necesitamos asignar el texto que se va a evaluar de acuerdo a esa expresión
-
(pre.Subject:= s;)
-
-
Y finalmente, necesitamos invocar el procedimiento que pone en marcha el proceso de analisis y compilación de la expresión
-
(if pre.Match then...)
-
A partir de ese momento, ya nos es posible evaluar si ha tenido exito gracias al método FoundMatch. También podemos conocer qué posición ocupa en primer caracter encontrado, que cumple la expresión, gracias a MatchedExpressionOffset y que longitud tiene el término MatchedExpressionLength.
Y en el caso de querer seguir buscando la expresión, simplemente sería algo tan sencillo como usar MatchAgain, nos avanzaría hasta la siguiente posición encontrada, de la misma forma que hemos comentado en el parrafo anterior.
En el ejemplo se puede ver claramente como avanzamos de forma progresiva a lo largo del texto, hasta encontrar todas las palabras que cumplen la expresión regular.
Término | Posición | Longitud |
Mancha | 19 | 6 |
hidalgo | 95 | 7 |
rocín | 149 | 5 |
rocín | 702 | 5 |
hidalgo | 760 | 7 |
Guardar en la mente que estamos trabajando con cadenas no Unicode sino AnsiString. Esto lo vamos a ver de reojo en el apartado próximo. Pero antes, y volviendo al comentario que iniciaba éste, la mecánica de trabajo que vamos a utilizar por ejemplo desde Delphi XE, a nivel de la unidad RegularExpressionsCore va a ser similar. Cambian los collares pero los perros son los mismos.
Podemos comparar esos chuchos: 🙂
Salvando que ahora necesitamos crear y destruir dinámicamente la instancia de TPerlRegEx, puesto que ya no tenemos un componente sino un objeto, los metodos que usamos tienen nombres similares como era de esperar. A fin de cuenta estamos usando una importación de la librería y ya se han abstraido los conceptos aplicados a la funcionalidad. Asi pues tenemos de igual forma las propiedades RegEx y Subject, que esperan respectivamente la expresión y el texto a evaluar. Seguimos teniendo Match y MatchAgain. Y tan solo respecto a nuestro ejemplo, nos ha variado que MatchedExpressionOffset ahora es MatchedOffset y MatchedExpressionLength se convierte en MatchedLength.
No parecen unos cambios muy complejos… 🙂
RegularExpressionsCore Vs RegularExpressions
Si nos decidimos a ejecutar el ejemplo 2, que utiliza el módulo RegularExpressionsCore, obtenemos un resultado un tanto diferente del ejemplo anterior. Permitidme unos cambios rápidos en el código… veamos… cambiad tanto en el cuerpo de la accion Iniciar como en Seguir las dos lineas, de forma que sean similares a las que teníamos en el primer ejemplo:
rchContenedorTexto.SelStart:= ConvertirOffSet(pre.MatchedOffset-1); rchContenedorTexto.SelLength:= Length(UTF8ToString(pre.MatchedText));
por
rchContenedorTexto.SelStart:= pre.MatchedOffset-1; rchContenedorTexto.SelLength:= pre.MatchedLength;
Esta es la nueva lectura que podemos hacer:
Término | Posición | Longitud |
Mancha | 19 | 6 |
hidalgo | 96 | 7 |
rocín | 150 | 6 |
rocín | 717 | 6 |
hidalgo | 776 | 7 |
¡Veis! Comparad unas y otras. Ya no nos coinciden y por lo tanto, las selecciones de texto en nuestro componente RichEdit andan digámoslo como desenfocadas.
😀
Esa es la magia de Unicode frente a AnsiString. Las vocales acentuadas que aparecen en algunas palabras del texto (como vivía, rócín, más, etc…) van a retornar un Offset y un valor de Length aparentemente incorrectos y ciertamente, esto no es exactamente así. Por tal motivo he utilizado en el ejemplo 2 un array de enteros y una función que corrige la posición.
El motivo de esta contrariedad es simple y de forma llana porque estamos trabajando con cadenas unicode UTF8String, y la codificación de éstas varía entre 1 y 4 bytes según el punto de codigo que consideremos. Para todos aquellos caracteres con mas de 1 byte, ese byte posterior al primero, tanto si consideramos el de dos, tres o cuatro bytes, tiene siempre la estructura binaria [10xxxxxx] donde esos primeros 2 bits (10) son fijos y el resto variable, dependiendo en conjunto de la codificacion del resto de bytes. Eso nos ha permitido de forma sencilla recontar los caracteres (descartando en el conteo los caracteres que representaban ese byte posterior) y recalcular el offset (ConvertirOffSet( ))
Hay un ejemplo muy bueno en Wikipedia que explica visualmente este punto. Os aconsejo la lectura así como la del libro de Marco Cantú «La guia de Delphi» (2010) donde se habla abiertamente del cambio a Unicode en nuestro entorno de desarrollo.
http://es.wikipedia.org/wiki/UTF8
http://es.wikipedia.org/wiki/Archivo:Codificaci%C3%B3n_UTF-8.svg
Todo esto comentado, va a influir en nuestra decisión de elegir entre las dos formas de trabajo distintas: una por decirlo de alguna forma de más bajo nivel, RegularExpresionsCore, que manipulará tanto a nivel de parámetros como retorno de funciones cadenas UTF8String. De hecho, en el constructor de la clase, se ocultan las opciones de creación que por defecto, para que esto no pueda ser cambiado, por razones obvias. Notad como estas constantes no estan definidas en el conjunto de opciones TPerlRegExOptions, que tambien van a combinarse previa a la compilación.
constructor TPerlRegEx.Create; begin inherited Create; FState := [preNotEmpty]; FCharTable := pcre_maketables; FPCREOptions := PCRE_UTF8 or PCRE_NEWLINE_ANY; end;
Y por otro lado RegularExpressions, una capa por encima de la anterior, permitiendo que trabajemos directamente con nuestras cadenas habituales String unicode, de forma que para nuestra selección del texto, no nos ha hecho falta ningun mecanismo adicional que haga el recalculo. Por lo demas, podeis usar sin problema tanto una forma como otra. Si os hace falta obtener el mapa de coincidencias, lo mas sencillo es que os decidais por la capa de nivel superior.
Bueno. Nos despedimos aquí. Es posible que queden algunas dudas. Mi consejo en ese caso es que activeis la depuración de los dcus en las opciones del compilador y recorrais con puntos de parada también las clases que participan. Es una buena forma de comprender que sucede realmente entre bastidores.
Espero que os haya gustado y que os pueda ser útil.
Deja una respuesta