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.
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:
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))
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.
Questa è la stringa in cui stiamo cercando il modello.
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.
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. IlnumberOfRanges
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 usarerangeAtIndex (0)
, potresti anche usare ilgamma
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 catturan
. 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 affidamentoenumerateMatchesInString (_: opzioni: Gamma: usingBlock :)
, che è il metodo più flessibile e generale (e quindi complicato) nelNSRegularExpression
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 ilReportCompletion
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 falsePasso 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
ofalso
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 prestoUsiamo 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
avero
. Esciamo dal blocco impostandostop.memory
avero
. 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 restituiscefalso
.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.