Expresiones rápidas y regulares Swift

En el primer tutorial de esta serie, exploramos los conceptos básicos de las expresiones regulares, incluida la sintaxis para escribir expresiones regulares. En este tutorial, aplicamos lo que hemos aprendido hasta ahora para aprovechar las expresiones regulares en Swift.

1. Expresiones regulares en Swift

Abre Xcode, crea un nuevo Patio de Juegos, nómbralo RegExTut, y establecer Plataforma a OS X. La elección de la plataforma, iOS u OS X, no hace ninguna diferencia con respecto a la API que vamos a utilizar.

Antes de comenzar, hay otra cosa que necesita saber. En Swift, necesitas usar dos barras invertidas, \\, por cada barra invertida que uses en una expresión regular. La razón tiene que ver con Swift que tiene literales de cadena de estilo C. La barra invertida se procesa como un escape de personaje además de su función en la interpolación de cadenas en Swift. En otras palabras, necesitas escapar del personaje de escape. Si eso suena raro, no te preocupes por eso. Solo recuerda, para usar dos barras invertidas en lugar de una.

En el primer ejemplo, algo artificial, imaginamos que estamos hurgando en una cadena en busca de un tipo de dirección de correo electrónico muy específico. La dirección de correo electrónico cumple los siguientes criterios:

  • La primera letra es la primera letra del nombre de la persona.
  • seguido de un punto
  • seguido del apellido de la persona
  • seguido del símbolo @
  • seguido de un nombre, representando a una universidad en el Reino Unido
  • seguido por .ac.uk, El dominio para instituciones académicas en el Reino Unido.

Agregue el siguiente código al patio de recreo y repasemos paso a paso este fragmento de código.

importar Cocoa // (1): let pat = "\\ b ([az]) \\. ([az] 2,) @ ([az] +) \\. ac \\. uk \\ b "// (2): deje testStr =" [email protected], [email protected] [email protected], [email protected], [email protected]. uk "// (3): deja regex = try! NSRegularExpression (pattern: pat, options: []) // (4): let matches = regex.matchesInString (testStr, options: [], range: NSRange (location: 0, length: testStr.characters.count))

Paso 1

Definimos un patrón. Tenga en cuenta las barras invertidas doblemente escapado. En la representación de expresiones regulares (normal), como la que se usa en el sitio web RegExr, esto sería ([a-z]) \. ([a-z] 2,) @ ([a-z] +) \. ac \ .uk. También tenga en cuenta el uso de paréntesis. Se están utilizando para definir grupos de captura con los que podemos extraer las subcadenas coincidentes con esa parte de la expresión regular.

Debería poder distinguir que el primer grupo de captura captura la primera letra del nombre del usuario, el segundo su apellido y el tercero el nombre de la universidad. Tenga en cuenta también el uso de la barra diagonal inversa para escapar del carácter de punto con el fin de representar su significado literal. Alternativamente, podríamos ponerlo en un conjunto de caracteres por sí mismo ([.]). En ese caso, no tendríamos que escapar..

Paso 2

Esta es la cadena en la que estamos buscando el patrón..

Paso 3

Creamos un NSRegularExpression Objeto, pasando en el patrón sin opciones. En la lista de opciones, puede especificar NSRegularExpressionOption constantes, tales como:

  • CaseInsensitive: Esta opción especifica que la coincidencia no distingue entre mayúsculas y minúsculas.
  • Ignorar los caracteres: Utilice esta opción si desea realizar una coincidencia literal, lo que significa que los metacaracteres no tienen un significado especial y se corresponden como caracteres comunes.
  • AnchorMatchLines: Usa esta opción si quieres el ^ y PS anclas para coincidir con el inicio y el final de las líneas (separadas por saltos de línea) en una sola cadena, en lugar del inicio y final de la cadena completa.

Debido a que el inicializador está lanzando, usamos el tratar palabra clave. Si pasamos una expresión regular no válida, por ejemplo, se produce un error..

Etapa 4

Buscamos coincidencias en la cadena de prueba invocando matchesInString (_: opciones: rango :), pasando un rango para indicar en qué parte de la cadena estamos interesados. Este método también acepta una lista de opciones. Para mantener las cosas simples, no pasamos ninguna opción en este ejemplo. Hablaré de opciones en el siguiente ejemplo..

Las coincidencias se devuelven como una matriz de NSTextCheckingResult objetos. Podemos extraer las coincidencias, incluidos los grupos de captura, de la siguiente manera:

para coincidencias en coincidencias para n en 0 ... 

El fragmento anterior itera a través de cada NSTextCheckingResult Objeto en la matriz. los numberOfRanges propiedad para cada partido en el ejemplo tiene un valor de 4, uno para toda la subcadena coincidente correspondiente a una dirección de correo electrónico (por ejemplo, [email protected]) y los tres restantes corresponden a los tres grupos de captura dentro del partido ("a", "khan" y "surrey "respectivamente).

los rangeAtIndex (_ :) método devuelve el rango de las subcadenas en la cadena para que podamos extraerlos. Tenga en cuenta que, en lugar de utilizar rangeAtIndex (0), También podrías usar el distancia propiedad para todo el partido.

Haga clic en el Mostrar resultado Botón en el panel de resultados a la derecha. Esto nos muestra "Surrey", el valor de testStr.substringWithRange (r) Para la última iteración del bucle. Haga clic derecho en el campo de resultados y seleccione Historia del valor para mostrar una historia de valores.

Puede modificar el código anterior para hacer algo significativo con las coincidencias y / o los grupos de captura.

Hay una forma conveniente de realizar operaciones de búsqueda y reemplazo, utilizando una cadena de plantilla que tiene una sintaxis especial para representar grupos de captura. Continuando con el ejemplo, supongamos que queremos reemplazar cada dirección de correo electrónico coincidente con una subcadena de la forma "apellido, inicial, universidad", podríamos hacer lo siguiente:

let replacementStr = regex.stringByReplacingMatchesInString (testStr, opciones: [], rango: NSRange (ubicación: 0, longitud: testStr.characters.count), withTemplate: "($ 2, $ 1, $ 3)")

Nota la $ n sintaxis en la plantilla, que actúa como un marcador de posición para el texto del grupo de captura norte. Manten eso en mente $ 0 representa todo el partido.

2. Un ejemplo más avanzado

los matchesInString (_: opciones: rango :) método es uno de varios métodos de conveniencia que se basan en enumerateMatchesInString (_: options: range: usingBlock :), ¿Cuál es el método más flexible y general (y por lo tanto complicado) en el NSRegularExpression clase. Este método llama a un bloque después de cada coincidencia, lo que le permite realizar cualquier acción que desee.

Al pasar en una o más reglas coincidentes, usando NSMatchingOptions constantes, puede asegurarse de que el bloque se invoca en otras ocasiones. Para operaciones de larga ejecución, puede especificar que el bloque se invoque periódicamente y terminar la operación en algún momento. Con el ReportCompletar Opción, usted especifica que el bloque debe ser invocado al finalizar.

El bloque tiene un parámetro de indicadores que informa cualquiera de estos estados para que pueda decidir qué acción tomar. Similar a algunos otros métodos de enumeración en el Fundación marco, el bloque también puede ser terminado a su discreción. Por ejemplo, si una coincidencia de larga duración no tiene éxito o si ha encontrado suficientes coincidencias para comenzar a procesar.

En este escenario, buscaremos en algún texto cadenas de caracteres que parezcan fechas y comprobaremos si hay una fecha en particular. Para mantener el ejemplo manejable, imaginaremos que las cadenas de fecha tienen la siguiente estructura:

  • un año con dos o cuatro dígitos (por ejemplo, 09 o 2009)
  • solo desde el presente siglo (entre 2000 y 2099), por lo que se rechazaría 1982 y 16 se interpretaría automáticamente como 2016
  • seguido de un separador
  • seguido de un número entre 1 y 12 que representa el mes
  • seguido de un separador
  • Concluyendo con un número entre 1 y 31 que representa el día.

Los meses y fechas de un solo dígito posiblemente se pueden rellenar con un cero inicial. Los separadores válidos son un guión, un punto y una barra diagonal. Aparte de los requisitos anteriores, no verificaremos si una fecha es realmente válida. Por ejemplo, estamos bien con fechas como 2000-04-31 (abril tiene solo 30 días) y 2009-02-29 (2009 no es un año bisiesto, lo que significa que febrero tiene solo 28 días) que no son fechas reales.

Agregue el siguiente código al patio de recreo y repasemos paso a paso este fragmento de código.

// (1): typealias PossibleDate = (año: Int, mes: Int, día: Int) // (2): func dateSearch (texto: String, _ date: PossibleDate) -> Bool // (3): let datePattern = "\\ b (?: 20)? (\\ d \\ d) [-. /] (0? [1-9] | 1 [0-2]) [-. /] (3 [ 0-1] | [1-2] [0-9] | 0? [1-9]) \\ b "let dateRegex = try! NSRegularExpression (pattern: datePattern, options: []) // (4): var wasFound: Bool = false // (5): dateRegex.enumerateMatchesInString (text, options: [], range: NSRange (location: 0, length: text.characters.count)) // (6): (match, _, stop) in var dateArr = [Int] () para n en 1… 3 let range = match! .rangeAtIndex (n) let r = text.startIndex.advancedBy (range.location) ... < text.startIndex.advancedBy(range.location+range.length) dateArr.append(Int(text.substringWithRange(r))!)  // (7): if dateArr[0] == date.year && dateArr[1] == date.month && dateArr[2] == date.day  // (8): wasFound = true stop.memory = true   return wasFound  let text = " 2015/10/10,11-10-20, 13/2/2 1981-2-2 2010-13-10" let date1 = PossibleDate(15, 10, 10) let date2 = PossibleDate(13, 1, 1) dateSearch(text, date1) // returns true dateSearch(text, date2) // returns false

Paso 1

La fecha cuya existencia verificamos estará en un formato estandarizado. Utilizamos una tupla con nombre. Solo pasamos un entero de dos dígitos a año, es decir, 16 significa 2016.

Paso 2

Nuestra tarea es enumerar las coincidencias que parecen fechas, extraer los componentes de año, mes y día de ellas y verificar si coinciden con la fecha que pasamos. Crearemos una función para hacer todo esto por nosotros. La función devuelve cierto o falso Dependiendo de si la fecha fue encontrada o no.

Paso 3

El patrón de fecha tiene algunas características interesantes:

  • Nota el fragmento (?: 20)?. Si reemplazamos este fragmento con (20)?, ojalá reconozca que esto significa que estamos bien con que "20" (que representa el milenio) esté presente en el año o no. Los paréntesis son necesarios para agrupar, pero no nos importa formar un grupo de captura con este par de paréntesis y eso es lo que ?: poco es para.
  • Los posibles separadores dentro del conjunto de caracteres. [-. /] no es necesario escapar para representar su ser literal. Puedes pensarlo de esta manera. El guion, -, está al principio, por lo que no puede representar un rango. Y no tiene sentido para el período, ., para representar cualquier carácter dentro de un conjunto de caracteres, ya que lo hace igualmente bien fuera.
  • Hacemos un uso intensivo de la barra vertical de alternancia para representar las distintas posibilidades de dígitos de mes y fecha.

Etapa 4

La variable booleana extraviado será devuelto por la función, indicando si se encontró o no la fecha buscada.

Paso 5

los enumerateMatchesInString (_: options: range: usingBlock :) se está llamando No estamos usando ninguna de las opciones y estamos pasando el rango completo del texto que se busca.

Paso 6

El objeto de bloque, invocado después de cada partido, tiene tres parámetros:

  • el partido (a NSTextCheckingResult)
  • banderas que representan el estado actual del proceso de coincidencia (que estamos ignorando aquí)
  • un booleano detener variable, que podemos configurar dentro del bloque para salir temprano

Usamos el booleano para salir del bloque si encontramos la fecha que buscamos ya que no necesitamos buscar más. El código que extrae los componentes de la fecha es bastante similar al del ejemplo anterior..

Paso 7

Verificamos si los componentes extraídos de la subcadena coincidente son iguales a los componentes de la fecha deseada. Tenga en cuenta que forzamos un yeso a En t, lo cual estamos seguros de que no fallará porque creamos los grupos de captura correspondientes para que solo coincidan con los dígitos.

Paso 8

Si se encuentra una coincidencia, configuramos extraviado a cierto. Salimos del bloque estableciendo. Stop.memorya cierto. Hacemos esto porque detener es un puntero a un booleano y la forma en que Swift trata con la memoria "apuntada a" es a través de la propiedad de memoria.

Observe que la subcadena "2015/10/10" en nuestro texto corresponde a Posible Fecha (15, 10, 10), Es por eso que la función retorna. cierto en el primer caso. Sin embargo, ninguna cadena en el texto corresponde a Posible Fecha (13, 1, 1), es decir, "2013-01-01" y la segunda llamada a la función devuelve falso.

Conclusión

Hemos tomado una visión pausada, aunque razonablemente detallada, de cómo funcionan las expresiones regulares, pero hay mucho más que aprender si está interesado, como mirar hacia el futuro y Mira atrás afirmaciones, aplicando expresiones regulares a las cadenas Unicode, además de observar las diversas opciones que hemos hechado en la API de Foundation.

Incluso si decide no profundizar, esperamos que haya recogido lo suficiente aquí para poder identificar las situaciones en las que las expresiones regulares pueden ser útiles, así como algunos consejos sobre cómo diseñar expresiones regulares para resolver sus problemas de búsqueda de patrones.