Declaración y ejecución de una expresión regular

Podemos declarar una expresión regular como var reg = /.*/gmi;. Es lo que se llama una construcción literal, porque al final lo que estamos haciendo es crear un nueva instancia del objeto de JavaScript window.RegExp. Lo anterior equivale a var reg = new window.RegExp(".*", "gmi");. Esta última forma tiene la ventaja de que podemos modificar dinámicamente el patrón ".*" y los modificadores "gmi" dado que son tipos String.

A partir de ese momento tendríamos en la variable reg un objeto de expresión regular. Y entonces podemos ejecutarla sobre un texto para obtener un resultado. Los posibles métodos de JavaScript para ejecutar la expresión regular reg son:

  • Métodos de cadena (String):
    • Evaluar: texto.search(reg)
    • Buscar: texto.match(reg)
    • Dividir: texto.split(reg[, límite])
    • Reemplazar: texto.replace(reg, reemplazo)
  • Métodos del objeto expresión regular:
    • Evaluar: reg.test(texto)
    • Buscar: reg.exec(texto)

En la herramienta probador de expresiones regulares podrá comprobar los ejemplos expuestos aquí.

Métodos search() y test() para evaluar texto

El método search se aplica sobre un String para buscar una primera coincidencia de una expresión regular sobre un String. Nos devuelve un entero no negativo con la posición de esa coincidencia. Las posiciones de los caracteres se cuentan desde cero. Si no hay coincidencia devolverá -1. Un ejemplo simple:

var texto = "En un lugar de la Mancha";
var reg = /un/;
var posicion = texto.search(reg);
console.log(posicion);
//Nos devuelve la posición 3
    

La búsqueda de la primera coincidencia es indiferente al modificador "g", pero no al "i" para el caso de búsquedas donde sea indifierente maýusculas y minúsculas. Por ejemplo, si queremos buscar la palabra "Mancha" en el texto "En un lugar de la Mancha" usando diferentes versiones de la palabra y con o sin "i" podríamos obtener estos resultados:

Como argumento del método search() podemos pasar una variable de instancia de expresión regular, igual que el ejemplo anterior con reg. O también un literal como /Mancha/i. E incluso un String como "Mancha". En este caso será convertido en un patrón de expresión regular por el navegador usando new RegExp(String), aunque no podremos especificar los modificadores. Hay que tener en cuenta que algunos caracteres necesitarán ser escapados y que la conversión pudiera producir un error.

En el siguiente ejemplo vemos esto y además como el método search() es equivalente a otro método indexOf() de uso frecuente:

Ejemplo: Método search()

Los dos primeros ejemplos buscan la palabra "Mancha" que si existe en el texto. Con el método search() pasamos un String que se convierte en la expresión regular /Mancha/. El método indexOf() es, en este caso, totalmente equivalente. También lo es para la palabra "mancha" que no existe en el texto. Con search() para hacer la búsqueda insensible a mayúsculas hemos de poner necesariamente el patrón /mancha/i en lugar del string "mancha".

Vea como el último ejemplo da un error que hemos interceptado, error que hemos puesto a propósito dado que hemos dejado un paréntesis sin cerrar. Si vamos a usar String dentro de search() hemos de tener cuidado con este detalle de conversión a expresión regular que hace el navegador. Por eso creo que es recomendable usar indexOf() si estamos tratando con String y, por otro lado, exclusivamente expresiones regulares si tratamos con search().

El método test() se aplica sobre la expresión regular y su argumento es el String donde vamos a evaluar si el patrón coincide, devolviendo un booleano verdadero o falso.

var texto = "En un lugar de la Mancha";
var reg = /lugar/;
console.log(reg.test(texto));
//Devuelve true
console.log(texto.search(reg)>-1);
//Devuelve true
console.log(texto.indexOf("lugar")>-1);
//Devuelve true
    

En el código anterior hemos usado las tres posibilidades para evaluar si la palabra "lugar" existe en el texto: con test(), con search() y con indexOf(). Si con search() podíamos usar un String o un objeto de expresión regular indistintamente, ahora para test() no es posible usar un String.

