Comunidad de diseño web y desarrollo en internet

Expresiones Regulares en ActionScript 3, tip 2

En el primer artículo de expresiones regulares nos concentramos en patrones completos. Cada patrón coincide o no. Pero las expresiones regulares son mucho más potentes que eso. Cuando una expresión regular coincide, puede extraer partes concretas, se puede saber qué es lo que causó la coincidencia.

El ejemplo que veremos será analizar un número de teléfono. El cliente debe ser capaz de introducir un número de forma libre(en un único campo), pero se tendrá que almacenar de forma separada el código de área, la troncal, el número y una extensión opcional.

Estos son los números de teléfono que se han de aceptar:

  • 212-864-2802
  • 212 864 2802
  • 212.864.2802
  • (212) 864-2802
  • 0-212-864-2802
  • 212-864-2802-1234
  • 212-864-2802x1234

En cada uno de estos casos se necesita saber que el código de área es 212, la troncal 864 y el resto del número de teléfono era 2802. Para aquellos con extensión, se debe almacenar 1234.

Crea un archivo .as y un .fla y nómbralos PhoneParser.as y PhoneTest.fla respectivamente, luego copia el siguiente código en el archivo PhoneParser.as

Código :

package {
   public class PhoneParser{
      private var pattern = /^(?P<codArea>(\d{3}))-(?P<troncal>(\d{3}))-(?P<numero>(\d{4}))$/;
      private var numeroResultante:Array = new Array();
      function PhoneParser(){
         //Constructor de la Clase
      }
      
      public function ValidarNumero(numero:String):Boolean{         
         if(pattern.test(numero)){
            numeroResultante = pattern.exec(numero);
            trace("Codigo de Area: "+numeroResultante.codArea);
            trace("Troncal: "+numeroResultante.troncal);
            trace("Numero: "+numeroResultante.numero);
         }
         return pattern.test(numero);
      }      
   }   
}

En la variable pattern almacenaremos el patrón a seguir para hallar los números telefónicos. Tenemos que esta coincide con el comienzo de la cadena, y luego (\d{3}). ¿Qué es \d{3}? Bien, el {3} significa "coincidir con exactamente tres caracteres"; es una variante de la sintaxis {n,m} que vimos antes. \d significa "un dígito numérico" (de 0 a 9). Al colocar ?P<nombre> antes de cada grupo asignamos un identificador para luego recuperar los valores obtenidos. Luego coincide con un guión. Después con otro grupo de exactamente tres dígitos. Luego con otro guión. Entonces con otro grupo de exactamente cuatro dígitos y ahora con el final de la cadena. Dentro de la función ValidarNumero utilizamos el método exec de la ER. Este método devuelve un objeto que contiene el patrón encontrado, dicho objeto lo almacenamos en el array numeroResultante, ahora para acceder a los grupos creados anteriormente basta con escribir "numeroResultante.NombreDelGrupo". En este caso, hemos definido tres grupos, un con tres dígitos(codArea), otro con tres dígitos(troncal) y otro más con cuatro dígitos(numero).

Copia en tu archivo .fla lo siguiente y ejecútalo:

Código :

import PhoneParser

var parser:PhoneParser = new PhoneParser()

trace(parser.ValidarNumero("212-864-2802"))         //devuelve true
trace(parser.ValidarNumero("212-864-2802-1234"))     //false

Vemos que esta expresión regular no es la respuesta final, porque no trabaja con un número de teléfono con extensión al final. Para eso, hace falta aumentar la expresión.

Código :

package clases{
   public class PhoneParser{
      private var pattern = /^(?P<codArea>(\d{3}))-(?P<troncal>(\d{3}))-(?P<numero>(\d{4}))-(?P<ext>(\d+))$/;
      private var numeroResultante:Array = new Array();
      function PhoneParser(){
         //Constructor de la Clase
      }
      
      public function ValidarNumero(numero:String):Boolean{         
         if(pattern.test(numero)){
            numeroResultante = pattern.exec(numero);
            trace("Codigo de Area: "+numeroResultante.codArea);
            trace("Troncal: "+numeroResultante.troncal);
            trace("Numero: "+numeroResultante.numero);
            trace("Extensión: "+numeroResultante.ext);
         }
         return pattern.test(numero);
      }
   }
}
Esta expresión regular es casi idéntica a la anterior. Igual que antes buscamos el comienzo de la cadena, luego recordamos un grupo de tres dígitos, luego un guión, un grupo de tres dígitos a recordar, un guión y un grupo de cuatro dígitos a recordar. Lo nuevo es que ahora buscamos otro guión y un grupo a recordar de uno o más dígitos y por último el final de la cadena.

Código :

import clases.PhoneParser

