Tal como lo indica el título del artículo estamos buscando patrones pero de caracteres y dentro de un texto o cadena (string). Para ello usamos las expresiones regulares, tecnología presente en varios lenguajes de programación (PHP, Javascript, Phyton, Perl) que mediante una concatenación estructurada de caracteres nos permite moldear una fórmula o patrón para aplicar a un texto determinado y encontrar así las secuencias.

Cada vez que tengo que trabajar con ellos me veo con sentimientos encontrados. Por un parte  sufro un poco ya que su sintaxis es muy compleja y difícil de memorizar, pero por otra parte me maravillo por todo lo que podemos hacer con un par de líneas de código.

Por ejemplo: Muchas plataformas no permiten que los usuarios envíen sus direcciones de correo electrónico en los mensajes que intercambian. Para ello cada mensaje pasa por un filtro de expresiones regulares. Aplicando el regexp (acrónimo de Regular Expressions) /\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b/ podemos detectar estos caracteres tal como lo muestra la siguiente imagen:

ejemplo de expresiones regulares
Ejemplo visual que iremos viendo a lo largo del artículo

Sintaxis

A modo de ejemplo, y para que luego lo podamos utilizar en nuestro WordPress, vamos a utilizar la nomenclatura y funciones propias de PHP. Verán en cada sintaxis, en la última columna, un enlace a la web regex101 donde podrán testear los ejemplos en vivo.

Caracteres

RegexpDescripciónResultadoregex101
.Cualquier caracterTengo 36 años y mido 1,80m de altura.🔗
\sCualquier caracter de espacio en blancoTengo 36 años y mido 1,80m de altura.🔗
\SCualquier caracter que NO sea un espacio en blancoTengo 36 años y mido 1,80m de altura.🔗
\dCualquier caracter que sea un dígito entre 0 y 9Tengo 36 años y mido 1,80m de altura.🔗
\DCualquier caracter que NO sea un dígito entre 0 y 9Tengo 36 años y mido 1,80m de altura.🔗
\wCualquier caracter que sea una letraTengo 36 años y mido 1,80m de altura.🔗
\WCualquier caracter que NO sea una letraTengo 36 años y mido 1,80m de altura.🔗

Cuantificadores

RegexpDescripciónEjemploExplicaciónValores válidosregex101
*Cero o más/v\d(\.\d)*/g– Una «v»
– un dígito
– Cero o más de (
– un «.»
– un dígito
)
WordPress v4.8.
WordPress v4.8.3.
WordPress 4.8 ❌
🔗
?Uno o ninguno/Star Wars\s?\d?/g– Las palabras «Star Wars»
– Uno o ningún espacio
– Uno o ningún dígito
La película Star Wars
La película Star Wars 4
La película Star Wars 5
La película StarWars ❌
🔗
+ Uno o más/v\d[.]\d+/g– Una «v»
– un dígito
– un «.»
– Uno o más dígitos
WordPress v4.8.
WordPress v5.10.
WordPress v5 ❌
🔗
 {n}Exactamente n veces/w{3}/g– Exactamente tres letras «w»www.maugelves.com
www.ayudawp.com
desarrollowp.com ❌
🔗
{n,m}Entre n y m veces/\d{3,5}/g– Entre 3 y 5 dígitosPeugeot 306
Año 2017
20000 leguas de viaje submarino
12 monos ❌
🔗
{n,}n o más veces/\d{3,}/g – Mínimo 3 dígitosPeugeot 306
Año 2017
20000 leguas de viaje submarino
1000000 de años luz
12 monos ❌
 🔗

Lógica

Sí, también se pueden agregar cierta lógica dentro de la misma expresión. Echa un ojo a los siguientes ejemplo:

RegexpDescripciónEjemploExplicaciónValores válidosregex101
|Operador OR (ó)/Batman|Superman/g– La palabra «Batman» ó «Superman»Batman
Superman
Robin ❌
🔗
( … )Agrupador
Este grupo puede luego ser reutilizado.
/Mar(cos|tín)/g– Los caracteres «Mar» seguidos de
– «cos» ó «tín»
Marcos
Martín
Mauricio ❌
🔗
(?(1)yes|no)Condicional
Si el grupo 1 entonces «yes»
Sino «no»
/(Esta condición)?(?(1) es verdadera| es falsa)/g – Uno o ninguna aparición de los caracteres «Esta condición»
– Si existe concatena la búsqueda de los caracteres «es verdadera»
– Caso contrario concatena la búsqueda de los caracteres «es falsa»
Esta condición es verdadera
Esta otra es falsa
Aquí no se debe seleccionar nada ❌
🔗
(?=…)Futuro positivo (Positive Lookahead)

 

