MarioGiron.com

Vitamina tus scripts con expresiones regulares

31 October, 2018

Son numerosas las ocasiones en las que un desarrollador Javascript se debe enfrentar a la difícil tarea de buscar, filtrar o validar ciertos datos en función de algunos valores específicos.

El error en este caso sería inundar nuestro código con sentencias if anidadas que comprueben cada una de las opciones disponibles. Esto nos llevaría irremediablemente a cometer uno de los errores más comunes cuando estamos empezando en este mundillo de la programación, el código se vuelve bastante ilegible y el mantenimiento resulta extremadamente complicado, ya no sólo para nosotros, si no para el programador que recoja nuestro código.

Es por ello que, la mayoría de las veces, todos estos problemas los podemos solucionar con el uso de las expresiones regulares.

Las expresiones regulares son construcciones que el programador utiliza para definir una serie de patrones, los cuales posteriormente podremos comparar con los datos recibidos en nuestros script para poder realizar búsquedas, filtros, comprobaciones… Se trata de una herramienta muy poderosa y que, desarrollando todo su potencial puede ayudarnos a optimizar nuestras aplicaciones.

Podemos crear una expresión regular de dos manera diferentes. La primera sería de manera literal, almacenando su valor dentro de una variable:

let exp = /abc/

Las expresiones regulares son objetos, por lo que tendremos que tratarlas como tales. En caso de crearlas como el ejemplo anterior, la expresión regular es compilada cuando se cargue el script que la contiene. Si vamos a mantener constante la expresión regular, el uso de esta nomenclatura es la más óptima para nuestros desarrollos.

De todas maneras, siempre podemos crear nuestros patrones a partir del constructor del objeto RegExp:

let expObj = new RegExp('abc')

En este caso, la compilación de esta expresión regular se realiza en tiempo de ejecución, por lo que el gasto de recursos es mayor. Usaremos esta opción cuando sepamos a ciencia cierta que nuestra expresión puede ser modificada o cuando tengamos que recuperar su valor de una fuente externa a nuestro script, como podría ser un fichero.

En este caso, los dos objetos representan un mismo patrón. Una letra ‘a’ seguida de una letra ‘b’ y posteriormente una letra ‘c’. La única diferencia entre ambas se encuentra en su creación y en el uso del carácter / para delimitar el patrón definido por la primera expresión regular.

Probando nuestras expresiones regulares

Este tipo de objetos cuentan con numerosos métodos aplicables en diferentes ámbitos. Incluso podemos utilizar algunos métodos relacionados con la clase String. El más sencillo de todos es el método test.

Mediante este método comprobamos si la cadena de caracteres que pasamos por parámetro cumple el patrón sobre el cual lanzamos el método. Algún ejemplo usando las expresiones anteriores serían los siguientes:

> exp.test('abc')
true
> exp.test('abcd')
true
> exp.test('adbcd')
false
> exp.test('saabcd')
true

Será verdadero siempre y cuando se encuentre el patrón establecido dentro del string correspondiente, independientemente de la posición que ocupe dentro del total de la cadena.

Llegados a este punto, cualquiera de los ejemplos anteriores los podríamos haber alcanzado con una simple llamada al método indexOf, pero las expresiones regular van un paso más allá y nos permiten definir patrones más complejos para representar cualquier tipo de cadena con la que necesitemos trabajar.

Uno de los patrones más utilizados nos permite especificar un rango de caracteres para compararlo con los datos que recibamos por parámetro. Para especificar este tipo de rangos, debemos contener entre corchetes todos los caracteres que queramos incluir. Por ejemplo, si queremos comprobar si existe algún número dentro de la cadena del parámetro, podemos hacerlo de la siguiente manera:

> /[0123456789]/.test('estamos en 2018')
true
> /[0123456789]/.test('este año no es bisiesto')
false

