Expresiones regulares y lenguajes de programación I: Javascript y Java

Una vez hemos visto la teoría básica de las expresiones regulares y una vez hemos trasteado un poco con el testeador web de expresiones regulares es momento de aprender cómo usar estas expresiones regulares en los lenguajes de programación en los que, al menos nosotros, solemos programar.

Para esta primera entrada de la serie vamos a mostrar las funciones que ofrecen Javascript y Java y casos de uso para tratar expresiones regulares.
Para simplificar la comprensión, vamos a usar la misma expresión regular para todos los lenguajes, y las mismas cadenas de texto para cada ejemplo. En estos casos usaremos la siguiente expresión regular, que aunque es bastante incompleta nos servirá para los ejemplos por su simplicidad. Se trata de un patrón que intenta encajar correos electrónicos sin subdominios (caracteres alfanuméricos o _, una @, caracteres alfanuméricos o _, un punto y entre 2 y 6 letras para el dominio generico).
^(\w+)@(\w+)\.([a-zA-z]{2,6})$
 

Javascript


Javascript es un lenguaje de programación interpretado, dinámico, débilmente tipado, orientado a objetos y básicamente usado en páginas web dinámicas en el lado del cliente.

Para poder usar expresiones regulares en Javascript necesitaremos construir un objeto que sea una expresión regular. Para ello tenemos dos alternativas:
  • La primera se recomienda usar en caso de conocer la expresión regular previamente, y es usar el "constructor" literal. Para ello utilizaremos la siguiente sintaxis:
    var regex = /^(\w+)@(\w+)\.([a-zA-z]{2,6})$/;
  • La segunda se recomienda usar en caso de que la expresión regular venga dada más tarde, por ejemplo desde una función o desde un campo de texto. Sin embargo puede usarse también usando una expresión regular literal, sólo que con una salvedad que puede convertirse en un problema. En este caso necesitamos escapar cada \ con otra \. De forma que para usar la expresión \w necesitaríamos escribir "\\w". El peor caso se da cuando queremos escribir la expresión \\, que significa una barra invertida. Para ello necesitariamos usar "\\\\" para encajar ¡sólo una barra invertida! Esta situación se da también en Java, como mencionaremos más adelante. La sintaxis es, entonces:
    var regex = new RegExp("^(\\w+)@(\\w+)\\.([a-zA-z]{2,6})$");

Una vez tenemos nuestra expresión regular almacenada en una variable, existen diversas funciones para comprobar si existe o no el patrón en una cadena.
  • Por una parte tenemos la función match(regex); de los string. Se invoca sobre una variable que contenga una cadena de caracteres y se intenta validar contra una expresión regular. En caso de no encontrarse el patrón en la cadena el valor devuelto será null, que en valores booleanos es 0. En caso de encontrarse, se devuelve un array que contiene la cadena que ha encajado en su posicion 0 y, en caso de haber usado paréntesis en nuestra expresión regular, las cadenas encajadas por cada paréntesis en una posición diferente del array. Veamos un ejemplo completo:
    // Meta @ vidasconcurrentes
    
    <html>
    <head>
    <script type="text/Javascript">
     function test() {
      var regex = /^(\w+)@(\w+)\.([a-zA-z]{2,6})$/;
      var email = "test@vidasconcurrentes.com";
      document.write(email.match(regex));
     }
    </script>
    </head>
    <body onload="test()">
    </body>
    </html>
    
    Por supuesto podríamos haber usado la expresión regular y la cadena de texto directamente y haberlo hecho en una linea, pero es menos claro. Bien, si creásemos un fichero nuevo con esto y lo ejecutásemos en un navegador web, obtendríamos la siguiente salida en pantalla: test@vidasconcurrentes.com,test,vidasconcurrentes,com. En caso de haber usado la expresión regular sin los paréntesis agrupadores, la salida habría sido sólo test@vidasconcurrentes.com.

  • Por otra parte tenemos una función equivalente de las expresiones regulares en lugar de los string. La función en cuestión es exec(string); y se invoca sobre expresiones regulares. Hace lo mismo que la anterior, devolviendo un array que podemos recorrer con un bucle accediendo a los índices. Veámoslo en el siguiente ejemplo completo:
    // Meta @ vidasconcurrentes
    
    <html>
    <head>
    <script type="text/Javascript">
     function test() {
      var regex = /^(\w+)@(\w+)\.([a-zA-z]{2,6})$/;
      var email = "test@vidasconcurrentes.com";
      var coincidencias = regex.exec(email);
      for(i = 0; i < coincidencias.length; i++)
       document.write(coincidencias[i] + "<br/ >");
     }
    </script>
    </head>
    <body onload="test()">
    </body>
    </html>
    
    En este caso, lo que veríamos en nuestro navegador si lo escribimos sería:
    test@vidasconcurrentes.com
    test
    vidasconcurrentes
    com