Solo encuentra patrones si existe una condición futura.

/para(?=psicología|normal)/g – Seleccionará los caracteres «para» solo si:
– los siguientes caracteres son «psicología» ó «normal»
La parapsicología.
Fenómeno paranormal.
El parapente. ❌
 🔗
(?!…)Futuro negativo (Negative Lookahead)

 

Solo encuentra patrones si no existe una condición futura.

/para(?!psicología|normal)/gSelecciona los caracteres «para» solo si:
– los siguientes caracteres no son «psicología» ó «normal»
El parapente
El paraíso fiscal
Fenómeno paranormal ❌
🔗
 (?<=…)Pasado positivo (Positive Lookbehind)

 

Solo encuentra patrones si existe una condición pasada.

/(?<=solu|informa)ción/gSelecciona los caracteres «ción» solo si:
– los caracteres anteriores son «solu» ó «informa»
Tengo la solución,
con la información
de la ecuación. ❌
🔗
(?<!…)Pasado negativo (Negative Lookbehind)

 

Solo encuentra patrones si no existe una condición pasada.

/(?<!informa)ción/gSelecciona los caracteres «ción» solo si:
– los caracteres anteriores no son «informa»
La desilución
y manipulación
de la información
🔗

Clases de caracteres

Limita los caracteres que estás buscando con las siguientes sintaxis:

RegexpDescripciónEjemploExplicaciónValores válidosregex101
[…]Delimitador de caracteres/[aeiouáéíóú]/g– Encuentra cualquiera de los caracteres especificados.murclago
aburriendo
bcdfghjklm ❌
🔗
[^…]
 Excepción de caracteres /[^aeiouáéíóú]/g– Encuentra todos los caracteres excepto los especificados.murclago
aburriendo
bcdfghjklm
🔗
[[:alnum:]]
Letras y dígitos/[[:alnum:]]/g– Encuentra todas letras y números comprendidos entre [A-Za-z0-9]¡¡¡Texto, 12345!!!🔗
[[:lower:]]
Caracteres en minúsculas /[[:lower:]]/g– Encuentra todos los caracteres en minúsculasabcDEF🔗
[[:upper:]]
 Caracteres en mayúsculas /[[:upper:]]/g – Encuentra todos los caracteres en mayúsculas abcDEF 🔗

Anclas

Las anclas sirven para indicar en qué parte del texto queremos aplicar el patrón de búsqueda. Veamos a continuación las sintaxis más utilizadas:

RegexpDescripciónEjemploExplicaciónValores válidosregex101
^Busca el patrón de caracteres al comienzo de la cadena o al comienzo de cada línea, según el modificador global.

 

Recuerda que cuando se encuentra entre llaves significa lo contrario: [^entre llaves]

Sin modificador multilínea
/^\w+/gCon modificador multilínea
/^\w+/gm
– Busca el patrón al comienzo del texto.Sin modificador multilínea:
Soy un texto multilínea
sin el modificador global «m»Con modificador multilínea:
Soy un texto multilínea
con el modificador global «m»
🔗
 $Busca el patrón de caracteres al final de la cadena o al final de cada línea, según el modificador global.Sin modificador multilínea
/\w+$/gCon modificador multilínea/Unicode
/\w+$/gum
 – Buscal el patrón al final del texto.Este es el fin
de la historia
🔗
/ABusca el patrón de caracteres siempre al comienzo del texto. No se ve afectado por el modificador global «m»./\A\w+/gm– A pesar de indicar el modificador «m» solo devolverá el conjunto de letras al principio del texto.Solo afecta al comienzo
aún con el modificador multilínea
🔗
/ZBusca el patrón de caracteres siempre al final del texto. No se ve afectado por el modificador global «m». /\w+\Z/gm– A pesar de indicar el modificador «m» solo devolverá el conjunto de letras al final del texto.Siempre seleccionaré
la última palabra
 🔗

Modificadores globales

Como verán en los ejemplos anteriores, todas las expresiones regulares las hemos encerrado con barras y una letra g al final (/…/g). Esta sintaxis permite que podamos englobar la expresión y modificarla con distintos valores que veremos a continuación:

ModificadorDescripciónEjemploExplicaciónValores válidosregex101
/…/gGlobal: busca todas las posibles coincidencias en el texto (no se detiene ante el primer hallazgo)./(je|jé)/g– Encuentra todos los caracteres «je» o «jé» en la cadena.Asere, ja deje tejebe tude jebere
Sebiunouba majabi an de bugui an de buididipí
Asere, ja deje tejebe tude je.
🔗
/…/mMultilínea: busca el patrón en cada línea del texto./Fin de la cita.$/gm– Encuentra la frase exacta «Fin de la cita.» al final de cada línea.Yo presido. Fin de la cita.
Y lo digo. Fin de la cita.
🔗
/…/iInsensitivo: busca el patrón en minúsculas o mayúsculas./a/gi– Encuentra la letra «a» sea minúscula o mayúscula.A de accesibilidad🔗
/…/u Unicode: busca el patrón incluyendo caracteres de UTF-16./voc\w/gu – Encuentra la frase «voc» seguida de cualquier caracter comprendido en  UTF-16.Assim você me mata 🔗

Funciones PHP

Una vez que tengamos el patrón de caracteres a buscar, podremos especificar una serie de funciones de PHP:

preg_replace:

Esta función de PHP busca un patrón de caracteres en un texto y lo reemplaza por otro que le especifiquemos.

<?php
/** 
* Mensaje que quiere evadir la política
* de comisiones de la plataforma.
*/
$mensaje = "Hola Juan: mi nombre es Mauricio y te dejo mi email para que 
			contactemos fuera de esta aplicación que cobra comisiones muy caras, 
			toma nota: mg@maugelves.com. Un saludo, Mauricio.";
/**
* Pero aquí viene la función justiciera.
* 
* En PHP existe una función mejor para validar emails, pero 
* usaremos funciones de expresiones regulares con 
* fines didácticos.
* http://php.net/manual/en/filter.filters.php
*/
// Patrón para encontrar direcciones de email
$patron = "/\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,6}\b/";
// Reemplazo cualquier email por el texto "correo eliminado"
$mensaje = preg_replace( $patron, "(correo eliminado)", $mensaje );
echo $mensaje;
/*
* Esto devuelve:
*
* Hola Juan: mi nombre es Mauricio y te dejo mi email para que 
* contactemos fuera de esta aplicación que cobra comisiones muy caras, 
* toma nota: (correo eliminado). Un saludo, Mauricio.
*
*/

preg_split:

Función ideal si queremos dividir un texto en un array por un patrón de texto. Veamos el siguiente ejemplo donde separamos un texto por espacios o comas.

<?php
/** 
* Fragmento del Quijote de la Mancha
*/
$texto = 	"Y, viéndole don Quijote de aquella manera, con muestras de tanta 
			tristeza, le dijo: Sábete, Sancho, que no es un hombre más que 
			otro si no hace más que otro.";
// Patrón para encontrar espacios y comas (,).
$patron = "/[\s,]+/";
// Guardo en un array todas las palabras separadas por comas o espacios.
$array = preg_split( $patron, $texto );
var_dump( $array );
/*
* Esto devuelve:
*
* array(30) { [0]=> string(1) "Y" [1]=> string(9) "viéndole" [2]=> string(3) "don" 
* [3]=> string(7) "Quijote" [4]=> string(2) "de" [5]=> string(7) "aquella" 
* [6]=> string(6) "manera" [7]=> string(3) "con" [8]=> string(8) "muestras" 
* [9]=> string(2) "de" [10]=> string(5) "tanta" [11]=> string(8) "tristeza" 
* [12]=> string(2) "le" [13]=> string(5) "dijo:" [14]=> string(7) "Sábete" 
* [15]=> string(6) "Sancho" [16]=> string(3) "que" [17]=> string(2) "no" 
* [18]=> string(2) "es" [19]=> string(2) "un" [20]=> string(6) "hombre" 
* [21]=> string(4) "más" [22]=> string(3) "que" [23]=> string(4) "otro" 
* [24]=> string(2) "si" [25]=> string(2) "no" [26]=> string(4) "hace" 
* [27]=> string(4) "más" [28]=> string(3) "que" [29]=> string(5) "otro." }
*
*/

preg_match:

Esta es tal vez la función más simple ya que devuelve un 1 si encuentra al menos un patrón de caracteres y un 0 en caso negativo o error. Vamos a definir un patrón para detectar si alguien escribe mal la palabra WordPress en un texto cualquiera (recuerden que lleva la letra W y P en mayúsculas).

<php
/** 
* Un fragmento de texto con la palabra WordPress mal escrita
*/
$texto = 	"Hola, tengo un problema con mi página web. Está hecha
			en wordpress y quería que alguien me eche una mano.
			¿Qué debo hacer?"; // Parece gracioso pero suelo recibir emails de este tipo.
// Patrón para encontrar WordPress mal escritos.
$patron = "/(wordpress|wordPress)+/";
// Guardo en un array todas las palabras separadas por comas o espacios.
$hay_error = preg_match( $patron, $texto );
var_dump( $hay_error );
/*
* Esto devuelve:
*
* int(1)
*
*/

¿Cómo usarlo en WordPress? Plugin Moderdonizer

Moderdonizer WordPress Plugin

Luego de la chapa que solté con tanto tecnicismo es hora de ver cómo podemos implementar estas expresiones regulares en WordPress. Solo cambiará el entorno ya que seguiremos utilizando las mismas funciones nativas de PHP.

Voy a detallar un ejemplo con una linda locura que se nos ocurrió junto a Álvaro Gómez (AKA @Mrfoxtalbot).

Ambos somos fieles seguidores del programa de radio «La vida moderna«, una show humorístico donde a lo largo del tiempo han desarrollado un lenguaje propio. Por ejemplo, en lugar de decir «comedia» dicen «commedia» para identificar el tipo de humor que ellos realizan (que a veces roza límites muy arriesgados) y también reemplazan las terminaciones «ción/sión» por «ció/sió», haciedo alusión (y un poco de burla) al idioma catalán.

Con el juego de palabras se nos ocurrió que podíamos hacer un plugin (el cual está en fase de aprobación 😝) y subirlo al repositorio. La finalidad del mismo es simplemente reemplazar el título y contenido de cualquier entrada o página con las palabras típicas del programa. Para ello debíamos definir en primer lugar las reglas gramaticales, dos de ellas resultaron simples pero la doble «m» tenía un poco más de chicha: Este es el detalle de cada regla:

  1. Reemplazar palabras que acaben en «ción» por «ció» cuando después venga un espacio, punto, coma, interrogación o exclamación.
  2. Reemplazar palabras que acaben en «sión» por «sió» cuando después venga un espacio, punto, coma, interrogación o exclamación.
  3. Reemplazar la letra «m» por doble «mm» cuando la consonante se encuentre entre vocales.

Con las reglas definidas me puse manos a la obra y luego de varias correcciones di con las expresiones regulares correctas. Las mismas las utilizo con la función preg_replace() de PHP cuando se ejecutan los filtros the_content y the_title.

El plugin, que de momento pueden encontrar en mi repositorio de Github, luce más o menos así:

<?php
/**
 * Esta función reemplaza el contenido Godo con palabras de la República Dictatorial de Moderdonia.
 * Estas son las reglas que se aplican:
 *
 * @author  Mauricio Gelves
 * @params  $content    string  El contenido del post
 * @returns             string  El contenido del post con las nuevas palabras moderdonizadas.
 */
function fn_modernonize( $content ) {
	// Regla 1: "ción" por "ció" (ver comentarios en función)
	$content = preg_replace('/ción(?=[ .!,?])/', 'ció', $content );
	// Regla 2: "sión" por "sió" (ver comentarios en función)
	$content = preg_replace('/sión(?=[ .!,?])/', 'sió', $content );
	// Regla 3: "m" por "mm" (ver comentarios en función)
	$content = preg_replace('/(?<=[aeiouáéíóú])m(?=[aeiouáéíóú])/', 'mm', $content );
	return $content;
}

Conclusión

No me sorprenderé ni me avergonzaré cuando vuelva a mi propio artículo para saber cómo funcionan las expresiones regulares. Espero que vosotros también la dejéis apuntada como favorita para consultar en cualquier momento. Lo que sí puedo contarles es que escribiendo este artículo, que llevó muchas horas de desarrollo, me ha hecho conocer mucho más esta potente tecnología y sentirme más seguro de cara a futuras implementaciones. Es lo bueno de tener un blog.