De todas maneras, esta forma de especificar un rango, todavía no es demasiado óptima. Mediante el uso del guión entre dos caracteres podemos definir qué rango de caracteres vamos a usar para la creación de nuestra expresión. Por ejemplo, el uso de [0-9] como expresión regular tendría el mismo efecto que las utilizadas en el ejemplo anterior (todos los caracteres situados entre el 0 y el 9).

De la misma manera que trabajamos con caracteres numéricos, podemos definir nuestros patrones sobre letras. Aplicamos los mismos conceptos anteriores:

> /[a-z]/.test('buscando caracteres')
true
> /[a-z]/.test('BUSCANDO CARACTERES')
false
> /[A-Z]/.test('BUSCANDO CARACTERES')
true

Aunque con el uso de rangos ya podemos simplificar bastante nuestras expresiones, siempre podemos hacerlo más, ya que existen varias construcciones sencillas que nos permiten hacer referencia a ciertos rangos muy usuales:

\d - representa cualquier dígito \w - un carácter alfanumérico \s - cualquier espacio en blanco \D - un carácter distinto de un dígito \W - un carácter no alfanumérico \S - un carácter distinto de espacio en blanco . - cualquier carácter, excepto el salto de línea

Por lo tanto, conociendo estos atajos, ya podemos construir patrones más complejos. Si necesitamos comprobar el formato de una fecha, podemos proceder de la siguiente manera:

let datePatt = /\d\d-\d\d-\d\d\d\d \d\d:\d\d/
> datePatt.test('20-09-1983 23:54')
true
> datePatt.test('20-Sep-1983 23:54')
false

De todas maneras, el uso de este tipo de patrones sigue siendo bastante ineficiente, ya que nos obliga a repetir cada uno de los tipos específicos para todos los caracteres que queremos comprobar. Para poder facilitarnos el trabajo de generar nuestras expresiones, contamos con una serie de operadores.

Si utilizamos el operador + a continuación de cualquier expresión regular, estamos especificando que el elemento se puede repetir una o más veces. Por ejemplo, siguiendo con la práctica de los números:

let numPatt = /\d+/
> numPatt.test('1')
true
> numPatt.test('13231')
true
> numPatt.test('abc')
false

La expresión regular anterior comprueba sin en la cadena recibida existe un número o más de uno. El único momento en el que nos devuelve false es cuando no encontramos ningún número dentro del literal.

Si en vez del operador + usamos el operador asterisco (*) estamos indicando que esperamos un carácter, repetidos o ninguno:

> let numPatt2 = /\d*/
> numPatt2.test('123')
true
> numPatt2.test('a')
true
> numPatt2.test('')
true

Disponemos también del uso de ? para indicar que un carácter o grupo de caracteres pueden ser opcionales dentro de la cadena con la que estamos trabajando. En el siguiente ejemplo definimos la letra d como opcional.

let opcPatt = /abc?d/
> opcPatt.test('abcd')
true
> opcPatt.test('abd')
true

Con los operadores + y * definimos la repetición del carácter al que le aplicamos dicho operador, pero no especificamos cuántas veces se puede repetir dicho carácter. Para poder concretar el número exacto de repeticiones, podemos acompañar la expresión regular del número de repeticiones entre llaves.

Modificamos el ejemplo de las fechas para que se adapte a este formato:

datePatt = /\d{1,2}-\d{1,2}-\d{4} \d{1,2}:\d{1,2}/
> datePatt.test('20-09-1983 23:54')
true
> datePatt.test('20-Sep-1983 23:54')
false

De momento, estos operadores los hemos estado aplicando sobre un unico elemento dentro de la definición de nuestros patrones. Mediante el uso de los paréntesis podemos agrupar ciertos caracteres dentro de nuestras expresiones regulares para poder aplicar a todos en conjunto el operador que necesitemos en cada caso.

Vemos un pequeño ejemplo.

optPatt = /a(bc)?d/
> optPatt.test('abcd')
true
> optPatt.test('ad')
true
> optPatt.test('abd')
false

Aplicando el operador opcional sobre el grupo bc conseguimos un patrón en el cual esos dos elementos son opcionales. En la tercera ejecución observamos como no podemos incluir uno solo de los elementos, deben coexistir los dos para que se cumpla con los requisitos especificados.

Métodos que nos permiten trabajar con expresiones regulares

Los ejemplos sobre expresiones regulares sencillas los hemos realizado gracias al método test, el cual simplemente nos devuelve si se ha encontrado o no el patrón especificado dentro de la expresión regular en el literal que pasamos por parámetro. Pero existen otra serie de métodos que podemos utilizar para sacar mayor partido al uso de este tipo de objetos.

El primero es el método exec correspondiente a la clase RegExp, el cual nos permite realizar una búsqueda dentro de una cadena a partir de un patrón concreto. Devuelve un array con información referente a la búsqueda.

var expReg = /ab(c+)d/i
> expReg.exec('1234abccCcd')
[ 'abccCcd', 'ccCc', index: 4, input: '1234abccCcd' ]

La ejecución del método nos devuelve la cadena que coincide con el patrón, en este caso, al trabajar con un carácter repetido, también nos devuelve el literal que coincide con dicha repetición, el índice dentro de la cadena donde encontramos el patrón y por último la cadena original. En caso de no encontrar ninguna ocurrencia obtendriamos null.

El elemento i en la última posición de la expresión regular nos indica que no se van a tener en cuenta las mayúsculas y las minúsculas para realizar la búsqueda.

El siguiente de los métodos interesantes que podemos aplicar dentro de nuestros desarrollos es match. Este método nos permite, llamado sobre una cadena de caracteres, obtener aquellas partes que coincidan con la expresión regular que pasamos por parámetro. Aparte, la llamada a dicha función nos devuelve un array con aquellas ocurrencias.

var str = 'A las dos fueron las dos. No tuvieron modos'
> str.match(/dos\s/g)
[ 'dos ' ]

En este ejemplo, nuestra expresión regular buscará en la cadena todos las ocurrencias de la palabra dos seguida de un espacio. Al sólo encontrarla en una única ocasión, nos devuelve un array con una posición.

Para terminar, vamos a ver una serie de ejemplo utilizando algunas funciones que aceptan expresiones regular sobre cadenas de caracteres:

// Modificamos la posición de una cadena con replace
var expRep = /(\w+)\s(\w+)/

var nombre = "Antonio Garcia"
var newNombre = nombre.replace(expRep, '$2, $1')
console.log(newNombre)
> Garcia, Antonio

// Comprobamos un nombre de usuario con mayúsculas y minúsculas entre 3 y 16 caracteres
var usernameRe = /^[a-zA-Z0-9_-]{3,16}$/

var OK = usernameRe.exec('nomBre_UsuAriO13')
if(!OK){
console.log('El nombre de usuario no es válido')
}else{
console.log('El nombre de usuario '+ OK[0] +' es correcto')
}
> El nombre de usuario nomBre_UsuAriO13 es correcto

// Comprobamos si un mail es correcto
var mailRe = /^([a-z0-9_\.-]+)@([\da-z\.-]+)\.([a-z\.]{2,6})$/

mailRe.exec('correo@gmail.com')
[ 'correo@gmail.com',
  'correo',
  'gmail',
  'com',
  index: 0,
  input: 'correo@gmail.com' ]

mailRe.exec('correo@gmail')
null

mailRe.exec('correo@.com')
null

Siguiendo estos ejemplos e investigando sobre las expresiones regulares, podemos conseguir alcanzar un nivel extra de optimización para nuestras aplicaciones.


Perpetrado por Mario Girón, desarrollador Full Stack y formador Senior
Puedes seguirme en: Twitter | Linkedin | Github

© 2020, Hecho con mucho ☕️