var parser:PhoneParser = new PhoneParser()

trace(parser.ValidarNumero("212-864-2802-1234")) //true
trace(parser.ValidarNumero("212 864 2802 1234")) //false
trace(parser.ValidarNumero("212-864-2802"))       //false   
Desafortunadamente, esta expresión regular tampoco es la respuesta definitiva, porque asume que las diferentes partes del número de teléfono las separan guiones. ¿Qué pasa si las separan espacios, comas o puntos? Necesita una solución más general para coincidir con varios tipos de separadores. ¡Vaya! No sólo no hace todo lo que queremos sino que incluso es un paso atrás, porque ahora no podemos reconocer números sin una extensión. Eso no es lo que queríamos; si la extensión está ahí, queremos saberla, pero si no, aún queremos saber cuales son las diferentes partes del número principal.

El siguiente ejemplo muestra la expresión regular que maneja separadores entre diferentes partes del número de teléfono:

Código :

pattern = /^(?P<codArea>(\d{3}))\D+(?P<troncal>(\d{3}))\D+(?P<numero>(\d{4}))\D+(?P<ext>(\d+))$/;

Al Ejecutarlo:

Código :

trace(parser.ValidarNumero("212-864-2802-1234"))   //true
trace(parser.ValidarNumero("212 864 2802 1234"))   //true

Primero Estamos buscando el comienzo de la cadena, luego un grupo de tres dígitos, y después \D+. ¿Qué diantre es eso? Bien, \D coincide con cualquier carácter excepto un dígito numérico y + significa "1 o más". Así que \D+ coincide con uno o más caracteres que no sean dígitos. Esto es lo que vamos a usar en lugar de un guión, para admitir diferentes tipos de separadores. Usar \D+ en lugar de - implica que ahora podemos reconocer números de teléfonos donde los números estén separados por espacios en lugar de guiones. Por supuesto, los números separados por guión siguen funcionando.

Código :

trace(parser.ValidarNumero("21286428021234"))      //false
trace(parser.ValidarNumero("212-864-2802"))         //false 

Desafortunadamente, aún no hemos terminado, porque asume que hay separadores. ¿Qué pasa si se introduce el número de teléfono sin ningún tipo de separador? ¡Vaya! Aún no hemos corregido el problema de la extensión obligatoria. Ahora tenemos dos problemas, pero podemos resolverlos ambos con la misma técnica.

El siguiente ejemplo muestra la expresión regular para manejar números de teléfonos sin separadores.

Código :

pattern = /^(?P<codArea>(\d{3}))\D*(?P<troncal>(\d{3}))\D*(?P<numero>(\d{4}))\D*(?P<ext>(\d*))$/;

Código :

trace(parser.ValidarNumero("21286428021234"))      //true
trace(parser.ValidarNumero("212.864.2802 x1234"))   //true
trace(parser.ValidarNumero("212-864-2802"))         //true

La única modificación que hemos hecho desde el último paso es cambiar todos los + por *. En lugar de buscar \D+ entre las partes del número de teléfono, ahora busca \D*. ¿Recuerda que + significa "1 o más"? Bien, * significa "cero o más". Así que ahora es capaz de reconocer números de teléfono incluso si no hay separadores de caracteres. También funcionan otras variantes: puntos en lugar de guiones, y tanto espacios como una x antes de la extensión. Por último, hemos resuelto el otro problema que nos ocupaba: las extensiones vuelven a ser opcionales. Si no se encuentra una extensión, el método exec() sigue devolviendo 4 grupos, pero el cuarto es simplemente una cadena vacía. Odio ser portador de malas noticias, pero aún no hemos terminado. ¿Cual es el problema aquí?

Código :

trace(parser.ValidarNumero("(212)8642802 x1234"))   //false

Hay un carácter adicional antes del código de área, pero la expresión regular asume que el código de área es lo primero que hay al empezar la cadena. No hay problema, podemos usar la misma técnica de "cero o más caracteres no numéricos" para eliminar los caracteres que hay antes del código de área:

Código :

pattern = /^\D*(?P<codArea>(\d{3}))\D*(?P<troncal>(\d{3}))\D*(?P<numero>(\d{4}))\D*(?P<ext>(\d*))$/;

Esto es lo mismo que en el ejemplo anterior, excepto que ahora empezamos con \D*, cero o más caracteres no numéricos. Antes del primer grupo que hay que recordar (el código de área). Observe que no estamos recordando estos caracteres no numéricos (no están entre los paréntesis). Si los encontramos, simplemente los descartaremos y empezaremos a recordar el código de área en cuanto lleguemos a él.

Código :

trace(parser.ValidarNumero("(212)8642802 ext. 1234"))      //true