Métodos match() y exec() para buscar texto

El método match() con búsqueda global (modificador "g") busca una o todas las coincidencias de una expresión regular sobre un String, devolviéndolas en un array. Sin búsqueda global devuelve también un array con la primera coincidencia en la posición cero del array y en el resto de posiciones estarán las coincidencias con los grupos de captura.

Por otro lado tenemos el método exec() que hace lo mismo que match() sin "g". Este método se ejecuta sobre la expresión regular, siendo su argumento el String. Con una búsqueda global (modificador "g") y con cada ejecución modificará las propiedades index y lastIndex.

La propiedad index es del array de resultados y nos da la posición del carácter en el texto donde se encontró la primera coincidencia. En la ejecución de match() con "g" nos dará "undefined", porque es el único caso que busca todas las coincidencias. Por otro lado lastIndex es una propiedad de la instancia u objeto de expresión regular, conteniendo tras cada ejecución de exec() con "g" la posición del carácter donde comenzará la siguiente ejecución del método.

Una tabla nos ayudará a resumir todo esto:

GlobalMétodoArray resultados busbus.indexreg.lastIndex
NOtexto.match(reg)bus[0] contiene la primera coincidencia con el patrón completo y en bus[1] a bus[n] estan las coincidencias con los grupos de captura.Número0
reg.exec(texto)
texto.match(reg)bus[0] a bus[n] contienen todas las coincidencias con el patrón completo.undefined0
reg.exec(texto)bus[0] contiene la primera coincidencia con el patrón completa y en bus[1] a bus[n] estan las coincidencias con los grupos de captura.NúmeroNúmero

El código siguiente es un ejemplo simple para usar el método match() con búsqueda global. El patrón buscará todos los grupos de tres letras que tengan una vocal en medio. E resultado de la búsqueda estará en la variable bus. Si no es nula, tendremos un array con esos resultados "lug", "Man":

var texto = "En un lugar de la Mancha";
var reg = /(\w)([aeiou])(\w)/g; //con "g" para match
var bus = texto.match(reg);
if (bus) console.log(bus);
//Devuelve bus = ["lug", "Man"]
    

Por otro lado para match() sin "g" y para exec() con o sin "g" obtendremos un array con otros resultados:

var texto = "En un lugar de la Mancha";
var reg = /(\w)([aeiou])(\w)/; //sin "g" para match
var bus = texto.match(reg);
if (bus) console.log(bus);
//Devuelve bus = ["lug", "l", "u", "g", 
//                 index: 6, 
//                 input: "En un lugar de la Mancha"]
//Igual para bus=reg.exec(texto) con o sin "g"
    

El array bus contiene en su posición cero la primera coincidencia con el patrón completo ("lug"). En el resto de posiciones estarán las coincidencias con los grupos de captura ("l", "u", "g"). Dos propiedades siempre serán agregadas, una es index que ya mencionamos más arriba y que contiene la posición donde se encontró la coincidencia completa "lug", en este caso la posición 6 en el texto. La otra propiedas es input que contiene el texto.

Con match() y "g" podemos buscar todas las coincidencias. También podemos hacerlo con exec() y "g" en un bucle. Obtendremos un array con arrays de búsquedas parciales, cada uno de ellos con la coincidencia completa y sus grupos de captura.

var texto = "En un lugar de la Mancha";
var reg = /(\w)([aeiou])(\w)/g; 
var bus = [], coin;
while ((coin = reg.exec(texto)) !== null) {
    bus[bus.length] = coin;
}
console.log(bus);
/* Devuelve bus = 
[["lug", "l", "u", "g"
     index: 6
     input: "En un lugar de la Mancha"],
 ["Man", "M", "a", "n"
     index: 18
     input: "En un lugar de la Mancha"]] */

El siguiente ejemplo interactivo le permitirá hacer más pruebas usando como patrón uno que ya vimos en un tema anterior. Se trata de buscar palabras de un número dado de letras.

Ejemplo: Métodos match() y exec() para buscar texto

