7 days of WordPress plugins, themes & templates - for free!* Unlimited asset downloads! Start 7-Day Free Trial
Advertisement
  1. Code
  2. Swift

Swift y Expresiones regulares: Sintaxis

Scroll to top
Read Time: 18 mins
This post is part of a series called Swift and Regular Expressions: Syntax.
Swift and Regular Expressions: Swift

Spanish (Español) translation by Elías Nicolás (you can also view the original English article)

1. Introducción

En pocas palabras, las expresiones regulares (regexes o regexps para abreviar) son una forma de especificar patrones de cadena. Usted está indudablemente familiarizado con la función de búsqueda y reemplazo en su editor de texto favorito o IDE. Puede buscar palabras y frases exactas. También puede activar opciones, como la insensibilidad de mayúsculas y minúsculas, para que la búsqueda de la palabra "color" encuentre también "Color", "COLOR" y "CoLoR". ¿Pero qué si usted quisiera buscar las variaciones ortográficas de la palabra "color" (deletreo americano: color, deletreo británico: colour) sin tener que realizar dos búsquedas separadas?

Si ese ejemplo parece demasiado simple, ¿qué tal si quieres buscar todas las variaciones ortográficas del nombre en inglés "Katherine" (Catherine, Katharine, Kathreen, Kathryn, etc. por nombrar algunos). De forma más general, es posible que desee buscar en un documento todas las cadenas que se asemejaban a números hexadecimales, fechas, números de teléfono, direcciones de correo electrónico, números de tarjetas de crédito, etc.

Las expresiones regulares son una manera poderosa de (parcial o totalmente) abordar estos (y muchos otros) problemas prácticos que implican el texto.

Esquema

La estructura de este tutorial es la siguiente. Introduciré los conceptos básicos que necesitas para comprender adaptando un enfoque utilizado en los libros de texto teóricos (después de eliminar cualquier rigor innecesario o pedantería). Yo prefiero este enfoque porque le permite poner su comprensión de tal vez el 70% de la funcionalidad que necesitará, en el contexto de unos pocos principios básicos. El 30% restante son características más avanzadas que puedes aprender más adelante o saltar, a menos que tengas la intención de convertirte en un maestro regex.

Hay una cantidad copiosa de sintaxis asociada con expresiones regulares, pero la mayor parte está ahí para permitirle aplicar las ideas básicas lo más sucintamente posible. Voy a introducir estos de forma incremental, en lugar de dejar caer una gran tabla o listar para que pueda memorizar.

En lugar de saltar directamente a una implementación de Swift, vamos a explorar los conceptos básicos a través de una excelente herramienta en línea que le ayudará a diseñar y evaluar expresiones regulares con la cantidad mínima de fricción y equipaje innecesario. Una vez que te sientas cómodo con las ideas principales, escribir código Swift es básicamente un problema de mapear tu comprensión a la Swift API.

A lo largo de todo, trataremos de mantener una mentalidad pragmática. Regexes no son la mejor herramienta para cada situación de procesamiento de cadenas. En la práctica, debemos identificar situaciones en las que las expresiones regulares funcionan muy bien y las situaciones en las que no. También hay un punto medio donde se pueden usar regexes para hacer parte del trabajo (usualmente algunos preprocesos y filtrado) y el resto del trabajo a la lógica algorítmica.

Conceptos básicos

Las expresiones regulares tienen sus fundamentos teóricos en la "teoría de la computación", uno de los temas estudiados por la informática, donde desempeñan el papel de la entrada aplicada a una clase específica de máquinas de computación abstracta llamadas autómatas finitos.

Relájese, sin embargo, no está obligado a estudiar los antecedentes teóricos para utilizar expresiones regulares prácticamente. Sólo los menciono porque el enfoque que usaré para motivar inicialmente las expresiones regulares desde el principio refleja el enfoque utilizado en los libros de texto de ciencias de la computación para definir expresiones regulares "teóricas".