Puede analizar con éxito el número de teléfono, incluso con el paréntesis abierto a la izquierda del código de área. (El paréntesis de la derecha también se tiene en cuenta; se le trata como un separador no numérico y coincide con el \D* tras el primer grupo a recordar).

Código :

trace(parser.ValidarNumero("212-864-2802"))            //true

Un simple control para asegurarnos de no haber roto nada que ya funcionase. Como los caracteres iniciales son totalmente opcionales, esto coincide con el principio de la cadena, luego vienen cero caracteres no numéricos, después un grupo de tres dígitos a recordar (212), luego un carácter no numérico (el guión), un grupo de tres dígitos a recordar (864), un carácter no numérico (el guión), un grupo de cuatro dígitos a recordar (2802), cero caracteres no numéricos, un grupo a recordar de cero dígitos, y el final de la cadena.

Código :

trace(parser.ValidarNumero("trabajo 0-(212) 864.2802 #1234"))      //false

OK, ahora ¿Por qué no funciona con este numero telefónico?. Porque hay un 0 antes del código de área y asumimos que todos los caracteres antes de ese código son no numéricos (\D*). Parémonos a pensar por un momento. La expresión regular hasta ahora ha buscado coincidencias partiendo siempre del inicio de la cadena. Pero ahora vemos que hay una cantidad indeterminada de cosas al principio de la cadena que queremos ignorar. En lugar de intentar ajustarlo todo para simplemente ignorarlo, tomemos un enfoque diferente: no vamos a buscar coincidencias explícitamente desde el principio de la cadena. Esto lo mostramos en el siguiente ejemplo:

Código :

pattern = /(?P<codArea>(\d{3}))\D*(?P<troncal>(\d{3}))\D*(?P<numero>(\d{4}))\D*(?P<ext>(\d*))$/;
Observe la ausencia de ^ en esta expresión regular. Ya no buscamos el principio de la cadena. No hay nada que diga que tenemos que hacer que la entrada completa coincida con nuestra expresión regular. El motor de expresiones regulares hará el trabajo duro averiguando desde dónde ha de empezar a comparar en la cadena de entrada, y seguirá de ahí en adelante.

Código :

trace(parser.ValidarNumero("trabajo 0 -(212) 864.2802 #1234"))   //true
trace(parser.ValidarNumero("212-864-2802"))      //true
trace(parser.ValidarNumero("21286428021234"))   //true

Al realizar las pruebas todas funcionan Correctamente.

Al final la clase debería lucir como sigue

Código :

package clases{
   public class PhoneParser{
      private var pattern = /(?P<codArea>(\d{3}))\D*(?P<troncal>(\d{3}))\D*(?P<numero>(\d{4}))\D*(?P<ext>(\d*))$/;
      private var numeroResultante:Array = new Array();
      function PhoneParser(){
         //Constructor de la Clase
      }
      
      public function ValidarNumero(numero:String):Boolean{         
         if(pattern.test(numero)){
            numeroResultante = pattern.exec(numero);
            trace("Codigo de Area: "+numeroResultante.codArea);
            trace("Troncal: "+numeroResultante.troncal);
            trace("Numero: "+numeroResultante.numero);
            trace("Extensión: "+numeroResultante.ext);
         }
         return pattern.test(numero);
      }      
   }   
}

Ésta es sólo la minúscula punta del iceberg de lo que pueden hacer las expresiones regulares. En otras palabras, incluso aunque esté completamente saturado con ellas ahora mismo, créame, todavía no ha visto nada.

Ahora una pequeña lista de meta caracteres y meta secuencias de uso en las ER
  • ^ coincide con el principio de una cadena.
  • $ coincide con el final de una cadena.
  • \b coincide con el límite de una palabra.
  • \d coincide con cualquier dígito numérico.
  • \D coincide con cualquier carácter no numérico.
  • x? coincide con un carácter x opcional (en otras palabras, coincide con x una o ninguna vez).
  • x* coincide con x cero o más veces.
  • x+ coincide con x una o más veces.
  • x{n,m} coincide con un carácter x al menos n pero no más de m veces.
  • (a|b|c) coincide sólo con una entre a, b o c.
  • ?P<nombredelgrupo> para asignar un identificador a un grupo que después queramos recuperar.

[Ver Ejemplo] [Descargar Ejemplo]

¿Sabes SQL? ¿No-SQL? Aprende MySQL, PostgreSQL, MongoDB, Redis y más con el Curso Profesional de Bases de Datos que empieza el martes, en vivo.

Publica tu comentario

o puedes...

¿Estás registrado en Cristalab y quieres
publicar tu URL y avatar?

¿No estás registrado aún pero quieres hacerlo antes de publicar tu comentario?

Registrate