RegEx - una introducción

facebook-svg gplus-svg twitter-svg

Una expresión regular, a menudo llamada también regex, es una secuencia de caracteres que forma un patrón de búsqueda. Por ejemplo para buscar la palabra "Girona" en una cadena de texto dada, podemos utilizar esta expresión regular:

/Girona/

El modificador g

Pero esta expresión regular devuelve solo la primera ocurrencia de "Girona" en un texto. Para encontrar todas las ocurrencias tenemos que utilizar g, un modificador ( modifier o flag ).

/Girona/g

El modificador g indica que queremos efectuar una búsqueda global ( global match ).

El modificador i

Si queremos efectuar una búsqueda insensible a mayúsculas y minúsculas ( case insensitive ) tenemos que utilizar el modificador i.

/Girona/gi

En este caso i indica que queremos efectuar una búsqueda que no distingue entre Girona  y girona, entre mayúsculas y minúsculas.

Por ejemplo en este texto:

var cadena = "El teléfono del Aeropuerto Girona Costa Brava ( www.girona-airport.net/ ) es el 972 186 600";

La búsqueda anterior devuelve tanto Girona de Girona Costa Brava, como girona de www.girona-airport.net.

See the Pen RegEx Girona #1 by Gabi (@enxaneta) on CodePen.

El modificador m

El modificador m efectúa una búsqueda en múltiples líneas de texto ( Multiple lines ). Utilizado cuando la expresión regular incluye los caracteres para principio ( ^ ) y/o final ( $ ) de línea.

La siguiente expresión regular quiere encontrar la palabra "Girona", pero solo si se encuentra al final $ de una línea. La búsqueda es global ( g ) o sea: no para después de encontrar la primera ocurrencia.

/Girona$/gm;

See the Pen RegEx modificador m by Gabi (@enxaneta) on CodePen.

Más modificadores
Modificadores Descripción
i Insensible a las mayúsculas y minúsculas W (case insensitive)
g Busqueda global (global match)
m Búsqueda en múltiples líneas de texto. (Multiple lines)
s Incluye saltos de línea. Sin él, las nuevas líneas son excluidas.

Utilizando clases de caracteres y caracteres específicos

En RegEx podemos efectuar una búsqueda literal ( busca ciertos caracteres en el orden especificado ) como arriba, o podemos efectuar una búsqueda generalizada, utilizando clases de caracteres y caracteres específicos.

Dígitos

Por ejemplo para buscar un dígito cualquiera podemos utilizar:

/\d/ /*encuentra un dígito cualquiera */
  o
/[0-9]/ /* cualquier carácter entre 0 y 9*/

Observe por favor que al \d le anteponemos una contrabarra ( o barra inclinada hacia atrás ) para decir que se trata de un carácter especial y no de un carácter literal ( no busca la letra d ).

En regex la contrabarra ( \ ) se utiliza para escapar metacarracteres.

Para encontrar cualquier carácter que NO es un digito utilizamos de la misma manera una \D mayúscula precedida por una contrabarra.

En regex las mayúsculas se utilizan siempre para los caracteres omitidos del patrón de búsqueda.

/\D/ /*encuentra cualquier carácter que NO sea un dígito*/
  o
/[^0-9]/ /* cualquier carácter que NO sea un dígito entre 0 y 9*/

También el símbolo de intercalación (^) es el símbolo predeterminado para los caracteres omitidos, en este caso cualquier carácter que NO sea un dígito entre 0 y 9.

