Expresiones Regulares


IMPORTANTE: Apache OpenOffice 3.4 cambió el sistema de expresiones regulares. Utiliza el sistema ICU, que finalmente resuelve algunos viejos problemas del sistema utilizado en OOo hasta la versión 3.3 y en LibO hasta las versiones 3.6.x y resulta más rápido y confiable... al costo de no ser completamente compatible con el sistema anterior. En el libro Domando al escritor, Apache OpenOffice Writer para escritores doy una introducción al nuevo sistema.

También iré dando algunos consejos en el blog del pingüino tolkiano (etiqueta OpenOffice)

Por lo tanto, el artículo en la presente página es válido solo para OOo 3.3 y para todas las versiones de LibO hasta la 3.6.x: LibO 4.0 ha copiado el sistema de expresiones regulares introducido en AOO 3.4, por lo que lo comentado aquí tampoco servirá en las últimas versiones de la suite patrocinada por TDF.


Introducción

Una de las herramientas más potentes pero a la vez más difíciles de utilizar en OOo es la búsqueda avanzada de texto a través de expresiones regulares.

Puesto que no puedo considerarme un experto en el tema, intentaré en este artículo dar tan sólo una introducción para que el lector pueda dar sus primeros pasos en esta herramienta que, bien utilizada, es un importante aliado para la edición de textos complejos.

Qué son las expresiones regulares

Todos alguna vez hemos utilizado el famoso asterisco para buscar un archivo determinado: escribiendo *.odt en el buscador de archivos de nuestro sistema nos dará como resultado todos los archivos odt que tengamos en el directorio en el que estemos buscando. Aquí el asterisco funciona como un «comodín» que le dice al sistema de búsqueda «estoy a la búsqueda de todos los archivos que tengan cualquier nombre, pero que terminen con la extensión .odt».

Pues bien, las expresiones regulares son una extensión (hasta el infinito) de esta idea.

Supongamos que tenemos un texto en el cual se intercalan números, y que queremos cambiar todo ese texto en cursiva salvo por los números, que tienen que permanecer normales; ¿cómo podemos hacer esto?

Abran el cuadro de diálogo «Buscar y reemplazar» (Editar → Buscar y reemplazar, o simplemente Ctrl + B), hagan click en «Más opciones» y marquen «Expresiones regulares». Ahora, en «Buscar» escriban nuestra primer expresión regular:

(([^[0-9]]*)*)

en «Reemplazar por» escriban

$1

Con el cursor aún en «Reemplazar por» hagan click en «Formato» y seleccionen, sin cambiar nada más, el «Estilo» cursiva. Verán ahora que debajo de «Reemplazar por» ha aparecido la palabra «Cursiva», indicando que ese estilo será utilizado al reemplazar el texto.

Deberían ver algo parecido a lo siguiente:

si ahora van haciendo click en «Reemplazar», verán cómo progresivamente el texto que no contiene números viene seleccionado y posteriormente reemplazado por su versión en cursiva.

Pues bien, ahora que ya sabemos para qué sirven las expresiones regulares, veamos cómo se las utiliza.

Signos especiales

Una de las mayores dificultades de las expresiones regulares es que están llenas de signos cuyo significado cambia de acuerdo al contexto.

Pongamos un ejemplo.

El signo ^ por sí solo sirve para localizar cosas al comienzo del texto. Por ejemplo, escribiendo

^un

en «Buscar», encontraremos cualquier palabra al comienzo de un párrafo cuyas primeras letras sean «un» (un, una, uno...), ignorando las mismas palabras en el interior de los mismos párrafos.

Por otra parte, la misma expresión entre corchetes

[^un]

significa «un carácter diferente de un»; por lo tanto, si al principio de un párrafo tenemos la palabra «uno» y utilizamos la expresión regular [^un] en «Buscar», la herramienta de búsqueda ignorará el «un» y se detendrá en la «o».

Los corchetes, además de utilizarse para crear «negaciones» como la precedente, sirven para indicar rangos de caracteres. Por ejemplo

[0-9]

indica cualquier número del cero al nueve.

Con esto, nuestra expresión regular de más arriba comienza a comprenderse:

[^[0-9]]

representa un carácter cualquiera que no sea un número.

Por otra parte, el asterisco se utiliza para representar un número arbitrario de caracteres iguales al precedente: a* encontrará a, aa, aaa… etcétera, pero también encontrará cero instancias de ese carácter: cr*aso encontrará craso, crraso, crrrrrrrrraso… pero también caso. Evidentemente, al asterisco hay que utilizarlo con cuidado…

Los paréntesis sirven para «agrupar» expresiones. Hablaremos de eso más abajo.

A este punto nuestra expresión regular resulta clara: busca un número arbitrario de caracteres que no sean números.

Vemos otros ejemplos.

El punto .

Puede utilizarse para indicar un carácter genérico. Por ejemplo

m.s

buscará mas, más, mis… incluso mXs.

El signo interrogativo ?

Sirve para encontrar «cero o una instancias» del carácter precedente. Por ejemplo

haces?

Encontrará haces y hace.

El más +

Similar al asterisco, sirve para indicar una o más instancias del carácter precedente.

Las llaves {}

Sirven para indicar cuántas repeticiones nos interesan. Por ejemplo

¡a{1,4}y!

