Expresiones Regurales


Introducción

Muchas de las características de JavaScript fueron tomadas de otros lenguajes. La sintaxis de Java, las funciones de Scheme, y herencias de prototipos de sí mismo. De Perl se tomaron las Expresiones Regulares.

Una expresión regular es la especificación de la sintaxis de un lenguaje sencillo. Las expresiones regulares se utilizan con métodos para buscar, reemplazar y extraer información de las cadenas. Los métodos que funcionan con las expresiones regulares son regexp.exec , regexp.test , string.match , string.replace , string.search y string.split. Todos ellos serán descritos en el Capítulo 8. Las expresiones regulares por lo general tienen una gran ventaja de rendimiento en las operaciones de cadena equivalente en JavaScript.

Las expresiones regulares vinieron del estudio matemático de lenguajes formales. Ken Thompson adapted Stephen Kleene’s theoretical work on type-3 languages into a practical pattern matcher that could be embedded in tools such as text editors and programming languages.

La sintaxis de las expresiones regulares en JavaScript se ajusta estrictamente a las formulaciones originales de los Laboratorios Bell, con algunas reinterpretaciones y extensión de Perl. Las reglas para escribir expresiones regulares pueden ser sorprendentemente compleja porque interpretan caracteres en algunas posiciones como operadores y, en posiciones ligeramente diferentes como literales. Peor que ser difícil de escribir, esto hace que las expresiones regulares sean difícil de leer y peligrosas de modificar. Es necesario tener un entendimiento más completo de toda la complejidad de las expresiones regulares para leerlas correctamente.

Para mitigar esto, he simplificado las reglas un poco. Tal como se presenta aquí, las expresiones regulares serán un poco menos conciso, pero también será un poco más fácil de usar correctamente. Y eso es algo bueno porque las expresiones regulares pueden ser muy difíciles de mantener y depurar.

Hoy las expresiones regulares no son estrictamente regulares, pero pueden ser muy útiles. Las expresiones regulares tienden a ser extremadamente concisas, incluso críptica.  Son fáciles de usar en su forma más simple, pero pueden convertirse rápidamente en algo desconcertante. Las expresiones regulares de JavaScript son difíciles de leer en parte porque no permitir comentarios o espacios en blanco.

Todas las partes de una expresión regular se empujan entre si, lo que las hace casi indescifrable. Esto es de especial interés cuando se utilizan en aplicaciones de seguridad para la captura y validación. Si usted no puede leer y comprender una expresión regular, ¿cómo se puede tener confianza en que funcionará correctamente para todas las entradas? Sin embargo, a pesar de sus obvias desventajas, las expresiones regulares son ampliamente utilizadas.

Un Ejemplo

Aquí hay un ejemplo. Es una expresión regular que coincide con las direcciones URL. Las páginas de este libro no son infinitamente anchas, así que se partió en dos líneas. En un programa de JavaScript, la expresión regular debe estar en una sola línea. El espacio en blanco es significativo osea que no debería estar:

var parse_url = /^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+) (?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/;
var url = "http : // www . ora.com:80/goodparts?q#fragment";

Llamemos al método exce de parse_url. Si coincide con éxito la cadena que le pasamos, devolverá una matriz que contiene fragmentos extraídos de la url:

var result = parse_url.exec(url);
var names = ['url', 'scheme', 'slash', 'host', 'port', 'path', 'query', 'hash'];
var blanks = '        ';
var i;

for (i = 0; i < names.length; i += 1) {
    document.writeln(names[i] + ':' + blanks.substring(names[i].length), result[i]);
}

Esto produce:

url:        http : // www . ora.com:80/goodparts?q#fragment
scheme:     http
slash:      //
host:       www . ora.com
port:       80
path:       goodparts
query:      q
hash:       fragment

En el Capítulo 2, hemos utilizado diagramas de ferrocarril para describir el lenguaje JavaScript. También podemos utilizarlos para describir los lenguajes definidos por las expresiones regulares. Esto puede hacer que sea más fácil ver lo que hace una expresión regular. Este es un diagrama de ferrocarril para parse_url() .

Las expresiones regulares no se pueden dividir en trozos pequeños la forma en la que las funciones lo hacen, por lo que la vía de parse_url() es larga.

Vayamos al factor de parse_url() en esa parte para ver cómo funciona:

^

El carácter ^ indica el principio de la cadena. Es un ancla que previene a exec saltar sobre un prefijo como-no-URL:

(?:([A-Za-z]+):)?

Este factor coincide con un nombre de esquema, pero sólo si es seguido por un : (dos puntos). El (?: ...) indica un grupo que no captura. El sufijo ? indica que el grupo es opcional.
Esto significa repetir cero o una vez. El (... ) indica la captura. Un grupo que captura copias del texto que coinciden y los coloca en la matriz resultante. A cada grupo de captura se le da un número. Este primer grupo de captura es 1, por lo que una copia del texto que coincide con este grupo de captura aparecerá en el resultado[ 1]. La [... ] indica una clase de caracteres.
Esta clase de caracteres, A-Za-z , contiene 26 letras mayúsculas y minúsculas 26. Los guiones indican rangos, de la A a la Z. El sufijo + indica que la clase de caracteres será igualada una o más veces. El grupo es seguido por el carácter : , que se corresponde literalmente: 

(\/{0,3})

El siguiente factor es la captura del grupo 2. \/ Indica que un carácter de / (barra inclinada) debe estar igualada. Se escapó con \ (barra invertida) para que no se malinterprete como la final de la expresión regular literal. El sufijo {0,3 } indica que el / se complementará 0 o 1 o 2 o 3 veces.

([0-9.\-A-Za-z]+)

El siguiente factor es la captura del grupo 3. Coincidirá con el nombre de host, que se compone de uno o más dígitos, letras o . (puntos) o -(guión). El - se había escapado como \, de evitar que se confunda con un guión gama:

(?::(\d+))?

El siguiente factor opcionalmente coincide con un número de puerto, que es una secuencia de uno o más dígitos precedidos por : . \d que representa un dígito. La serie de uno o más dígitos será capturando el grupo 4:

(?:\/([^?#]*))?

Tenemos otro grupo opcional. Este comienza con una / . La clase de caracteres [^?#] comienza con un ^ , lo que indica que la clase incluye todos los caracteres excepto ? y # . El símbolo * indica que la clase de caracteres coincide con cero o más veces. Ten en cuenta que estoy siendo descuidado aquí.La clase de todos los caracteres excepto ? y el # incluyen caracteres de final de línea, caracteres de control, y un montón de otros caracteres que realmente no deberían ser igualados aquí. La mayoría de las veces es lo que desearíamos, pero hay un riesgo de que un texto malo pueda deslizarse por aquí. Las expresiones regulares descuidadas son una fuente popular de vulnerabilidades de seguridad. Es mucho más fácil escribir expresiones regulares descuidadas que expresiones regulares rigurosas:

(?:\?([^#]*))?

A continuación, tenemos un grupo opcional que comienza con un ? . Que contiene la captura del grupo 6, que contiene cero o más caracteres que no son # :

(?:#(.*))?

Tenemos un grupo opcional final que comienza con # . El . coincidirá con cualquier carácter excepto el carácter final de la línea:

$

El $ representa el final de la cadena. Nos asegura que no había material extra después del final de la dirección URL.

Estos son los factores de la expresión regular parse_url() . *

Es posible hacer expresiones regulares más complejas que parse_url() , pero yo no lo recomendaría. Las expresiones regulares son mejores cuando son cortas y simples. Sólo entonces podemos tener confianza en que están trabajando correctamente y que podrían ser modificadas correctamente si fuera necesario.

Hay un alto grado de compatibilidad entre los procesadores del lenguaje JavaScript. La parte del lenguaje que es menos portátil es la implementación de expresiones regulares. Las expresiones regulares que son muy complicadas o enrevesadas son más propensas a tener problemas de portabilidad. Expresiones regulares anidadas también pueden sufrir problemas de rendimiento horribles en algunas implementaciones. La simplicidad es la mejor estrategia.

Veamos otro ejemplo: una expresión regular que coincide con los números. Los números pueden tener una parte entera con un signo opcional menos, una parte fraccionaria opcional y una parte exponencial opcional:

var parse_number = /^-?\d+(?:\.\d*)?(?:e[+\-]?\d+)?$/i;
var test = function (num) {
    document.writeln(parse_number.test(num));
};

test('1');                // true
test('number');           // false
test('98.6');             // true
test('132.21.86.100');    // false
test('123.45E-67');       // true
test('123.45D-67');       // false

parse_number identificó con éxito las cadenas que se ajustaban a nuestra specificación y a aquellas que no lo hicieron, pero para aquellos que no lo hicieron, que no nos da información sobre por qué o dónde fallaron las pruebas de número.

Vamos a descomponer parse_number:

/^
$/i

Volvemos a utilizar ^ y $ para anclar la expresión regular. Esto hace que todos los caracteres en el texto sean comparados con la expresión regular. Si omitimos los anclajes, la expresión regular nos diría si una cadena contiene un número. Con las anclas, nos indica si la cadena contiene sólo un número. Si incluimos sólo el ^ , coincidiría con las cadenas que comienzan con un número. Si incluimos sólo el $ , coincidiría con las cadenas que termina con un número.

* Cuando pones todos juntos, visualmente es bastante más confuso: 

/^(?:([A-Za-z]+):)?(\/{0,3})([0-9.\-A-Za-z]+)(?::(\d+))?(?:\/([^?#]*))?(?:\?([^#]*))?(?:#(.*))?$/

La bandera i provoca que se ignore cuando se compara con letras. La única letra en nuestro patrón es e . Queremos que e también coincida con E . Podríamos haber escrito el factor e como [Ee] o ( ? :E|e), pero no tuvimos porque hemos utilizado la i bandera:

-?

El sufijo ? con el signo menos indica que el signo menos es opcional:

\d+

\d significa lo mismo que [ 0-9]. Coincide con un dígito. El sufijo + hace que coincidan con uno o más dígitos:

(?:\.\d*)?

El (?:... )? indica un gropo de no caputra opcional. Normalmente es mejor utilizar grupos noncapturing en lugar de grupos de captura menos feos porque captura tiene una penalización en el rendimiento. El grupo que coincida con un punto decimal seguido por cero o más dígitos:

(?:e[+\-]?\d+)?

Este es otro grupo noncapturing opcional. Coincide con e (o E), un signo opcional y uno o más dígitos.

Construcción

Hay dos formas de hacer un objeto RegExp. La mejor manera, como hemos visto en los ejemplos, es el de utilizar un literal de expresión regular.
Los literales de expresiones regulares se encuentran encerrados en barras. Esto puede ser un poco complicado porque la barra también es utilizada como el operador de división y de comentarios.

Hay tres indicadores que se pueden establecer en una RegExp . Que se indican mediante las letras g , i , y m , como se indica en la Tabla 7-1. Las banderas se agrega directamente al final de la expresión regular literal:

// Make a regular expression object that matches
// a JavaScript string.
var my_regexp = /"(?:\\.|[^\\\"])*"/g;
Indicador Significado
g Global (coincide multiples veces; el significado preciso de esto varía con el método)
i Insensible (ignorar mayúsculas y minúsculas)
m Multilínea (^ y $ puede coincidir con caracteres de fin de línea)

La otra manera de hacer una expresión regular es para usar el constructor RegExp. El constructor toma una cadena y lo compila en un objeto RegExp. En algunas construcciones de string se debe tener cuidado porque las barras invertidas han significado algo diferente en las expresiones regulares que en los literales de cadena. Por lo general es necesario dos barras invertidas y escapar de la cita:

// Make a regular expression object that matches
// a JavaScript string.
var my_regexp = new RegExp("\"(?:\\.|[^\\\\\\\"])*\"", 'g');

El segundo parámetro es una cadena que especifica las banderas. El constructor RegExp es útil cuando una expresión regular debe ser generada en tiempo de ejecución usando material que no está disponible para el programador.

Objetos RegExp contienen las propiedades que figuran en la Tabla 7-2.

Propiedad Uso
global true si se utiliza la bandera g
ignoreCase true si se usa la bandera i
lastIndex El índice en el que para empezar la proxima coincidencia exec. Inicialmente es cero.
multiline true si se utiliza la bandera m
source El texto fuente de la expresión regular
function make_a_matcher( ) {
    return /a/gi;
}

var x = make_a_matcher( );
var y = make_a_matcher( );

// Beware: x and y are the same object!
x.lastIndex = 10;
document.writeln(y.lastIndex);
// 10

Echemos un vistazo más de cerca a los elementos que componen las expresiones regulares.

Elección de Regexp

Una elección de regexp contiene una o más secuencias regexp. Las secuencias están separadas por el carácter | (barra vertical). La elección coincide si cualquiera de las secuencias coinciden. Se intenta hacer coincidir cada una de las secuencias en orden. Por lo tanto:

"into".match(/in|int/)

Coincide el in en into . No sería coincidencia int porque la coincidencia de in fue realizada correctamente.

Secuencia Regexp

Una secuencia regexp contiene uno o más factores regexp. Cada uno de estos factores puede opcionalmente ser seguido de un cuantificador que determina cuántas veces el factor tiene permitido aparecer. Si no hay ningún cuantificador, entonces el factor será comparado una sola vez.

Factor Regexp

Un factor regexp puede ser un carácter, un grupo entre paréntesis, una clase de caracteres, o una secuencia de escape. Todos los caracteres son tratados literalmente a excepción de los caracteres de control y caracteres especiales:

\ / [ ] ( ) { } ? + * | . ^ $

Que se debe escapar con el prefijo \ si se van a coincidir literalmente. En caso de duda, cualquier carácter especial se puede dar un \ prefijo para que sea literal. El prefijo \ no hacer letras o dígitos literal.
Un . sin escape coincide con cualquier carácter excepto un carácter de final de línea.
Una ^ sin escape coincide con el principio del texto cuando la propiedad lastIndex es cero. También puede coincidir con los caracteres de fin de linea, cuando el indicador m se especifica.
Una $  sin escape coincide con el final del texto. También puede coincidir con el caracter de fin de linea caracteres, cuando el indicador m se especifica.

Escape Regexp

El backslash o carácter de barra invertida indica escape en factores de expresiones regulares, así como en cadenas, pero en factores de expresiones regulares, funciona un poco diferente.

Al igual que en las cadenas, \f es el carácter de avance, \n es el carácter de nueva línea, \r es el carácter de retorno de carro, \t es el carácter de tabulación, y \u permite para especificar un carácter Unicode como una constante hexadecimal de 16 bits. En los factores de regexp, \b no es el carácter de retroceso. 

d\ es lo mismo que [0-9]. Coincide con un dígito. \D es lo contrario: [^ 0-9].

\s es el mismo que [\f\n\r\t\u000B\u0020\u00a0\u2028\u2029]. Se trata de un conjunto parcial de espacios en blanco Unicode. \S es lo contrario: [^\f\n\r\t\u000B\u0020\u00a0\u2028\u2029].

\w es el mismo que [0-9A-Z_a-z]. \W es lo contrario: [^ 0-9A-Z_a-z]. Se supone que esto representa los caracteres que aparecen en palabras. Por desgracia, la clase que define es inútil para trabajar con prácticamente cualquier lenguaje real. Si usted necesita para que coincida con una clase de cartas, debe especificar su propia clase.

Una clase de letra simple es [A-Za-z\u00C0-\u1FFF\u2800-\uFFFD]. Incluye a todas las letras Unicode, pero también incluye miles de caracteres que no son letras. Unicode es grande y complejo. Una clase de letra exacta del plano multilingüe básico es posible, pero sería enorme e ineficiente. Expresiones regulares de JavaScript proporcionan muy poco apoyo para la internacionalización.

\b estaba destinado a ser un ancla-límite de la palabra que haría más fácil que coincida el texto en los límites de palabra. Por desgracia, utiliza \ w para encontrar los límites de palabra, por lo que es completamente inútil para aplicaciones multilingües. Esto no es una buena parte.

\1 es una referencia al texto que fue capturado por el grupo 1 de manera que se puede adaptar de nuevo. Por ejemplo, puede buscar texto para las palabras duplicadas con:

var doubled_words = /[A-Za-z\u00C0-\u1FFF\u2800-\uFFFD'\-]+\s+\1/gi;

doubled_words busca ocurrencias de palabras (cadenas que contienen 1 o más letras) seguido de un espacio en blanco seguido de la misma palabra. 
\2 es una referencia al grupo 2, \3 es una referencia al grupo 3, y así sucesivamente. 

Añadir nuevo comentario

CAPTCHA
Esta pregunta es para comprobar si usted es un visitante humano y prevenir envíos de spam automatizado.
7 + 9 =
Resuelva este simple problema matemático y escriba la solución; por ejemplo: Para 1+3, escriba 4.