Clases de Caracteres
Expression Descripción
[abc] Encuentra uno de los caracteres entre corchetes
[^abc] Encuentra cualquier carácter que NO esté entre corchetes
[0-9] Encuentra un dígito de 0 a 9
[^0-9] Encuentra cualquier carácter que NO sea un dígito de 0 a 9
[A-Z] Encuentra cualquier carácter de
A mayuscula a Z mayuscula
[a-z] Encuentra cualquier carácter de
a minuscula a z minuscula
[A-z] Encuentra cualquier carácter de
A mayuscula a z minuscula
[adgk] Encuentra uno de los caracteres entre corchetes
[^adgk] Encuentra cualquier carácter que NO esté entre corchetes
(a|b) a o b
(...) Se utilizan para agrupar partes de una expresión.
Caracteres específicos
Carácter Descripción Equivalente
\w Encuentra un carácter alfanumérico, incluido el guión bajo ( _ )
¡OJO! no encuentra diacriticos.
[a-z
A-Z0-9_]
\W Encuentra cualquier carácter NO alfanumérico [^a-z
A-Z0-9_]
\d Encuentra un dígito [0-9]
\D Encuentra cualquier carácter que NO es un dígito. [^0-9]
\s Encuentra un espacio en blanco [ \t\r\n]
\S Encuentra cualquier carácter que NO es un espacio en blanco. [^ \t\r\n]
\b Encuentra una coincidencia al inicio o al final de una palabra.  
\B Encuentra una coincidencia que NO està al inicio o al final de una palabra.  
\0 Encuentra un carácter NUL  
\n Salto de línea (new line)  
\r Retorno de carro (return)  
Letras

Por de otra parte si queremos encontrar una letra podemos escribir el siguiente patrón de búsqueda:

/[a-z]/ /*encuentra cualquier letra minúscula de a a z*/

Si queremos tomar en consideración tanto minúsculas como mayúsculas podemos utilizar el modificador i ( case insensitive ) o escribir:

/[a-zA-Z]/ /*encuentra cualquier letra de a a z y de A a Z */

También podemos utilizar \w que encuentra un carácter alfanumérico, incluido el guión bajo.
El equivalente de \w es:

/[a-zA-Z0-9_]/ 
Diacríticos

Por lastima en español tenemos diacríticos y todo lo que hemos visto hasta ahora ignora los diacríticos. ¿Qué podemos hacer?
Una primera opción sería utilizar algo así ( no muy bonito ni muy aconsejable, pero puede sacar de un apurro ):

/[a-zA-ZàèìòùÀÈÌÒÙáéíóúÁÉÍÓÚñÑïöüÏÖÜçÇ]/

Claro está hay más diacríticos por ahí, y podemos ponerlos todos, o podemos utilizar una clase de caracteres diacríticos.

/[a-z\xC0-\xff]/i;
o
/[a-z\u00C0-\u017F]/i

Donde [\xC0-\xff] representa una clase de caracteres diacríticos. Si necesita una explicación, la \xdd se utiliza para encontrar una secuencia hexadecimal de hasta dos dígitos. Por ejemplo /[\xf3]/i encuentra tanto una ó como una ó y equivale a /[\u00f3]/i.

Probablemente la mejor solución, pero todo depende de lo que queremos conseguir, es utilizar \S o [^\s] que encuentra cualquier carácter que NO es un espacio en blanco. Y si tenemos en cuenta la puntuación podemos escribir:

/([^\s.,:;])/g

Observe por favor que entre corchetes no necesitamos escapar el punto ( . ).

See the Pen RegEx diacriticos by Gabi (@enxaneta) on CodePen.

Cuantificadores

Los cuantificadores especifican cuantas instancias de un carácter, grupo o clase  de caracteres buscamos. ( Lo de "codiciosas" y "perezosas" lo encontrará explicado más tarde. )

Codiciosas Perezosas Descripcion
* *? 0 o más veces
+ +? 1 o más veces
? ?? 0 o 1 veces
{ n } { n }? n veces
{ n ,} { n ,}? n o más veces
{ n , m } { n , m }? De n a m veces

Por ejemplo en lugar de buscar Girona, como en el caso anterior, podría buscar una palabra de 6 letras. La expresión regular /[a-z]{6}/i encuentra un grupo de seis letras, incluso grupos de seis letras de palabras más largas. Por ejemplo encuentra Oficin de Oficina y Princi de Principal. No es lo que queremos.  Para encontrar palabras enteras de seis letras tenemos que utilizar un delimitador de palabras: \b.