Asumiendo que usted tiene cierta familiaridad con la recursión, quisiera que usted tenga presente cómo se definen las funciones recursivas. Una función se define en términos de versiones más simples de sí mismo y, si rastrea una definición recursiva, debe terminar en un caso base que se define explícitamente. Digo esto porque nuestra definición a continuación va a ser recursiva también.

Tenga en cuenta que cuando hablamos de cuerdas en general, implícitamente tenemos un personaje en mente, como ASCII, Unicode, etc. Vamos a fingir por el momento que vivimos en un universo en el que las cadenas están compuestas por las 26 letras de las minúsculas Alfabeto (a, b, ... z) y nada más.

Reglas

Comenzamos afirmando que cada carácter en este conjunto puede ser considerado como una expresión regular que coincide con una cadena. Así a como una expresión regular coincide con "a" (considerada como una cadena), b es una regex que coincide con la cadena "b", etc. También digamos que existe una expresión regular "vacía" que coincide Ɛ con la cadena vacía "". Tales casos corresponden a los "casos base" triviales de la recursividad.

Ahora, consideramos las siguientes reglas que nos ayudan a crear nuevas expresiones regulares a partir de las existentes:

  1. La concatenación (es decir, "encadenamiento conjunto") de cualquier dos expresiones regulares es una nueva expresión regular que coincide con la concatenación de dos cadenas que coinciden con las expresiones regulares originales.
  2. La alternancia de dos expresiones regulares es una nueva expresión regular que coincide con cualquiera de las dos expresiones regulares originales.
  3. La estrella Kleene de una expresión regular coincide con cero o más instancias adyacentes de lo que coincida con la expresión regular original.

Hagamos esto concreto con varios ejemplos simples con nuestras cadenas alfabéticas.

Ejemplo 1

A partir de la regla 1, a y b son expresiones regulares que coinciden con "a" y "b", significa que ab es una expresión regular que coincide con la cadena "ab". Ya que ab y c son expresiones regulares, abc es una expresión regular que coincide con la cadena "abc", y así sucesivamente. Continuando de esta manera, podemos hacer arbitrarias largas expresiones regulares que coincidan con una cadena con caracteres idénticos. Nada interesante ha ocurrido todavía.

Ejemplo 2

De la regla 2, o y a siendo expresiones regulares, o|a coincide con "o" o "a". La barra vertical representa la alternancia. c y t son expresiones regulares y, combinadas con la regla 1, podemos afirmar que c(o|a)t es una expresión regular. Los paréntesis se utilizan para agrupar.

¿Qué coincide? c y t sólo coinciden consigo mismos, lo que significa que el regex c(o|a)t coincide con "c" seguido de un "a" o un "o" seguido de "t", por ejemplo, la cadena "cat" o "cuna". Tenga en cuenta que no coincide con "coat" como o|a sólo coincide con "a" o "o", pero no tanto a la vez. Ahora las cosas están empezando a ser interesantes.

Ejemplo 3

De la regla 3, a* coincide con cero o más instancias de "a". Hace coincidir la cadena vacía o las cadenas "a", "aa", "aaa", y así sucesivamente. Vamos a ejercer esta regla en conjunción con las otras dos reglas.

¿Qué coincide con ho*t? Coincide con "ht" (con cero casos de "o"), "hot", "hoot", "hooot", y así sucesivamente. ¿Qué pasa con b(o|a)*? Puede coincidir con "b" seguido de cualquier número de instancias de "o" y "a" (incluyendo ninguno de ellos). "B", "boa", "baa", "bao", "baooaoaoaoo" son sólo algunos de los números infinitos de cadenas que coincide esta expresión regular. Observe de nuevo que los paréntesis se utilizan para agrupar la parte de la expresión regular a la que se está aplicando *.

Ejemplo 4

Intentemos descubrir expresiones regulares que coincidan con cadenas que ya tenemos en mente. ¿Cómo haríamos una expresión regular que reconozca el balido de las ovejas, que considero como cualquier número de repeticiones del sonido básico "baa" ("baa", "baabaa", "baabaabaa", etc.)