Existen también funciones se que encargan de hacer reemplazos en una cadena a partir de una expresión regular. Para ello usaremos la función de los string replace(regex, nuevostring); que se invoca sobre una cadena, recibe la expresión regular contra la que intentaremos encajar y el string de reemplazo. Podríamos querer especificar opciones para las expresiones regulares, como por ejemplo g para encajar globalmente, i para hacerlo sin tener en cuenta las mayúsculas, s para encajar el punto como salto de línea y m para encajar patrones en multiples líneas (podemos usar una o varias a la vez).
En el siguiente ejemplo completo vamos a hacer una mezcla de ambas.
// Meta @ vidasconcurrentes

<html>
<head>
<script type="text/Javascript">
 function test() {
  var regexLiteral = /\w+/g;
  var regexObjeto = new RegExp("[a-z]+", "i");
  var email = "test@vidasconcurrentes.com";
  var emailInsensitive = "TesT@VidasConcurrentes.COM";
  document.write(email.replace(regexLiteral, "prueba") + "<br/ >");
  document.write(emailInsensitive.replace(regexObjeto, "prueba") + "<br/ >");
 }
</script>
</head>
<body onload="test()">
</body>
</html>
Lo que mostraría el navegador en este caso son dos líneas. La primera sale de una sustitución de todos los grupos de \w por prueba, y la segunda sale de la sustitución del primer grupo de letras por prueba ([a-z] son sólo letras minúsculas, pero que al añadir la i a las opciones del constructor RegExp() estamos diciendo case insensitive). El resultado es:
prueba@prueba.prueba
prueba@VidasConcurrentes.COM
Ejemplo completo en nuestro repositorio.

Java


Java es un lenguaje de programación orientado a objetos, fuertemente tipado, independiente de la plataforma (write once, run everywhere) que ejecuta sobre una máquina virtual.

Al igual que en Javascript, escribir una expresión regular literalmente en lugar de obtenerla posteriormente de un campo de texto o de una funcion, implicará tener que hacer un doble escapado de la \ para que se considere expresión regular. De esta forma, si utilizamos una variable String para guardar la expresión regular y la ponemos "a pelo", o si ponemos el texto directamente en el constructor, necesitaremos hacer un doble escapado (recordamos que para representar el caracter \ necesitaremos poner \\\\).
Por otra parte, a diferencia de Javascript (que no es Java, para evitar la típica equivocación), en Java usaremos más de un objeto para utilizar las expresiones regulares. En este caso serán dos clases:

  • Clase Pattern: define una expresión regular compilada. Para poder construir un objeto de la clase Pattern haremos lo siguiente:
    Pattern regex = Pattern.compile("^(\\w+)@(\\w+)\\.([a-zA-z]{2,6})$");
  • Clase Matcher: define un objeto que va a intentar encajar la expresión regular en una cadena por medio de varias funciones. Para crear un objeto de esta clase necesitamos un Pattern ya creado. De forma que:
    Pattern regex = Pattern.compile("^(\\w+)@(\\w+)\\.([a-zA-z]{2,6})$");
    Matcher matcher = regex.matcher("test@vidasconcurrentes.com");