Texto:
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.
Usando el método
Declara patrón con la configuracion anterior y ejecuta ese método
Expresión regular (instanciada como reg):
  
Coincidencias 0 en el array de Resultados (bus).
bus.index: 0
reg.lastIndex: 0
Vuelve a ejecutar el método. Sólo será efectivo con exec() y búsqueda global "g", que es el único caso que modifica esta propiedad en cada ejecución.

Método split() para dividir texto

El método split() divide un String en Strings y las introduce en un array usando como separador un String o una expresión regular declarado en su primer argumento. El segundo argumento es opcional para limitar el máximo de entradas en el array.

var texto = "a, b, c, d, e, f";
var array1 = texto.split(", ");
console.log(array1);
//devuelve el array ["a", "b", "c", "d", "e", "f"]
var array2 = texto.split(/\s*,\s*/, 3);
console.log(array2);
//devuelve el array ["a", "b", "c"] con 3 entradas
    

En este código de ejemplo metemos en un array una cadena de letras separadas por comas. Usando como separador la cadena ", " nos arriesgamos a que en el texto puedan aparecer comas con espacios antes o bien sin un espacio después. En estos casos es preferible usar la expresión regular /\s*,\s*/ para contemplar esas posibilidades. Vea como el limitador sólo devuelve las tres primeras posiciones.

Si el patrón contiene grupos de captura también realizará una división por ellos:

var texto = "1) Primera.\n 2) Segunda.";
var partes = texto.split(/\s*(\d+)\)\s*/);
console.log(partes);
//Devuelve ["", "1", "Primera.", "2", "Segunda."]
    

En este ejemplo aparecerá una primera entrada vacía en el array pues el separador es "1)" y considera una parte la cadena vacía antes de ese separador. Vea como también incluye entradas de los dígitos del grupo de captura.

Método replace() para reemplazar texto

El método replace() nos permite reemplazar texto en un String con alguna o todas las coincidencias encontradas por el patrón o String de su primer argumento, reemplazándolas por el String de su segundo argumento. También es posible usar una función en este segundo argumento.

Reemplazando con String

La forma más simple de usar replace() es buscando un String. En el siguiente ejemplo reemplaza "IE" por "Internet Explorer".

var texto = "Firefox, Chrome, IE";
texto = texto.replace("IE", "Internet Explorer");
console.log(texto);
//Devuelve "Firefox, Chrome, Internet Explorer"
    

Reemplazando con expresiones regulares

El reemplazo con String es bastante limitado, porque por ejemplo sólo reemplaza la primera coincidencia encontrada. En el siguiente ejemplo usamos ambos, consiguiendo con la expresión regular y el modificador de búsqueda global ("g") ejecutar todos los reemplazos:

var texto = "abc, abc, abc";
texto = texto.replace("abc", "ABC");
console.log(texto);
//Devuelve "ABC, abc, abc"
texto = texto.replace(/abc/g, "ABC");
//Devuelve "ABC, ABC, ABC"
    

Usando una expresión regular tenemos en el segundo argumento de reemplazo la posibilidad de utilizar algunas de las propiedades del objeto RegExp que vimos en el primer tema como plantillas String.