Encontrará un mínimo de una y un máximo de cuatro repeticiones de la a, y por lo tanto encontrará ¡ay!, ¡aay!, ¡aaay!, ¡aaaay!. Si ponemos un solo número el programa buscará exactamente ese número de repeticiones, por ejemplo ¡a{3}y! nos dará solamente ¡aaay!. Por último la expresión ¡a{3,}y! encontrará la palabra con por lo menos tres instancias de la a, pero sin límite máximo.

El signo $

Así como ^ busca el principio, el signo $ precedido de algún texto busca ese texto al final de un párrafo. El signo $ por sí solo puede utilizarse para buscar fines de párrafo.

En el casillero de «Reemplazar por» $ tiene otro significado que veremos más adelante.

La barra \

La barra puede utilizarse para decir que caracteres con un significado especial sean tratados como normales: si queremos buscar

. ^ $ * + ? \ [ ( { |

como si fueran letras, debemos anteponerles la \ (exactamente: para buscar \ debemos escribir \\)

Pero esta misma barra puede utilizarse para decir que algunos caracteres normales actúan ahora como especiales: \< busca al principio de una palabra mientras que \> busca al final de una palabra. Por ejemplo

\<jus

buscará: justo, justa, justamente… mientras que

ora\>

buscará hora, mora, ahora…

\t busca un «tab». Tengan en cuenta que \tasa no buscará la palabra tasa, sino un «tab» seguido de «asa».

\n tiene un doble significado: en «Buscar» encuentra quiebres de línea (Mayúsculas+Enter), mientras que en «Reemplazar por» introduce un quiebre de párrafo (sí, aquellos que se buscaban con $… lo sé, es extraño…)

La barra |

Utilizada entre corchetes, sirve para «elegir» entre dos opciones. Por ejemplo

bar[r|c]a

encontrará barra y barca, pero no barza. Puede ser utilizado más de una vez, en expresiones del tipo [a|b|c]

Grupos y referencias

Los paréntesis «agrupan» expresiones. La ventaja de esto, además de la claridad, es que las expresiones agrupadas pueden ser «llamadas» nuevamente. Veamos el siguiente ejemplo. Supongamos que queremos buscar texto repetido (típico error tipográfico que aparece cuando uno se detiene a pensar en la la mitad de una frase); la expresión regular

(.+)\1

encontrará cualquier grupo de caracteres que se repita, como por ejemplo dos veces la misma palabra o dos veces un espacio, ya que el grupo (.+) está llamado nuevamente con \1.

NOTA: Si tuviéramos dos o más grupos, algo así como (exp1)(exp2)… podríamos llamar al primero con \1, al segundo con \2…

Si ahora queremos «limpiar» esta repetición, podemos poner $1 en «Reemplazar por».

Sí, «Reemplazar por» funciona diferente de «Buscar»: la referencia se hace con $ y no con la \. De hecho, y salvo por algunas excepciones, «Reemplazar por» no acepta expresiones regulares.

NOTA: esta simple expresión encontrará también dos l consecutivas, dos r… en fin, nada es perfecto. Si quieren experimentar, prueben con algo como esto

\<([^ ]+)[ ]+\1

(noten el espacio luego del ^ y el que se encuentra entre los corchetes) o mejor aún

\<([^ ]+)[ ]+\1[^[a-z]]*

\<([^ ]+)[ ]+\1\>

dejo al lector la interpretación de estas expresiones… Noten, eso sí, que si bien estas tres últimas expresiones funcionan a la perfección para encontrar palabras duplicadas (en especial la última), si utilizan $1 en «Reemplazar por» en lugar de obtener la palabra sin repetir tendrán $1. Esto es un error de Writer reportado en este enlace. Aparentemente, este error se solucionará durante el ciclo de vida de la versión 3 del programa.

Algunos ejemplos

Para encontrar párrafos vacíos que solamente contengan algunos espacios, pero no palabras, letras o símbolos escribamos

^([ ]*)$

(entre los corchetes se encuentra un espacio) en «Buscar» (es decir, un comienzo de párrafo, un número arbitrario de espacios y un fin de párrafo).

Por alguna razón esta expresión falla para encontrar el caso de «cero espacios», es decir un párrafo sin ningún tipo de contenido. Para buscar este tipo de párrafos hay que utilizar

^$

Para introducir párrafos vacíos entre dos párrafos busquemos un fin de párrafo con $ en «Buscar» e introduzcamos dos consecutivos escribiendo

\n\n

en «Reemplazar por».

¡ATENCIÓN, ATENCIÓN! ¡Si desean hacer esto NO utilicen el botón «Reemplazar todo», ya que el programa podría entrar en un loop insertando infinitos párrafos en blanco! En este caso sería mejor editar el estilo de párrafo y agregar espacio antes o después del mismo.

En general, no es buena idea utilizar «Reemplazar todo» cuando se trabaja con expresiones regulares.

Un último ejemplo: Para encontrar números enteros escribamos

\<[1-9][0-9]*\>

Si queremos encontrar un número «con coma», del tipo 0,1234

\<[0-9]+,[0-9]*\>

ahora, si no sabemos si la persona que ha escrito el documento ha utilizado coma o punto como separador decimal:

\<[0-9]+[,|\.][0-9]*\>

Como pueden ver, las aplicaciones son infinitas. Para profundizar en este tema, recomiendo los siguientes sitios (en inglés):

http://wiki.services.openoffice.org/wiki/Documentation/How_Tos/Regular_Expressions_in_Writer

http://www.oooninja.com/2007/12/example-regular-expressions-for-writer.html

¡Buena suerte!