Delimitadores de palabras

A diferencia de un espacio en blanco \s, el delimitador de palabras \b no tiene anchura alguna.

/\b[a-z]{6}\b/ig

Y el patrón de búsqueda encuentra dos palabras de 6 letras ( Girona ). ¡Perfecto! ¡Hemos dado en el clavo! . . . O al menos esto es lo que parece hasta que decidimos añadir otro número de teléfono:

See the Pen RegEx Girona #21 by Gabi (@enxaneta) on CodePen.

El patrón de búsqueda encuentra también Atenci de Atención, y no tendría. Esta es una de aquellas cosas raras que pasan cuando hay diacríticos de por medio. Veamos como podemos corregirlo.

Declaraciones (Assertions)

En RegEx existe algo llamado declaración positiva de búsqueda hacia adelante ( positive lookahead assertion ) que hace una cosa fantástica.

Declaraciones (Assertions)
  Descripción
?= declaración positiva de búsqueda hacia adelante
(positive lookahead assertion)
/(?=prematuro)pre/ encuentra pre de prematuro
pero no pre de precavido
/pre(?=maturo)/ encuentra pre de prematuro
pero no pre de precavido
?! declaración negativa de búsqueda hacia adelante
(negative lookahead assertion)
/(?!prematuro)pre/ encuentra pre
pero no de prematuro
/pre(?!maturo)/ encuentra pre
pero no de prematuro
/pre(?=maturo)/

La expresión regular de arriba busca pre, y si lo encuentra mira hacia adelante a ver si encuentra también maturo , y si lo encuentra, considera pre de prematuro una ocurrencia. De la misma manera podemos buscar hacia adelante a ver si hay un espacio en blanco \s, y si lo encuentra considera el grupo anterior ([a-z]{6}) un acierto.

/\b([a-z]{6})(?=\s)/ig

Podemos mejorar el patrón de búsqueda si tomamos en consideración la puntuación:

/\b([a-z]{6})(?=[\s,.;:])/ig

Y si queremos ser precavidos podemos mejorarlo todavía más: podemos tomar en cuenta que la palabra buscada puede encontrarse al final de una línea ( $ ).
En regex la pleca o barra vertical ( | ) es un operador que significa uno u otro (uno|otro), esto o aquello (esto|aquello), final de línea o espacio en blanco ($|\s).

/\b([a-z]{6})(?=$|[\s.,])/ig

¡Perfecto! Ahora sí que lo hemos acertado.

See the Pen RegEx Girona #3 by Gabi (@enxaneta) on CodePen.

Pero hasta ahora no hemos tomado en consideración los diacríticos. Hablamos y escribimos en castellano, y los diacriticos son inevitables. Y ya sabemos como hacerlo: en lugar de [a-z] vamos a utilizar \S o [^\s] que encuentra cualquier carácter que NO es un espacio en blanco.

/\b(\S{6})(?=$|[\s.,])/g

See the Pen RegEx Girona #4 by Gabi (@enxaneta) on CodePen.

Y de nuevo tenemos problemas. El patron de busqueda encuentra también ística de "Turistica", y todo por culpa de los diacriticos. En otros lenguajes de programación existe algo llamado declaración positiva de búsqueda hacia atras, pero no en Javascript. Entonces ¿como hacerlo? La solución es utilizar un grupo pasivo.

Grupos de captura y grupos pasivos

Los grupos de captura ( capturing groups ) son aquellas expresiones regulares que aparecen entre paréntesis, y se llaman grupos de captura porque pueden ser capturados ( guardados en memoria ) y pueden ser utilizadas más tarde mediante retroreferencias. Para designar una retroreferencia ( backreference ), a veces utilizamos la barra inversa ( \ ), otras veces el dólar ( $ ), dependiendo del lenguaje que se utilice. Por ejemplo en JavaScript si utilizamos el método replace, $1 es una retroreferencia que captura el primer grupo, $2 es otra retroreferencia que captura el segundo grupo . . .etc

  Descripción