Si dijo, (baa)*, entonces está casi correcto. Pero note que esta expresión regular coincidiría con la cadena vacía también, que no queremos. En otras palabras, queremos ignorar a las ovejas que no balan. baa(baa)* es la expresión regular que buscamos. Del mismo modo, una vaca mooing podría ser moo(moo)*. ¿Cómo podemos reconocer el sonido de cualquiera de los dos animales? Simple. Utilice la alternancia. baa(baa)*|moo(moo)*

Si ha entendido las ideas anteriores, felicitaciones, va por buen camino.

2. Cuestiones de Sintaxis

Recordemos que colocamos una tonta restricción en nuestras cadenas. Sólo podían estar compuestos de letras minúsculas del alfabeto. Ahora vamos a eliminar esta restricción y considerar todas las cadenas compuestas de caracteres ASCII.

Debemos darnos cuenta de que, para que las expresiones regulares sean una herramienta conveniente, ellas mismas necesitan ser representadas como cadenas. Así, a diferencia de lo anterior, ya no podemos usar caracteres como *, |, (,), etc. sin siquiera señalar si los estamos utilizando como caracteres "especiales" que representan la alternación, la agrupación, etc. o si los estamos tratando como ordinarios Caracteres que necesitan ser emparejados literalmente.

La solución es tratar estos y otros "metacaracteres" que pueden tener un significado especial. Para cambiar entre un uso y el otro, necesitamos poder escaparlos. Esto es similar a la idea de usar "\n" (sanitizando el n) para indicar una nueva línea en una cadena. Es un poco más complicado en que, dependiendo del carácter de contexto que es normalmente "meta", podría representar su yo literal sin escape. Veremos ejemplos de esto más adelante.

Otra cosa que valoramos es la concisión. Muchas expresiones regulares que se pueden expresar utilizando sólo la notación de la sección anterior sería tediosamente verbosa. Por ejemplo, supongamos que sólo desea encontrar todas las dos cadenas de caracteres compuestas por una letra minúscula seguida de un número (por ejemplo, cadenas como "a0", "b9", "z3", etc.). Usando la notación que discutimos anteriormente, esto daría lugar a la siguiente expresión regular:

Sólo teclear ese monstruo me agoto.

¿No parece una mejor representación el [abcdefghijklmnopqrstuvwxyz][0123456789]? Observe los metacaracteres [ y ] que significan un conjunto de caracteres, cualquiera de los cuales da una coincidencia positiva. En realidad, si consideramos que las letras a a z, y los números 0 a 9 ocurren en secuencia en el conjunto ASCII, podemos reducir la expresión regular a un cool [a-z][0-9].

Dentro de los confines de un conjunto de caracteres, el guión, -, es otro metacarácter que indica un rango. Tenga en cuenta que puede ajustar varios rangos en el mismo par de corchetes. Por ejemplo, [0-9a-zA-Z] puede coincidir con cualquier carácter alfanumérico. Los 9 y a (y z y A) apretados uno contra el otro podrían parecer graciosos, pero recuerda que las expresiones regulares son todas sobre brevedad y el significado es claro.

Hablando de brevedad, hay formas aún más concisas de representar ciertas clases de personajes relacionados como veremos en un minuto. Tenga en cuenta que la barra de alternancia, |, sigue siendo sintaxis válida y útil como veremos en un momento.

Más sintaxis

Antes de empezar a practicar, echemos un vistazo a la sintaxis un poco más.

Punto

El punto, ., coincide con cualquier carácter individual, con la excepción de los saltos de línea. Esto significa que c.t puede coincidir con "cat", "crt", "c9t", "c% t", "c.t", "c t", y así sucesivamente. Si queremos igualar el periodo como un carácter ordinario, por ejemplo, para que coincida con la cadena "c.t", podríamos escaparla  (c\.t) o ponerlo en una clase de carácter propia (c[.]T).

En general, estas ideas se aplican a otros metacaracteres, como [,], (,), * y otros que aún no hemos encontrado.

Paréntesis

Los paréntesis (( y )) se utilizan para agrupar como vimos antes. Vamos a utilizar la palabra token para significar un solo carácter o una expresión entre paréntesis. La razón es que muchos operadores de regex pueden aplicarse a cualquiera.

Los paréntesis también se utilizan para definir grupos de captura, lo que le permite averiguar qué parte de su coincidencia fue capturada por un grupo de captura en particular en la expresión regular. Hablaré más sobre esta funcionalidad muy útil más adelante.

Más

Un + que sigue a un token es una o más instancias de ese token. En nuestro ejemplo del balar de las  ovejas, baa(baa)* podría representarse más sucintamente como (baa)+. Recuerde que * significa cero o más ocurrencias. Obsérvese que (baa)+ es diferente de baa+, porque en el primero se aplica el + al token baa, mientras que en el último sólo se aplica a a antes de él. En este último, coincide con cadenas como "baa", "baaa", y "baaaa".

Signo de interrogación

Un ? siguiendo a un token significa cero o una instancia de ese token.

Práctica

RegExr es una excelente herramienta en línea para experimentar con expresiones regulares. Cuando se sienta cómodo leyendo y escribiendo expresiones regulares, será mucho más fácil usar la API de expresión regular del marco de la Fundación. Incluso entonces, será más fácil probar su expresión regular en tiempo real en el sitio web en primer lugar.

Visite el sitio web y centrese en la parte principal de la página. Esto es lo que verás:

RegexrRegexrRegexr

Introduzca una expresión regular en el cuadro de la parte superior e introduzca el texto en el que está buscando coincidencias.

El "/g" al final del cuadro de expresión no forma parte de la expresión regular per se. Es un indicador que afecta al comportamiento global de coincidencia del motor regex. Agregando "/g" a la expresión regular, el motor busca todas las coincidencias posibles de la expresión regular en el texto, que es el comportamiento que queremos. El resaltado azul indica una coincidencia. Pasar el ratón sobre la expresión regular es una manera práctica de recordarle el significado de sus partes constitutivas.

Sepa que las expresiones regulares vienen en varios tipos, dependiendo del idioma o la biblioteca que está utilizando. Esto no sólo significa que la sintaxis puede ser un poco diferente entre los distintos sabores, sino también las capacidades y características. Swift, por ejemplo, utiliza la sintaxis de patrón especificada por ICU. No estoy seguro de qué sabor se utiliza en RegExr (que se ejecuta en JavaScript), pero dentro del alcance de este tutorial, son bastante similares, si no idénticos.

También lo aliento a explorar el panel en el lado izquierdo, que tiene mucha información presentada de una manera concisa.

Nuestro primer ejemplo práctico

Para evitar posibles confusiones, debo mencionar que, al hablar de concordancia de expresiones regulares, podríamos significar cualquiera de dos cosas:

  1. Buscando cualquier (o todas) subcadenas de una cadena que coincida con un regex
  2. Verificando si la cadena completa coincide o no con la expresión regular

El significado predeterminado con el que funcionan los motores regex es (1). Lo que hemos estado hablando hasta ahora es (2). Afortunadamente, es fácil implementar (2) por medio de metacaracteres que serán introducidos más adelante. No se preocupe por esto por ahora.

Empecemos sencillos probando nuestro ejemplo de baladas de ovejas. Escriba (baa)+ en el cuadro de expresión y algunos ejemplos para probar las coincidencias que se muestran a continuación.

Our First Practical ExampleOur First Practical ExampleOur First Practical Example

Espero que entiendas por qué los partidos que tuvieron éxito realmente tuvieron éxito y por qué los otros fracasaron. Incluso en este ejemplo simple, hay algunas cosas interesantes a señalar.

Coincidencias codiciosas

¿La cadena "baabaa" contiene dos coincidencias o una? En otras palabras, cada individual "baa" es una coincidencia o es "baabaa" entera una sola coincidencia? Esto se reduce a si se busca o no un "coincidencia codiciosa". Una coincidencia codiciosa intenta igualar la mayor cantidad de una cadena como sea posible.

En este momento el motor de regex está emparejando avariciosamente, lo que significa "baabaa" es una única coincidenica. Hay maneras de hacer coincidencias perezosas, pero eso es un tema más avanzado y, ya que ya tenemos nuestros platos llenos, no cubriremos eso en este tutorial.

La herramienta RegExr deja una brecha pequeña pero perceptible en el resaltado si dos partes adyacentes de una cadena cada uno individualmente (pero no colectivamente) coinciden con la expresión regular. Veremos un ejemplo de este comportamiento en un momento.

Mayúsculas y minúsculas

"Baabaa" falla debido a la mayúscula "B". Digamos que quería permitir que sólo la primera "B" fuera mayúscula, ¿cuál sería la expresión regular correspondiente? Trata de averiguarlo por ti mismo primero.

Una respuesta es (B|b)aa(baa)*. Ayuda si lo lees en voz alta. Una mayúscula o minúscula "b", seguido de "aa", seguido de cero o más instancias de "baa". Esto es viable, pero tenga en cuenta que esto podría volverse rápidamente un inconveniente, especialmente si queríamos ignorar la capitalización por completo. Por ejemplo, tendríamos que especificar alternativas para cada caso, lo que resultaría en algo difícil de manejar como ([Bb][Aa][Aa])+.

Afortunadamente, los motores de expresión regular suelen tener la opción de ignorarlas. En el caso de RegExr, haga clic en el botón que dice "flags" y marque la casilla de verificación "ignore case". Observe que la letra "i" está precedida a la lista de opciones al final de la expresión regular. Pruebe algunos ejemplos con letras mayúsculas y minúsculas, como "bAABaa".

Otro ejemplo

Tratemos de diseñar una expresión regular que pueda capturar variantes del nombre "Katherine". ¿Cómo abordarías este problema? Escribiría tantas variaciones, miraría las partes comunes y trataría de expresar en palabras las variaciones (con énfasis en las letras alternativas y opcionales) como una secuencia. A continuación, intentaría formular la expresión regular que asimila todas estas variaciones.

Vamos a probarlo con esta lista de variaciones: Katherine, Katharine, Catherine, Kathreen, Kathleen, Katryn y Catrin. Voy a dejarselo a ustedes anotar varios más si lo desean. Mirando estas variaciones, puedo decir aproximadamente que:

  • El nombre empieza por "k" o "c"
  • Seguido de "at"
  • Seguido posiblemente de una "h"
  • Posiblemente seguido por un "a" o "e"
  • Seguido por una "r" o "l"
  • Seguido por uno de "i", "ee" o "y"
  • Y definitivamente seguido por un "n"
  • Posiblemente un "e" al final

Con esta idea en mente, puedo llegar a la siguiente expresión regular:

Another ExampleAnother ExampleAnother Example

Tenga en cuenta que la primera línea "KatherineKatharine" tiene dos partidos sin separación entre ellos. Si lo observa de cerca en el editor de texto de RegExr, puede observar la pequeña interrupción en el resaltado entre las dos coincidencias, que es lo que estaba hablando antes.

Observe que la expresión regular anterior también coincide con nombres que no consideramos y que tal vez ni siquiera existan, por ejemplo, "Cathalin". En el presente contexto, esto no nos afecta negativamente en absoluto. Sin embargo, en algunas aplicaciones, como la validación de correo electrónico, desea ser más específico sobre las cadenas que coincide y las que rechaza. Esto por lo general se suma a la complejidad de la expresión regular.

Más sintaxis y ejemplos

Antes de pasar a Swift, me gustaría discutir algunos aspectos más de la sintaxis de las expresiones regulares.