Una vez tenemos esto, llega el momento de ver las funciones que se usan para encajar expresiones regulares. Básicamente tendremos dos funciones para poder hacer esto:

  • Función matches(): esta función es parte del objeto Matcher. Intenta encajar la expresión regular en la cadena de forma absoluta. En otras palabras, en caso de que nuestro Pattern no tuviera los símbolos ^ y $, iba a actuar como si los tuviera. Devuelve un boolean. Veamos un ejemplo:
    Pattern regex = Pattern.compile("(\\w+)@(\\w+)\\.([a-zA-z]{2,6})");
    Matcher matcher = regex.matcher("test@vidasconcurrentes.com");
    System.out.println(matcher.matches());
    // output: true
    
    Pattern regex = Pattern.compile("(\\w+)@(\\w+)\\.([a-zA-z]{2,6})");
    Matcher matcher = regex.matcher("basura test@vidasconcurrentes.com basura");
    System.out.println(matcher.matches());
    // output: false
  • Función find(): esta función es también parte del objeto Matcher. Intenta encajar la expresión regular en alguna parte de la cadena. Al contrario que matches(), si el Pattern no contiene los símbolos de inicio y final, no los inserta. Devuelve un boolean también. Cuando utilizamos esta función, el objeto de la clase Matcher guarda la posición en la que consiguió encajar el patrón, de forma que podremos usar las funciones start(), end() y group() para acceder a la posición inicial donde encajó, la posición final y la cadena que encajó, respectivamente. Veamos el ejemplo anterior con esta función:
    Pattern regex = Pattern.compile("(\\w+)");
    Matcher matcher = regex.matcher("test@vidasconcurrentes.com");
    while(matcher.find())
       System.out.println(matcher.group());
    /* output:
              test
              vidasconcurrentes
              com
    */
    
    Pattern regex = Pattern.compile("(\\w+)@(\\w+)\\.([a-zA-z]{2,6})");
    Matcher matcher = regex.matcher("test@vidasconcurrentes.com");
    System.out.println(matcher.find());
    // output: true
    
    Pattern regex = Pattern.compile("(\\w+)@(\\w+)\\.([a-zA-z]{2,6})");
    Matcher matcher = regex.matcher("basura test@vidasconcurrentes.com basura");
    System.out.println(matcher.find());
    // output: true
Existe una forma "rápida" de ejecutar estas líneas en una sola, de forma que compilamos un Pattern y sobre él aplicamos la función matches() directamente sin crear el objeto. Esta forma es relativamente buena si, primero no vamos a querer reutilizar la expresión regular (hay que fijarse en que podemos sacar varios Matcher de un mismo Pattern), y segundo si lo que queremos es encajar una cadena entera, y no partes internas sólo. La clase Matcher permite resetear la cadena que guarda (también las posiciones que ha encajado con find() y de esta forma ahorrar movimiento al recolector de basura, para ello se usan las funciones reset() y reset(nuevaCadena). Veamos un ejemplo bastante simple para esta forma rápida de encajar:
System.out.println(Pattern.matches("(\\w+)@(\\w+)\\.([a-zA-z]{2,6})",
             "test@vidasconcurrentes.com"));
// output: true

System.out.println(Pattern.matches("(\\w+)@(\\w+)\\.([a-zA-z]{2,6})",
             "basura test@vidasconcurrentes.com basura"));
// output: false

Por otra parte existe una forma de reemplazar texto en una cadena utilizando una expresión regular, pero de una forma un poco enrevesada. Para esto vamos a usar las funciones appendReplacement() y appendTail().
Por una parte vamos a ir recorriendo nuestra cadena buscando el patrón y cuando lo encajemos vamos a guardarlo en un String. Una vez hayamos acabado de recorrer la cadena, vamos a poner todo el contenido de este String en el Matcher para realmente hacer efectiva la sustitución. Hagamos una variante del ejemplo que mostramos anteriormente en Javascript:
Pattern patron = Pattern.compile("(\\w+)");
Matcher matcher = patron.matcher("test@vidasconcurrentes.com");
  
StringBuffer sb = new StringBuffer();
matcher.find(); // reemplazamos solo la primera
matcher.appendReplacement(sb, "prueba");
matcher.appendTail(sb);
  
System.out.println(sb.toString());
// output: prueba@vidasconcurrentes.com

Por otra parte podemos aplicar modificadores a la expresión regular como hicimos en Javascript con g, i, m, s. En el siguiente ejemplo mostramos como hacer el ejemplo de encajar una cadena sin distinguir mayúsculas de minúsculas:
Pattern patron = Pattern.compile("([a-z]+)", Pattern.CASE_INSENSITIVE);
Matcher matcher = patron.matcher("TeSt@VidasConcuRRentes.cOM");
    
StringBuffer sb = new StringBuffer();
while(matcher.find()) // reemplazamos todo, no solo la primera
 matcher.appendReplacement(sb, "prueba");
matcher.appendTail(sb);
    
System.out.println(sb.toString());
// output: prueba@prueba.prueba
Ejemplo completo en nuestro repositorio.


Ya hemos visto cómo usar las expresiones regulares compatibles con Perl en Javascript y Java. Java además tiene la opción de poder utilizar expresiones regulares para POSIX. Para más información sobre esta parte, sería interesante estudiar la documentación de la clase Pattern.

En la siguiente entrada sobre expresiones regulares completaremos el ciclo de Expresiones regulares y lenguajes de programación viendo cómo usarlas en PHP.


Más información: http://www.regular-expressions.info/