PropiedadDescripción
$&Reemplaza la coincidencia con el patrón completo.
$n, $nnReemplaza la coincidencia con los grupos de captura 1 a 9 o 01 a 99.
$`Reemplaza la porción de la cadena que precede a la coincidencia con el patrón completo.
$'Reemplaza la porción de la cadena que sigue a la coincidencia con el patrón completo.
$$Permite usar "$" como un carácter literal.

Usando estas propiedades en el siguiente ejemplo vamos a ocultar las direcciones de correo eléctronico cambiando el carácter "@" por "_at_":

var texto = "Usar info@example.com para consultar";
var textoModif = texto.replace(/(\s+[^@]+)@([^\s]+\s+)/g, "$1_at_$2");
console.log(textoModif);
//Devuelve "Usar info_at_example.com para consultar"
    

Los dos grupos de captura son las partes del email antes y después de "@", que luego remplazamos con "$1_at_$2". También hemos de resaltar que replace() no modifica el texto sobre el que se aplica. En el ejemplo verá que el resultado se devuelve en textoModif de tal forma que la variable texto permanece sin cambio alguno.

Entendiendo el segundo argumento String de replace()

En el segundo argumento de replace() ponemos un String con las plantillas "$" o también una expresión que devuelva un String. Pero el uso de las plantillas "$" require entender como se procesan antes y después de ejecutar la expresión regular.

En el siguiente ejemplo pretendemos reemplazar las unidades de medida "M2" y "Cm3" por literales HTML poniendo todo en minúsculas y usando el elemento <sup> de superíndice, debiendo quedar como "m<sup>2</sup>" y "cm<sup>3</sup>". El método toLowerCase() es del objeto String y convierte a minúsculas. Por otro lado el método sup() también es del objeto String y rodea una cadena con los tags <sup> y </sup>. Vamos a usarlos en el segundo argumento para ver si conseguimos el objetivo:

var texto = "de 20 M2 y 150 Cm3";
var textoModif1 = texto.replace(/\b([a-z]+)(\d)\b/gi, 
    "$1".toLowerCase() +  "$2".sup());
console.log(textoModif1);
//Devuelve "de 20 M<sup>2</sup> y 150 Cm<sup>3</sup>"
    

Vemos que el argumento de reemplazo es una expresión que devuelve una cadena, concatenado el primer grupo de captura convertido a minúsculas y el segundo grupo de captura con tag de superíndice. Esto último lo hizo correctamente pero no convirtió a minúsculas.

El motivo es que antes de ejecutar la expresión regular, JavaScript ha de evaluar la expresión "$1".toLowerCase() + "$2".sup() resultando en "$1<sup>$2</sup>". La conversión a minúsculas de "$1" no produce ningún efecto, pues en este momento los caracteres "$" no significan otra cosa para JavaScript. Tras esto procede a ejecutar la expresión regular y, efectivamente, no realiza ningún conversión a minúsculas pues la cadena del segundo argumento es ahora "$1<sup>$2</sup>".

Reemplazando con funciones callback

Podemos usar expresiones en el argumento de reemplazo, incluyendo métodos y funciones que modifiquen las plantillas "$", pero esos métodos y funciones serán ejecutados siempre antes de la ejecución de la expresión regular. Para lograr que se ejecuten después hemos de usar una función como segundo argumento, el llamado callback para reemplazo:

var texto = "de 20 M2 y 150 Cm3";
var textoModif2 = texto.replace(/\b([a-z]+)(\d)\b/gi, 
    function(captura0, captura1, captura2){
        return captura1.toLowerCase() + captura2.sup();
    });
console.log(textoModif2);
//Devuelve "de 20 m<sup>2</sup> y 150 cm<sup>3</sup>"
    

Cada vez que se produce una coincidencia con el patrón entra en la función de reemplazo. Especificaremos tantos argumentos como capturas y usando cualquier nombre de argumento. Siempre el primer argumento será la coincidencia con el patrón completo y el resto los grupos de captura en su orden. Muchas veces veremos esos argumentos como function($0, $1, $2) puesto que una variable que empiece por $ es válida en JavaScript. Pero debe entenderse que son nombres de variables y no las plantillas "$" del String de reemplazo.

Dentro de la función manejaremos esos argumentos como proceda y retornaremos el resultado que será reemplazado en la coincidencia. Esto se producirá tantas veces como coincidencias existan si hay un modificador "g" o bien una única vez sin ese modificador.

La función puede declararse aparte, especialmente si vamos a utilizarla en varios replace() ubicados en distintos puntos del código:

function minusSup($0, $1, $2){
    return $1.toLowerCase() + $2.sup();
};
var texto = "de 20 M2 y 150 Cm3";
var textoModif2 = texto.replace(/\b([a-z]+)(\d)\b/gi, minusSup);
console.log(textoModif2);
//Devuelve "de 20 m<sup>2</sup> y 150 cm<sup>3</sup>"
    

Observe como el segundo argumento de replace() es sólo una llamada a la función minusSup, sin argumentos.

Usando replace() con callback y arguments

Si queremos usar una función callback para replace() con un número arbitrario de grupos de captura hemos de echar mano del recurso arguments del objeto Function. Sobre cualquier función podemos acceder al array de argumentos con esta palabra clave arguments. Para el callback de reemplazo tenemos los siguientes argumentos:

  • Argumento 0 ($0) contiene la coincidencia con el patrón completo.
  • Argumentos 1 hasta n-2 ($i) contienen las coincidencias con los grupos de captura.
  • Argumento n-1 (offset) contiene la posición del carácter donde se produjo la coincidencia del patrón completo.
  • Argumento n (string) contiene el texto sobre el que se está reemplazando.

Los nombres de los argumentos pueden ser cualesquiera y los que aparecen en cursiva entre paréntesis los ponemos sólo a efectos de denominación, porque realmente no es necesario declararlos explícitamente. Retomamos el ejemplo del apartado anterior con alguna mejora.

Ahora tenemos el texto "de 3 a 5 Kg, 20 M2 y 150 Cm3 de volumen" y queremos volcarlo en el HTML así: "de 3 a 5 kg, 20 m2 y 150 cm3 de volumen". Se trata de rodear los números con <b>, pasar a minúsculas las unidades de medida y a superíndices las potencias. El script usando un callback y arguments que se puede probar en la consola del navegador es el siguiente:

function minusSup(){
    var cad = "";
    if (arguments){
        //Buscando $1
        if ((arguments.length > 3) && 
            (typeof(arguments[1])!="undefined")){ 
            //$0, $1, offset, string: 4 argumentos
            cad += '<b>' + arguments[1] + '</b>';
        }
        //Buscando $2
        if ((arguments.length > 4) && 
            (typeof(arguments[2])!="undefined")){ 
            //$0, $1, $2, offset, string: 5 argumentos
            cad += arguments[2].toLowerCase();
        }
        //Buscando $3
        if ((arguments.length > 5) && 
            (typeof(arguments[3])!="undefined")){
            //$0, $1, $2, $3, offset, string: 6 argumentos
            cad += arguments[3].sup();
        }
    }
    return cad;
};
var texto = "de 3 a 5 Kg, 20 M2 y 150 Cm3 de volumen";
var textoModif = texto.replace(/\b(\d+)(\s+[a-z]+)?(\d)?\b/gi, 
    minusSup);
console.log(textoModif);
//"de <b>3</b> a <b>5</b> kg, <b>20</b> m<sup>2</sup> 
//y <b>150</b> cm<sup>3</sup> de volumen"
    

El patrón es ahora /\b(\d+)(\s+[a-z]+)?(\d)?\b/gi, observándose tres grupos de captura, con los dos últimos opcionales. Así cubrimos el número "3" sin unidades de medida y "5 kg" sin potencia en la unidad de medida. En el callback vemos si arguments es no nulo y vamos preguntando cuántos argumentos hay. Siempre tendremos que sumar los dos últimos offset y string. Puesto que como mínimo un callback tendrá tres argumentos $0, offset y string, hemos de preguntar si tiene más de tres para descubrir el primer grupo de captura.

Un grupo de captura puede resultar que devuelva undefined si se produce coincidencia en el patrón completo pero no en ese grupo en particular. Hemos de detectar esto porque no podemos aplicarlo en su caso, es decir, no podemos ejecutar toLowerCase() y cualquier método sobre un tipo undefined.

Puede ver ese ejemplo a continuación que se ejecuta tras la carga de la página devolviendo el resultado en HTML:

Ejemplo: Usando replace() con callback y arguments

Este ejemplo se ejecuta tras la carga de la página, rodeando con el tag <b> los números, convirtiendo a minúsculas las unidades de medida y poniendo superíndices en las potencias de las unidades de medida del siguiente texto.

Texto:
"de 3 a 5 Kg, 20 M2 y 150 Cm3 de volumen"
Patrón:
/\b(\d+)(\s+[a-z]+)?(\d)?\b/gi
Resultado texto:
Resultado HTML: