Algunas expresiones regulares y como usarlas en PHP
In English  

Esta no es una guía sobre como crear expresiones regulares (aunque me gustaría hacer una). Estas son algunas pocas expresiones regulares que hice hace algo de tiempo, cuando tuve acceso a una lista de nombre, direcciones, números telefónicos, etc. Estaban formateados de maneras muy diferentes (en especial los números telefónicos) por lo que las expresiones regulares que usaba antes de eso tuvieron que ser mejoradas. También hablo sobre como usar estas expresiones regulares para validar campos de entrada en scripts de PHP.

Así que comencemos.

Temas
¿Qué son estas expresiones regulares?
Las expresiones regulares
Como usar estas expresiones regulares en PHP
Recursos
Referencias
Notas al pie

¿Qué son estas expresiones regulares?

Si comparamos múltiples cadenas de texto relacionadas al mismo tema, digamos nombres y apellidos, podemos ver que patrones emergen. Algunos obvios son que siempre hay un espacio separando el nombre del apellido, la primer letra siempre es mayúscula, siempre hay un apellido pero puede haber más de un primer nombre. Una expresión regular en usa forma de lenguaje de computación que nos permite definir estos patrones, entonces un programa llamado analizador sintáctico identifica estos patrones dentro de un texto dado. Como siempre, xkcd lo explica bien: http://xkcd.com/208/

Las expresiones regulares

Un nombre

[a-zA-ZÀ-ÖØ-öø-ÿ]+\.?(( |\-)[a-zA-ZÀ-ÖØ-öø-ÿ]+\.?)*

Creo que esta expresión regular funciona para la mayoría de nombres con caracteres no incluidos en el idioma inglés, o al menos funcionaron con todo lo que tenía disponible, permite el uso de nombres compuestos (como Sacovilla-Bolsón). Esta expresión regular realmente permite un gran número de palabras, más que usarla para localizar un nombre dentro de un texto es para usarse al validar que el nombre provisto por el usuario es algo válido.

Un nombre de calle

[a-zA-Z1-9À-ÖØ-öø-ÿ]+\.?(( |\-)[a-zA-Z1-9À-ÖØ-öø-ÿ]+\.?)*

En caso de que necesites tener nombres de calle separados de el ńumero de casa, esto solo revisa el nombre de la calle, es muy similar al ejemplo anterior solo que permite números. Y al igual que la expresión anterior, es más para usarla validando nombres de calle provistos por el usuario que para encontrar nombres de calle en un texto.

Un nombre de calle con número exterior y un número interior opcional

