Introducción al Uso de Expresiones Regulares en Javascript

Contenido

  1. Expresiones Regulares

  2. Usos

  3. Declaración

  4. Validando Datos

  5. Extrayendo Sub-cadenas

  6. Modificando Cadenas

  7. Resumen

  8. Referencias

Expresiones Regulares

Las expresiones regulares, regexp o regex del inglés (Regular Expressions), son un método usado para describir un patrón de caracteres, que luego serán identificados en una cadena de texto.

Conforman un lenguaje propio, agnóstico a cualquier lenguaje de programación, y algunos editores de texto lo incorporan en sus herramientas de búsqueda. Por ejemplo: Sublime Text, Visual Studio, el comando grep, entre otros.

Usos

Las expresiones regulares son al programador, lo que una motosierra a un leñador. Si dominas su lenguaje, este te permite extraer y modificar cadenas de texto de una forma rápida y limpia, eliminando incansables bucles, condicionales y llamadas a los métodos split y join.

De igual forma, en Javascript las expresiones regulares son ampliamente utilizadas para el procesamiento y validación de datos: Emails, nombres de usuario, contraseñas, direcciones, etc.

Declarión

Las expresiones regulares en javascript son objetos del tipo RegExp, y pueden ser declaradas mediante literales encerradas entre barras /…/ o por el constructor.

//Con literales de RegExp
const withLiteral = /abc/ig

//Con constructor de RegExp
const y = 'y'
const withConstructor = RegExp('x'+ y +'z', 'ig')

Las literales pueden ser declaradas con un bandera, que modifica el comportamiento, la i establece que ha de ignorarse si los caracteres estan es mayusculas o minusculas; la g o bandera global, establece que se han de buscar emparejamientos en toda la cadena.

Escapando Caracteres

Algunos caracteres, poseen un significado especial en expresiones regulares, por ejemplo: el signo + es un cuantificador, si queremos buscar una cadena que contenga este signo es necesario escaparla usando la barra invertida /+/.

Utilizando literales no hay mayor inconveniente, sin embargo, el constructor de RegExp, recibe como parámetro una cadena de texto, y en este caso, la barra invertida ya tiene un significado especial de por sí, por lo tanto, hay que escaparlo, el ejemplo anterior quedaría así: “\+”.

De esta forma, vemos que las literales nos ahorran hacer un doble escapado, a costa de ser estáticas, el método del constructor nos permite generar expresiones regulares de forma dinámica.

Referencias

En el sitio web de microsoft para developers puedes revisar una pequeña referencia sobre los caracteres expeciales de las expresiones regulares.

Si necesitas algo más detallado, tambien puedes revisar la documentación en Mozila Developer Network(MDN) sobre expresiones regulares.

La clase RegExp posee el método test al cual se le pasa por parámetro una cadena de texto, y comprueba si se ajusta a la expresión regular utilizada.

const phone = '0412-051-34-32'
const phonePattern = /\d{4}-\d{3}-\d{2}-\d{2}/

if(phonePattern.test(phone)) {
    console.log(`${phone} is a valid phone number`)

} else {
    console.log(`${phone} is an invalid phone number`)
}

//Expected output: 0412-051-34-32 is a valid phone

En el lenguaje de las expresiones regulares, el carácter \d sirve para identificar un digito, si se le une al cuantificador {n}, donde n es 4, establece que la expresión debe identificar 4 caracteres de tipo digito.

Por otro lado, la clase String proporciona el método search que devuelve el índice del primer emparejamiento en la cadena, o -1 en caso de no existir.

let index = phone.search(phonePattern)
if(index != -1) {
    console.log(`${phone} is a valid phone number in position: ${index}`)

} else {
    console.log(`${phone} is an invalid phone number`)
}
//Expected output: 0412-051-34-32 is a valid phone number in position: 0

Extrayendo Subcadenas

Como ya he mencionado, un uso bastante potente de las expresiones regulares es la capacidad de extraer sub-cadenas de texto. Se puede hacer de distintas formas, pero las más seguras son mediante los métodos match y matchAll de la clase String.

Usando .match()

