Swift e Regular Expressions Swift

Nel primo tutorial di questa serie, abbiamo esplorato le basi delle espressioni regolari, inclusa la sintassi per scrivere espressioni regolari. In questo tutorial applichiamo ciò che abbiamo imparato finora per sfruttare le espressioni regolari in Swift.

1. Espressioni regolari in Swift

Apri Xcode, crea un nuovo Playground, chiamalo RegExTut, e impostare piattaforma a OS X. La scelta della piattaforma, iOS o OS X, non fa differenza per quanto riguarda l'API che useremo.

Prima di iniziare, c'è un'altra cosa che devi sapere. In Swift, devi usare due barre inverse, \\, per ogni backslash che usi in un'espressione regolare. Il motivo ha a che fare con Swift che ha delle stringhe di tipo C in stile. Il backslash viene elaborato come escape di un personaggio in aggiunta al suo ruolo nell'interpolazione delle stringhe in Swift. In altre parole, è necessario sfuggire al personaggio di escape. Se sembra strano, non preoccuparti. Ricorda, per usare due barre inverse invece di una.

Nel primo esempio, un po 'inventato, immaginiamo di rovistare in una stringa alla ricerca di un tipo di indirizzo email molto specifico. L'indirizzo email soddisfa i seguenti criteri:

  • la prima lettera è la prima lettera del nome della persona
  • seguito da un punto
  • seguito dal cognome della persona
  • seguito dal simbolo @
  • seguito da un nome, che rappresenta un'università nel Regno Unito
  • seguito da .ac.uk, il dominio per le istituzioni accademiche nel Regno Unito

Aggiungi il seguente codice al parco giochi e analizziamo passo per passo questo snippet di codice.

import Cocoa // (1): let pat = "\\ b ([az]) \\. ([az] 2,) @ ([az] +) \\. ac \\. uk \\ b "// (2): let testStr =" [email protected], [email protected] [email protected], [email protected], [email protected]. uk "// (3): lascia regex = prova! NSRegularExpression (pattern: pat, opzioni: []) // (4): let matches = regex.matchesInString (testStr, opzioni: [], intervallo: NSRange (posizione: 0, lunghezza: testStr.characters.count))

Passo 1

Definiamo un modello. Nota i backslash doppiamente sfuggiti. In una (normale) rappresentazione regex, come quella usata sul sito web RegExr, sarebbe questa ([A-z]) \. ([A-z] 2) @ ([a-z] +) \. Ac \ .uk. Notare anche l'uso delle parentesi. Vengono utilizzati per definire i gruppi di acquisizione con i quali è possibile estrarre le sottostringhe corrispondenti a quella parte dell'espressione regolare.

Dovresti essere in grado di capire che il primo gruppo di cattura cattura la prima lettera del nome dell'utente, la seconda il loro cognome e la terza il nome dell'università. Nota anche l'uso del backslash per sfuggire al carattere del periodo per rappresentare il suo significato letterale. In alternativa, potremmo metterlo in un set di caratteri da solo ([.]). In tal caso, non avremmo bisogno di evitarlo.

Passo 2

Questa è la stringa in cui stiamo cercando il modello.

Passaggio 3

Creiamo a NSRegularExpression oggetto, passando nel modello senza opzioni. Nell'elenco di opzioni, è possibile specificare NSRegularExpressionOption costanti, come ad esempio:

  • CASEINSENSITIVE: Questa opzione specifica che la corrispondenza non fa distinzione tra maiuscole e minuscole.
  • IgnoreMetacharacters: Usa questa opzione se vuoi eseguire una corrispondenza letterale, il che significa che i metacaratteri non hanno un significato speciale e corrispondono a se stessi come normali personaggi.
  • AnchorMatchLines: Usa questa opzione se vuoi ^ e $ ancore per abbinare l'inizio e la fine delle linee (separate da interruzioni di riga) in una singola stringa, piuttosto che l'inizio e la fine dell'intera stringa.

Poiché l'inizializzatore viene lanciato, usiamo il provare parola chiave. Se passiamo in un'espressione regolare non valida, ad esempio, viene generato un errore.

Passaggio 4

Cerchiamo le corrispondenze nella stringa di test richiamando matchesInString (_: opzioni: Gamma :), passando in un intervallo per indicare quale parte della stringa ci interessa. Questo metodo accetta anche un elenco di opzioni. Per semplificare le cose, non passiamo in nessuna opzione in questo esempio. Parlerò delle opzioni nel prossimo esempio.

Le corrispondenze vengono restituite come una matrice di NSTextCheckingResult oggetti. Possiamo estrarre le corrispondenze, inclusi i gruppi di cattura, come segue:

per la partita nelle partite per n in 0 ... 

Il frammento di cui sopra iterates attraverso ciascuno NSTextCheckingResult oggetto nell'array. Il numberOfRanges la proprietà per ogni corrispondenza nell'esempio ha un valore di 4, uno per l'intera sottostringa corrispondente corrispondente a un indirizzo di posta elettronica (ad esempio, [email protected]) e gli altri tre corrispondono ai tre gruppi di cattura all'interno della corrispondenza ("a", "khan" e "surrey" "rispettivamente).

Il rangeAtIndex (_ :) metodo restituisce l'intervallo delle sottostringhe nella stringa in modo da poterle estrarre. Si noti che, invece di usare rangeAtIndex (0), potresti anche usare il gamma proprietà per l'intera partita.

Clicca il Mostra risultato pulsante nel pannello dei risultati a destra. Questo ci mostra "Surrey", il valore di testStr.substringWithRange (r) per l'ultima iterazione del ciclo. Fare clic con il tasto destro sul campo del risultato e selezionare Storia del valore per mostrare una storia di valori.

È possibile modificare il codice precedente per fare qualcosa di significativo con le partite e / oi gruppi di cattura.

Esiste un modo conveniente per eseguire operazioni di ricerca e sostituzione, utilizzando una stringa di modello con una sintassi speciale per rappresentare i gruppi di cattura. Proseguendo con l'esempio, supponiamo di voler sostituire ogni indirizzo email abbinato con una sottostringa del modulo "cognome, iniziale, università", potremmo fare quanto segue:

let replaceStr = regex.stringByReplacingMatchesInString (testStr, opzioni: [], intervallo: NSRange (posizione: 0, lunghezza: testStr.characters.count), withTemplate: "($ 2, $ 1, $ 3)")

Notare la $ n sintassi nel modello, che funge da segnaposto per il testo del gruppo di cattura n. Tieni presente che $ 0 rappresenta l'intera partita.

2. Un esempio più avanzato

Il matchesInString (_: opzioni: Gamma :) il metodo è uno dei molti metodi di convenienza su cui fare affidamento enumerateMatchesInString (_: opzioni: Gamma: usingBlock :), che è il metodo più flessibile e generale (e quindi complicato) nel NSRegularExpression classe. Questo metodo chiama un blocco dopo ogni partita, permettendoti di eseguire qualsiasi azione tu voglia.

Passando in una o più regole di corrispondenza, utilizzando NSMatchingOptions costanti, puoi assicurarti che il blocco venga richiamato in altre occasioni. Per operazioni a esecuzione prolungata, è possibile specificare che il blocco venga richiamato periodicamente e interrompere l'operazione in un determinato momento. Con il ReportCompletion opzione, si specifica che il blocco deve essere richiamato al completamento.

Il blocco ha un parametro flags che riporta uno di questi stati in modo da poter decidere quale azione intraprendere. Simile ad altri metodi di enumerazione nel Fondazione quadro, il blocco può anche essere chiuso a vostra discrezione. Ad esempio, se una corrispondenza di long run non ha esito positivo o se hai trovato abbastanza match per iniziare l'elaborazione.

In questo scenario, cercheremo del testo per stringhe che assomigliano a date e controlliamo se è presente una determinata data. Per mantenere l'esempio gestibile, immaginiamo che le stringhe di data abbiano la seguente struttura:

  • un anno con due o quattro cifre (ad esempio, 09 o 2009)
  • solo dal secolo presente (tra il 2000 e il 2099) il 1982 verrebbe respinto e 16 sarebbero automaticamente interpretati come il 2016
  • seguito da un separatore
  • seguito da un numero compreso tra 1 e 12 che rappresenta il mese
  • seguito da un separatore
  • concludendo con un numero compreso tra 1 e 31 che rappresenta il giorno

È possibile che i mesi e le date a una sola cifra vengano riempiti con uno zero iniziale. I separatori validi sono un trattino, un punto e una barra. Oltre ai suddetti requisiti, non verificheremo se una data sia effettivamente valida. Ad esempio, stiamo andando bene con date come 2000-04-31 (aprile ha solo 30 giorni) e 2009-02-29 (il 2009 non è un anno bisestile, il che significa che febbraio ha solo 28 giorni) che non sono date reali.

Aggiungi il seguente codice al parco giochi e analizziamo passo per passo questo snippet di codice.

// (1): typealias PossibleDate = (year: Int, month: Int, day: Int) // (2): func dateSearch (testo: 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 = prova! NSRegularExpression (pattern: datePattern, opzioni: []) // (4): var wasFound: Bool = false // (5): dateRegex.enumerateMatchesInString (testo, opzioni: [], intervallo: NSRange (posizione: 0, lunghezza: text.characters.count)) // (6): (match, _, stop) in var dateArr = [Int] () per n in 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

Passo 1

La data di cui stiamo controllando l'esistenza sarà in un formato standardizzato. Usiamo una tupla chiamata. Passiamo solo un numero intero a due cifre all'anno, ovvero 16 significa 2016.

Passo 2

Il nostro compito è quello di enumerare attraverso le partite che sembrano date, estrarre da loro i componenti dell'anno, del mese e del giorno e verificare se corrispondono alla data in cui siamo passati. Creeremo una funzione per fare tutto questo per noi. La funzione ritorna vero o falso a seconda che la data sia stata trovata o meno.

Passaggio 3

Il pattern della data ha alcune caratteristiche interessanti:

  • Nota il frammento (:? 20)?. Se abbiamo sostituito questo frammento con (20)?, si spera che riconoscerete che questo significa che stiamo bene con il "20" (che rappresenta il millennio) presente o no nell'anno. Le parentesi sono necessarie per il raggruppamento, ma non ci interessa creare un gruppo di cattura con questa coppia di parentesi e questo è ciò che ?: il bit è per.
  • I possibili separatori all'interno del set di caratteri [-. /] non è necessario essere scappati per rappresentare i loro sé letterali. Puoi pensare in questo modo. Il trattino, -, è all'inizio quindi non può rappresentare un intervallo. E non ha senso per il periodo, ., per rappresentare qualsiasi personaggio all'interno di un set di caratteri poiché lo fa altrettanto bene all'esterno.
  • Facciamo un uso pesante della barra verticale per l'alternanza per rappresentare le varie possibilità di cifre del mese e della data.

Passaggio 4

La variabile booleana non trovato verrà restituito dalla funzione, indicando se la data cercata è stata trovata o meno.

Passaggio 5

Il enumerateMatchesInString (_: opzioni: Gamma: usingBlock :) viene chiamato. Non stiamo usando nessuna delle opzioni e stiamo passando nell'intero intervallo del testo cercato.

Passaggio 6

L'oggetto block, invocato dopo ogni match, ha tre parametri:

  • la partita (a NSTextCheckingResult)
  • bandiere che rappresentano lo stato attuale del processo di abbinamento (che stiamo ignorando qui)
  • un booleano Stop variabile, che possiamo impostare all'interno del blocco per uscire presto

Usiamo il booleano per uscire dal blocco se troviamo la data che stiamo cercando poiché non abbiamo bisogno di cercare oltre. Il codice che estrae i componenti della data è abbastanza simile all'esempio precedente.

Passaggio 7

Controlliamo se i componenti estratti dalla sottostringa corrispondente corrispondono ai componenti della data desiderata. Nota che costringiamo un cast a Int, che sicuramente non falliremo perché abbiamo creato i corrispondenti gruppi di cattura per abbinare solo cifre.

Passaggio 8

Se viene trovata una partita, abbiamo impostato non trovato a vero. Esciamo dal blocco impostando stop.memorya vero. Lo facciamo perché Stop è un puntatore-a-a-booleana e il modo in cui Swift si occupa della memoria "puntata" è attraverso la proprietà della memoria.

Osservare che la sottostringa "2015/10/10" nel nostro testo corrisponde a PossibleDate (15, 10, 10), ecco perché la funzione ritorna vero nel primo caso. Tuttavia, nessuna stringa nel testo corrisponde a PossibleDate (13, 1, 1), ovvero "2013-01-01" e la seconda chiamata alla funzione restituisce falso.

Conclusione

Abbiamo analizzato con calma, ma in modo abbastanza dettagliato, come funzionano le espressioni regolari, ma c'è molto altro da imparare se sei interessato, ad esempio guarda avanti e guarda dietro asserzioni, applicando espressioni regolari alle stringhe Unicode, oltre a esaminare le varie opzioni che abbiamo sfogliato nell'API Foundation.

Anche se decidi di non scavare più a fondo, si spera che tu abbia raccolto abbastanza qui per essere in grado di identificare le situazioni in cui le regex potrebbero tornare utili e alcuni indicatori su come progettare espressioni regolari per risolvere i problemi di ricerca dei pattern.