Representaciones Concisas

Varias clases de caracteres relacionados tienen una representación concisa:

  • \w carácter alfanumérico, incluido subrayado, equivalente a [a-zA-Z0-9_]
  • \d representa un dígito, equivalente a [0-9]
  • \s representa espacios en blanco, es decir, espacio, tabulacion o salto de línea

Estas clases también tienen clases negativas correspondientes:

  • \W representa un carácter no alfanumérico, no subrayado
  • \D un no dígito
  • \S un carácter no espacio

Recuerde las clases sin capitalizar y luego recordar que la correspondiente capitalizada coincide con lo que la clase sin capitalizar no coincide. Tenga en cuenta que estos se pueden combinar incluyendo entre corchetes si es necesario. Por ejemplo, [\s\S] representa cualquier carácter, incluyendo saltos de línea. Recordemos que el punto . coincide con cualquier carácter excepto los saltos de línea.

Anclas

^ y $ son anclajes que representan el comienzo y el final de una cadena, respectivamente. ¿Recuerdas que escribí que querrías hacer coincidir una cadena entera, en lugar de buscar las coincidencias de subcadenas? Así es como lo haces. ^c[oau]t$ coincide con "cat", "cot" o "cut", pero no, digamos, "catch" o "recut".

Límites de palabras

\b representa un límite entre las palabras, tal como debido al espacio o la puntuación, y también el comienzo o el final de la cadena. Tenga en cuenta que es un poco diferente en que coincide con una posición en lugar de un carácter explícito. Podría ayudar a pensar en un límite de palabras como un divisor invisible que separa una palabra de la anterior / siguiente. Como era de esperar, \B representa "un no límite de palabra". \bcat\b encuentra coincidencias en "cat", "cat", "Hi, cat", pero no en "acat" o "catch".

Negación

La idea de la negación puede hacerse más específica usando el metacaracter ^ dentro de un conjunto de caracteres. Este es un uso completamente diferente de ^ desde el "inicio del ancla de cadena". Esto significa que, para la negación, ^ debe usarse en un conjunto de caracteres justo al principio. [^a] coincide con cualquier carácter además de la letra "a" y [^a-z] coincide con cualquier carácter excepto una letra minúscula.

¿Puede representar \W usando rangos de negación y carácter? La respuesta es [^A-Za-z0-9_]. ¿Qué crees que coincide con [a^] ? La respuesta es un carácter "a" o "^" ya que no se produjo al principio del conjunto de caracteres. Aquí "^" coincide literalmente.

Alternativamente, podríamos escapar explícitamente así: [\^a]. Esperanzadamente, usted está comenzando a desarrollar una cierta intuición de cómo sanitizacion funciona.

Cuantificadores

Vimos cómo * (y +) se puede utilizar para hacer coincidir un token cero o más (y uno o más) veces. Esta idea de hacer coincidir un token veces múltiples se puede hacer más específico utilizando cuantificadores dentro  {}. Por ejemplo, {2, 4} significa dos a cuatro coincidencias de la ficha anterior. {2,} significa dos o más coincidencias y {2} significa exactamente dos coincidencias.

Veremos ejemplos detallados que usan la mayoría de estos elementos en el próximo tutorial. Pero por el bien de la práctica, te aliento a hacer tus propios ejemplos y probar la sintaxis que acabamos de ver con la herramienta RegExr.

Conclusion

En este tutorial, nos hemos centrado principalmente en la teoría y la sintaxis de las expresiones regulares. En el siguiente tutorial, agregamos Swift a la mezcla. Antes de seguir adelante, asegúrese de entender lo que hemos cubierto en este tutorial usando RegExr.

Advertisement
Did you find this post useful?
Want a weekly email summary?
Subscribe below and we’ll send you a weekly email summary of all new Code tutorials. Never miss out on learning about the next big thing.
Advertisement
Looking for something to help kick start your next project?
Envato Market has a range of items for sale to help get you started.