El uso de la bandera g en la expresión regular cambia el comportamiento de match, al estar ausente, devuelve una arreglo el cual actua como una descripción detallada del emparejamiento encontrado: en la primera posicicón, el emparejamiento completo, seguido de los grupos de captura, e incluye un objeto index que informa de la posición en la cadena de texto.

Grupos de Captura: Son definidos mediantes paréntesis en las expresiones regulares, se comportan como sub-expresiones y es posible acceder a ellos individualmente.

let screenSize = "el tamaño de la pantalla es 780x1024"
let screenSizePattern = /(\d{3,})x(\d{3,})/i

let detallesDelEmparejamiento = screenSize.match(screenSizePattern)
let [emparejamientoCompleto, alto, ancho] = detallesDelEmparejamiento

console.log('el ancho es: ' + ancho + 'px\nel alto es: ' + alto + 'px')

Cuando la bandera g está presente, el metodo match retorna un array con todos los emparejamientos encontrados, ignorando los grupos de captura.

let multipleScreens = '780x1024\n3000x2000\n640x860\n1024x2000\n'
let screenSizePatternGlobal = /(\d{3,})x(\d{3,})/ig

console.log(multipleScreens.match(screenSizePatternGlobal))
//expected output: ["780x1024", "3000x2000", "640x860", "1024x2000"]

Usando .matchAll()

A veces deseamos un resultado más detallado, y para ello recurrimos a matchAll, el cual retorna un iterador con todos los emparejamientos de la cadena; cada elemento del iterador es un objeto similar al retornado por match sin la bandera global.

Debido a que retorna un iterado, es muy práctico de utilizar con el bucle for-of, Array spread y Array.from().

//Con for-of
for (const match of multipleScreens.matchAll(screenSizePatternGlobal)) {
    console.log(`match: ${match[0]}, 
                ancho: ${match[1]}, 
                alto: ${match[2]}, 
                start: ${match.index}, 
                end: ${match.index + match[0].length}`)
}

//Usando Array Spread
let matches = [...multipleScreens.matchAll(screenSizePatternGlobal)]

//Creando un array
let arrayOfMatches = Array.from(multipleScreens.matchAll(screenSizePatternGlobal), m => m[0])

Cabe señalar que matchAll acepta únicamente expresiones regulares con la bandera global, de lo contrario lanza una excepción.

Estos dos métodos están implementados alrededor del método exec de la clase RegExp, el cual es un poco confuso y es preferible evitarlo, en pro de match y matchAll.

Modificando Cadenas

Otro uso bastante potente para las expresiones regulares es la modificación de textos con replace; el lenguaje de las expresiones regulares establece una sintaxis en estos casos.

Podemos establecer cómo debe de quedar la porción de cadena emparejada, pasando como segundo parámetro una cadena de texto, y mediante el signo $ podemos formatear la nueva cadena: $& hará referencia a la cadena completa y con $1, …, $9 accederemos a los grupos de captura definidos en la expresión regular.

let names = "Liskov, Barbara\nMcCarthy, John\nWadler, Philip".replace(/(\w+), (\w+)/g, "$& -> $2 $1")

console.log(names);
// Expected output:
// Liskov, Barbara -> Barbara Liskov
// McCarthy, John -> John McCarthy
// Wadler, Philip -> Philip Wadler

Así mismo, también es posible pasar una función como segundo parámetro de replace, esta ha de recibir como primer parámetro, el match completo, seguido de los n grupos de captura que hayamos especificado en nuestra expresión regular.

La función pasada a replace, será llamada con cada uno de los emparejamientos, y se debe retornar algún valor que será colocado en su lugar.

let title = 'Artist1 - Some Song Title (ft. Artist2, Artist3)'
let regexp = /(\w+) - (.+) \(ft\. (.+)\)/i

title = title.replace(regexp, (_, artist, song, fts) => `${song} by ${artist} & ${fts}`)

console.log(title)
//Expected output: Some Song Title by Artist1 & Artist2, Artist3

Resumen

En este artículo hemos descubierto qué son las expresiones regulares, sus usos, así como las formas de emplearlas en el lenguaje javascript: Validación de datos, extracción de emparejamientos, así como el formateo de cadenas.