Ir directamente al contenido de esta página
Cuando hace unas semanas me puse a escribir los scripts para los ejemplos de «Cuestiones de accesibilidad al validar un formulario», recordé que las expresiones regulares son uno de los mejores inventos después de la PSP. Así que aquí estoy, refrescando mi memoria, y porque, como dice mi profesor de aikido, «intentando enseñar es cuando de verdad se empieza a aprender».
Por supuesto, tanto este artículo como su continuación —«Expresiones regulares (2)»— no son más que una mera introducción. A medida que se avanza en el estudio de las expresiones regulares, la profundidad y la complejidad del tema dan para escribir libros de esos que dan miedo por su volumen. Por ejemplo, Mastering Regular Expressions de Jeffrey Friedl son 542 páginas, y Beginning Regular Expressions de Andrew Watt, 768.
Las expresiones regulares son patrones con los que describir de manera abstracta una cadena literal. Su función es servir como modelo con el que contrastar una cadena.
Supongamos, por ejemplo, que quiero saber si lo que ha introducido un usuario en un campo de formulario es un correo electrónico. Tendría que preguntar algo como «¿el texto que ha introducido el usuario no tiene espacios en blanco, al menos cuenta con un caracter antes de una arroba, tras ésta hay algún caracter antes de un punto y tras éste hay dos o tres caracteres alfabéticos?». Pues el modelo que corresponde a esta descripción es la expresión regular.
Hay dos formas de crear expresiones regulares:
RegExp
:
var exp_reg = new RegExp(expresión_regular,parámetros);
var exp_reg = /expresión_regular/parámetros;
Los parámetros son dos, y en la construcción de una expresión pueden aparecer ambos, uno o ninguno:
g
: Abreviatura de global, significa que la expresión regular ha de compararse con toda una cadena literal, y no detenerse tras la primera coincidencia.i
: Abreviatura de insensitive, indica si se debe hacer diferencia entre mayúsculas y minúsculas al comparar letras.Las diferencias quedarán más claras en cuanto comencemos a trabajar sobre unos ejemplos.
String
que aceptan expresiones regularesAntes de empezar a explicar su sintaxis1, primero necesitamos saber qué métodos del objeto String
aceptan expresiones regulares. Estos métodos son:
match()
: Devuelve una matriz con las coincidencias de la expresión regular.search()
: Devuelve la posición en la cadena de la primera coincidencia.replace()
: Sustituye la parte de la cadena que coincide con la expresión regular.split()
: Devuelve una matriz con las subcadenas resultantes de disgregar la cadena inicial tomando como separador la expresión regular.Vamos a ver un ejemplo. Si tenemos el siguiente literal, consistente en un famoso verso de Gertrude Stein
var cadena = 'A Rose is a Rose is a Rose';
y una expresión regular tan sencilla como
var expr = /ro/i;
nos devolvería:
cadena.match(expr)
: El resultado sería una matriz con un solo elemento, Ro
.cadena.search(expr)
: El resultado sería 2
.cadena.split(expr)
: El resultado sería una matriz compuesta por A
, se is a
, se is a
y se
.Si ahora modificamos la expresión de esta manera:
var expr = /ro/g;
los resultados serían
cadena.match(expr)
: null
, puesto que hemos hecho que la expresión deje de ser insensible al caso, y deja de considerar ro y Ro como coincidentes.cadena.search(expr)
: -1
, dado que por el mismo motivo ahora no hay coincidencia.cadena.split(expr)
: A Rose is a Rose is a Rose
, ídem.Por último, si volvemos a modificar expr
:
var expr = /ro/gi;
el resultado sería como para /ro/i
salvo para match()
, que devolvería una matriz compuesta por Ro
, Ro
y Ro
, dado que ahora la expresión es global —y como hemos dicho, la comparación no se detiene en la primera coincidencia— e insensible.
Se pueden comprobar estos resultados en este ejemplo.
Por su parte, por medio de replace()
se pueden sustituir las coincidencias de una expresión en una cadena por otra cadena.
Supongamos que ahora nuestra cadena es la extensión del acrónimo VALIS:
var cadena = 'Vast Active Living Intelligence System';
y aplicamos replace()
así:
cadena.replace(/I/,'0')
: Sustituye la «I» de Intelligence por un cero.cadena.replace(/I/g,'0')
: Sustituye la «I» de Intelligence por un cero; ahora la expresión es global, por lo que tras la primera coincidencia sigue buscando las siguientes, pero como no es insensible no vuelve a dar con ninguna.cadena.replace(/I/i,'0')
: Sustituye la «i» de Active por un cero; ahora no tiene en consideración si la «i» es mayúscula o minúscula, pero como ahora la expresión no es global, tras la primera coincidencia la búsqueda se detiene.cadena.replace(/I/gi,'0')
: Sustituye toda «i» por un cero.Éste es el ejemplo interactivo.
Hasta ahora he empleado en los ejemplos las expresiones regulares más sencillas, pero para luego crear expresiones más complejas hay una serie de caracteres que tienen su propio significado, y que por tanto deben escaparse. Los recojo a continuación:
( [ { \ ^ $ | ) ? * + .
Su sintaxis sería expr = /\(/;
o expr = new RegExp('\\(');
. Sí, en el objeto se hace un doble escape.
También se pueden identificar caracteres ASCII o Unicode empleando su código. Los primeros se expresan por medio de \x
más el valor de dos dígitos hexadecimal correspondiente2; \x53
, por ejemplo, es la «S». Los segundos se indican con \u
y el valor hexadecimal de cuatro dígitos del caracter3; por ejemplo —y por si alguna vez se lo ha preguntado—, el código hexadecimal de la runa Tyr es \u16CF
.
Y ya, por último, existen una serie de caracteres predefinidos:
\0
: Vacío (null
).\a
: El caracter de advertencia.\b
: El caracter de retroceso.\c+código_de_control
4: El código de control correspondiente.\e
: El caracter de escape.\f
: El caracter de avance de formulario.\n
: Un salto de línea.\r
: Un salto de carro.\t
: Un tabulado horizontal.\v
: Un tabulado vertical.No está mal, pero con todo esto no podríamos más que buscar cadenas concretas de caracteres, lo que no nos daría la flexibilidad que buscamos, y que recordemos era poder describir patrones abstractos. Por ello, vamos a ver ahora las clases.
Las clases son un grupo de caracteres con los que incluir más casos de coincidencia —o de excepción— dentro de una expresión regular. Se indican entre corchetes ([]
).
Hay varios tipos:
/c[aeo]da/
coincidiría con «cada», «ceda» y «coda».^
). Por ejemplo, /te[^n]sa/
daría una coincidencia con «tersa», pero no con «tensa».-
). Por ejemplo, /[2-5]/
son las cifras entre el 2 y el 5, ambas inclusive; /[s-v]/
son las letras nimúsculas entre la «s» y la «v», ambas inclusive.Predefinidas: Se trata de una serie de clases que agrupan patrones comunes, con el fin de simplificar la sintaxis de una expresión:
Clase predefinida | Equivale a | Significa |
---|---|---|
. |
[^\n\r] |
Cualquier caracter excepto salto de línea y retorno de carro. |
\d |
[0-9] |
Cualquier dígito. |
\D |
[^0-9] |
Cualquier caracter que no sea un dígito. |
\s |
[ \t\n\x0B\f\r] |
Cualquier espacio en blanco. |
\S |
[^ \t\n\x0B\f\r] |
Cualquier caracter salvo los espacios en blanco. |
\w |
[a-zA-Z_0-9] |
Los caracteres que se llaman de palabra (word characters), que son las letras mayúsculas y minúsculas y el guión bajo. |
\W |
[^a-zA-Z_0-9] |
Los caracteres que se llaman de no palabra (non-word characters). |
Además, las clases se pueden combinar entre sí simplemente apilándolas unas junto a otras. [\s2-7a-c]
seleccionaría cualquier espacio en blanco, las cifras de 2 a 7 y las letras «a», «b» y «c».
Para familiarizarse con las clases lo mejor es aplicarlas, aunque para ver su funcionamiento de una forma rápida y ahorrar picar código en este momento, he creado un comparador de textos y expresiones regulares, en el que voy a someter a la cadena abbcCcddDDeEeffg1 234 a unas cuantas expresiones regulares aplicando match()
. Recojo aquí los resultados —el usuario puede repetir las pruebas o probar con sus propias cadenas y sus propias expresiones regulares—:
/c[cd]/gi
: Pido que busque las coincidencias de dos caracteres, siempre y cuando la primera sea una «c» y la segunda sea una «c» o una «d», de manera global e insensible. El método devuelve cC,cd
./[0-5][^\s]/i
: Pido que busque dos caracteres, siempre y cuando el primero sea un número entre 0 y 5 y es segundo no sea un espacio en blanco, de manera insensible —aunque en este caso este parámetro es irrelevante—. Devuelve 23
. Podría también haber empleado /[0-5][\S]/
con idéntico resultado./[D-F]\w\d/
: Pido tres caracteres, el primero una letra entre la «d» y la «f», después cualquier caracter de palabra y después un dígito cualquiera, de manera insensible y no global. Devuelve fg1
./[a-g]\d[^\s][2-5][0-9]/gi
: Pido que me busque cinco caracteres, el primero cualquier letra entre la «a» y la «g», seguida de cualquier dígito, seguidos de cualquier caracter que no sea un espacio en blanco, seguidos de cualquier número entre 2 y 5 y seguidos de cualquier número entre 0 y 9, de manera global e insensible. Devuelve null
.Como vemos, en todas las expresiones regulares he tenido que especificar todos y cada uno de los caracteres que he querido que match()
buscase. Para añadir potencia a las expresiones regulares, también podemos indicar el número de veces que uno de tales caracteres puede repetirse, pero eso lo veremos ya en la continuación de este artículo.