$n \n n-ésimo grupo no pasivo (no-passive group)
$2 \2 encuentra "xyz" en /^ (abc)(xyz) $/
$2 \2 encuentra "xyz" en /^ (abc(xyz)) $/
?   cambia el significado del grupo
:   el significado del grupo es: pasivo
?:   especifica un grupo pasivo
(passive group / non-capturing group)
$1 \1 "xyz" en /^ (?:abc)(xyz) $/
(porque el primer grupo es pasivo)

Imagínese un texto donde queremos encontrar todas las letras que llevan acento o tilde y ponerlas dentro de un elemento <span> para poder darlos un formato especial.

La expresión regular que encuentra un diacrítico es /[\xC0-\xff]/. Para capturar el diacrítico encontrado tenemos que poner la expresión entre paréntesis.

var ex = /([\xC0-\xff])/g

A continuación podemos utilizar el grupo capturado y para referenciarlo utilizamos $1 ya que es el primer grupo de captura.

var nuevaCadena = cadena.innerHTML.replace(ex, "<span>$1</span>");

Lea más acerca del método replace

See the Pen grupos de captura by Gabi (@enxaneta) on CodePen.

Para que un grupo no sea capturado tenemos que transformarlo en un grupo pasivo ( passive group o non-capturing group ). Para esto utilizamos un interrogante seguido de dos puntos ( ?: ). Un grupo pasivo es utilizado para encontrar una coincidencia, pero no es capturado.

En el ejemplo anterior hemos visto que utilizando un delimitador de palabras \b puede fallar en el caso de las palabras que con diacríticos. Para que esto no pase, en lugar de \b, podemos utilizar un grupo pasivo (?:^|\s)  que busca un comienzo de una línea (?:^|\s) o un espacio en blanco (?:^|\s)  delante del grupo de captura (\S{6}).

var RegEx3 = /(?:^|\s)(\S{6})(?=$|[\s.,:;])/g;

A continuación viene el grupo de captura que busca un conjunto de 6 caracteres; (\S{6}) cualquier carácter excepto un espacio en blanco (\S{6}):

var RegEx3 = /(?:^|\s)(\S{6})(?=$|[\s.,:;])/g;

Al final, en lugar del delimitador de palabras \b, viene la declaración positiva de búsqueda hacia adelante (?=$|[\s.,:;]) que busca un final de línea (?=$|[\s.,:;]), o un espacio en blanco (?=$|[\s.,:;]), o algún signo de puntuación (?=$|[\s.,:;]).

var RegEx3 = /(?:^|\s)(\S{6})(?=$|[\s.,:;])/g;

See the Pen RegEx Girona #5 by Gabi (@enxaneta) on CodePen.

Expresiones regulares codiciosas y perezosas

En el siguiente ejemplo queremos buscar palabras enteras de 5 o 6 letras, seguidas – como ya lo hemos hecho hasta ahora  - por un espacio en blanco o algún signo de puntuación.

/(?:^|\s)(\S{5,6})(?=$|[\s.,:;])/g;

See the Pen RegEx Sancho perezoso by Gabi (@enxaneta) on CodePen.

Sin embargo en lugar de seleccionar solo la palabra, en el caso de las palabras de 5 letras, si van seguidas de una coma, esta aparece seleccionada también. ¿Por qué pasa esto? De entada porque una coma coincide con lo que buscamos: cualquier carácter que no sea un espacio en blanco \S. También porque por defecto las expresiones regulares son codiciosas ( greedy ). Esto significa que una expresión regular devuelve la cadena de texto más larga que coincida con ella. Pero hay solución. El metacarácter ? , detrás de otro metacarácter, hace que una expresión regular, habitualmente codiciosa ( greedy ), se convierta en perezosa ( lazy ), y resulte en la cadena más corta posible que coincida con ella.

var RegEx3 = /(?:^|\s)(\S{5,6}?)(?=$|[\s.,:;])/g;

See the Pen RegEx Sancho perezoso #2 by Gabi (@enxaneta) on CodePen.