[a-zA-Z1-9À-ÖØ-öø-ÿ]+\.?(( |\-)[a-zA-Z1-9À-ÖØ-öø-ÿ]+\.?)* (((#|[nN][oO]\.?) ?)?\d{1,4}(( ?[a-zA-Z0-9\-]+)+)?)

Esto es en caso de que necesites la dirección completa.

Puede haber variaciones en las convenciones de nombrado en diferentes regiones. Como mencioné al comienzo del artículo, esto correctamente filtró lo que tenía disponible en el momento, y es muy probable que la gran mayoría de nombres de calles serán válidos. Idealmente, tendría una expresión regular por país, quizá incluso una por región, pero no tengo la información necesaria para hacer esto posible al momento de escribir esto. Pero trataré de hacerlo para al menos los lugares que visite desde ahora.

Un número telefónico

0{0,2}([\+]?[\d]{1,3} ?)?([\(]([\d]{2,3})[)] ?)?[0-9][0-9 \-]{6,}( ?([xX]|([eE]xt[\.]?)) ?([\d]{1,5}))?

Formatos válidos de teléfono:
+52(55)55555555
0052 (55) 55555555
(55) 55555555
55555555
55-555-555
01-800-765-8786
55555555 x23
5555-5555 Ext 23
55555555 ext23
5555-5555x43
55555555ext26
+52 (55) 5555-5555 ext 134

Un nombre de usuario

[a-zA-Z]((\.|_|-)?[a-zA-Z0-9]+){3}

Este es el nombre de usuario regular, debe comenzar con un carácter alfanumérico, debe ser de al menos 4 caracteres de longitud (puedes controlar la longitud eligiendo tu longitud mínima deseada, substrayendo uno de ella y poniendo ese número dentro de las llaves del final), puede contener números pero no comenzar con uno. También puede contener guiones bajos, puntos o guiones, pero no al comienzo o al final o tener más de uno junto (ae__, ae_- y ae._ serían inválidos).

Un correo

[a-z0-9_\-]+(\.[_a-z0-9\-]+)*@([_a-z0-9\-]+\.)+([a-z]{2}|aero|asia|arpa|biz|cat|com|coop|edu|gov|info|int|jobs|mil|mobi|museum|name|net|org|pro|tel|travel|xxx)

Debido a los recientes cambios propuestos por ICANN, esta expresión regular es muy probable que cambie, ya que ICANN planea vender nuevos TLDs (Top-level domains, dominios de nivel superior). Si ese es el caso podría resultar poco práctico revisar cada dominio de nivel superior existente dentro de la expresión regular, personalmente podría optar por solo revisar el formato, extraer el dominio de nivel superior y buscarlo en una base de datos de dominios de nivel superior, pero aún no llegamos a ese punto, así que esto debería de funcionar por ahora. Puedes agregar |onion después de xxx si necesitas usar la red de tor, o |bit si usas el servidor de nombres experimental bitname.

Adicionalmente, si usas dominios que permiten nombres de dominio internacionalizados, podrías necesitar agregar caracteres extras aceptados a la expresión regular. Podría hacer nuevas cadenas ajustadas para diferentes regiones en el futuro, revisa el artículo o envíame un mensaje en la forma de contacto en Contacto si tienes comentarios sobre esto.

Un número RFC (Registro federal de contribuyentes, un valor mexicano)

[A-Z]{3,4}[ \-]?[0-9]{2}((0{1}[1-9]{1})|(1{1}[0-2]{1}))((0{1}[1-9]{1})|([1-2]{1}[0-9]{1})|(3{1}[0-1]{1}))[ \-]?[A-Z0-9]{3}

Como usar estas expresiones regulares en PHP

Para usar expresiones regulares en PHP usamos la función preg_match(). Si el patrón es encontrado en el texto, preg_match() regresa 1, de otra forma regresa 0. La expresión regular debe estar encerrada por una diagonal en cada lado.

Ya que quiero comparar el contenido entero de la cadena con la expresión regular, siempre las comienzo con ^ y siempre las termino con $, además de usar el modificador D para representar el principio y el final de la linea. Por lo tanto, para usarlo dentro de preg_match() con este propósito, la expresión regular

[a-zA-Z]((\.|_|-)?[a-zA-Z0-9]+){3}

se convertiría en

/^[a-zA-Z]((\.|_|-)?[a-zA-Z0-9]+){3}$/D

Lo que sigue es un pequeño ejemplo de esta precisa expresión regular para revisar la variable $texto:

<?php
$texto = 'jveweb';
$regex = '/^[a-zA-Z]((\.|_|-)?[a-zA-Z0-9]+){3}$/D';
if (preg_match($regex, $texto)) {
    echo 'El texto es válido';
} else {
    echo 'El texto NO es válido';
}
?>

Recursos

Un pequeño (y simple) script que hice para probar expresiones regulares

prueba_regex.tar.gz
Tamaño: 2889 bytes
MD5: b46521df0846e8db7cc9cd4c763f3c07
SHA1: d2dc844ce3e5279f9b585403b80d3baef59ad5e3
Licencia: Similar a BSD-3

Si marcas el cuadro llamado "Multilinea" dividirá los contenidos de el texto en múltiples lineas y revisará cada linea separadamente, de otra manera evaluará el contenido entero de el texto.

Referencias

Regular Expression Pocket Reference (libro)
http://www.php.net/manual/en/function.preg-match.php

Notas al pie

Este es otro de esos temas que disfruto, me gustaría tener grandes sets de datos para analizar, pero usualmente no los tengo. Así que algo algunas cuando llego a enseñarlas. Si tienes un comentario sobre esto, o si te gustaría una expresión regular creada envíame un correo o un mensaje en mi forma de contacto en la sección Contacto.

Originalmente iba a recomendar la extensión de Firefox "Regular Expression Tester" pero me falló para algunas expresiones regulares complejas, lo que encuentro raro ya que recuerdo probando algunas hace tiempo con extra extensión de Firefox. Así que hice un pequeño script para probar las expresiones regulares, que es lo que publiqué aquí.