Swift og Regular Expressions Swift

I den første opplæringen av denne serien, undersøkte vi grunnleggende om regulære uttrykk, inkludert syntaksen for å skrive vanlige uttrykk. I denne opplæringen bruker vi det vi har lært så langt til å utnytte vanlige uttrykk i Swift.

1. Regular Expressions in Swift

Åpne Xcode, lag en ny Lekeplass, navngi den RegExTut, og sett Plattform til OS X. Valget av plattformen, iOS eller OS X, gjør ingen forskjell med hensyn til API som vi skal bruke.

Før vi starter, er det en annen ting du trenger å vite om. I Swift må du bruke to tilbakeslag, \\, for hver tilbakeslag du bruker i et vanlig uttrykk. Årsaken har å gjøre med Swift som har C-stil strengstavler. Bakslaget blir behandlet som et tegnflykte i tillegg til sin rolle i strenginterpolering i Swift. Med andre ord, du trenger å unnslippe flyktekarakteren. Hvis det høres rart, ikke bekymre deg for det. Bare husk å bruke to tilbakeslag i stedet for en.

I det første, noe oppriktige eksemplet, forestiller vi oss at vi rømmer gjennom en streng som ser etter en veldig spesifikk type e-postadresse. E-postadressen oppfyller følgende kriterier:

  • Første bokstav er første bokstav i personens navn
  • etterfulgt av en periode
  • etterfulgt av personens etternavn
  • etterfulgt av @ -symbolet
  • etterfulgt av et navn som representerer et universitet i Storbritannia
  • etterfulgt av .ac.uk, domenet for akademiske institusjoner i Storbritannia

Legg til følgende kode på lekeplassen og la oss gå gjennom dette kodestykket trinnvis.

importer kakao // (1): la pat = "\\ b ([az]) \\. ([az] 2,) @ ([az] +) \\. ac \\. uk \\ b "// (2): la testStr =" [email protected], [email protected] [email protected], [email protected], [email protected]. uk "// (3): la regex = prøv! NSRegularExpression (mønster: pat, alternativer: []) // (4): la matches = regex.matchesInString (testStr, alternativer: [], rekkevidde: NSRange (plassering: 0, lengde: testStr.characters.count))

Trinn 1

Vi definerer et mønster. Legg merke til de dobbelte rømmede tilbakeslaget. I (normal) regex-representasjon, som den som ble brukt på RegExr-nettsiden, ville dette være ([A-z]) \. ([A-z] 2) @ ([a-z] +) \. Ac \ Uk. Legg også merke til bruken av parenteser. De blir brukt til å definere fangstgrupper som vi kan trekke ut delstringene sammen med den delen av det vanlige uttrykket.

Du bør kunne finne ut at den første fangstgruppen fanger første bokstav i brukerens navn, den andre en etternavn, og den tredje navnet på universitetet. Legg også merke til bruken av tilbakeslaget for å unnslippe perioden karakter for å representere sin bokstavelige betydning. Alternativt kan vi sette det i et tegnesett av seg selv ([.]). I så fall trenger vi ikke å unnslippe det.

Steg 2

Dette er strengen der vi søker etter mønsteret.

Trinn 3

Vi lager en NSRegularExpression objekt, passerer i mønsteret uten alternativer. I listen over alternativer kan du spesifisere NSRegularExpressionOption konstanter, for eksempel:

  • CaseInsensitive: Dette alternativet angir at matchingen er uaktsom.
  • IgnoreMetacharacters: Bruk dette alternativet hvis du vil utføre en bokstavlig kamp, ​​noe som betyr at meta tegnene ikke har en spesiell betydning og matche seg som vanlige tegn.
  • AnchorMatchLines: Bruk dette alternativet hvis du vil ha ^ og $ ankre for å matche starten og slutten av linjene (adskilt av linjeskift) i en enkelt streng, i stedet for starten og slutten av hele strengen.

Fordi initialisatoren kaster, bruker vi prøve søkeord. Hvis vi sender inn et ugyldig regulært uttrykk, blir det for eksempel kastet en feil.

Trinn 4

Vi søker etter kamper i teststrengen ved å påkalle matchesInString (_: alternativer: rekkevidde :), passerer i et område for å indikere hvilken del av strengen vi er interessert i. Denne metoden aksepterer også en liste over alternativer. For å holde ting enkelt, passerer vi ikke inn noen alternativer i dette eksemplet. Jeg vil snakke om alternativer i det neste eksemplet.

Kampene returneres som en rekke av NSTextCheckingResult objekter. Vi kan trekke ut kampene, inkludert fangstgruppene, som følger:

for kamp i kamper for n i 0 ... 

Ovennevnte bunke detererer gjennom hver NSTextCheckingResult objekt i arrayet. De numberOfRanges Egenskapen for hver kamp i eksemplet har en verdi på 4, en for hele substringen matchet tilsvarende en e-postadresse (for eksempel [email protected]) og de resterende tre tilsvarer de tre fangstgruppene i kampen ("a", "khan" og "surrey "henholdsvis).

De rangeAtIndex (_ :) Metoden returnerer rekkevidden av substringene i strengen slik at vi kan trekke dem ut. Merk at i stedet for å bruke rangeAtIndex (0), du kan også bruke område eiendom for hele kampen.

Klikk på Vis resultat knappen i resultatpanelet til høyre. Dette viser oss "Surrey", verdien av testStr.substringWithRange (r) for den siste iterasjonen av løkken. Høyreklikk på resultatfeltet og velg Verdi historie for å vise en historie med verdier.

Du kan endre koden ovenfor for å gjøre noe som er meningsfullt med kampene og / eller fangstgruppene.

Det er en praktisk måte å utføre finne og erstatte operasjoner ved hjelp av en malstreng som har en spesiell syntaks for å representere fangstgrupper. Å fortsette med eksemplet, antar at vi ønsket å erstatte alle matchede e-postadresser med en substring av skjemaet "etternavn, innledende, universitet", vi kunne gjøre følgende:

la replacedStr = regex.stringByReplacingMatchesInString (testStr, alternativer: [], rekkevidde: NSRange (plassering: 0, lengde: testStr.characters.count), medTemplate: "($ 2, $ 1, $ 3)")

Legg merke til $ n syntaks i malen, som fungerer som plassholder for teksten til fangstgruppen n. Husk at $ 0 representerer hele kampen.

2. Et mer avansert eksempel

De matchesInString (_: alternativer: rekkevidde :) Metoden er en av flere bekvemmelighetsmetoder som er avhengige av enumerateMatchesInString (_: alternativer: Område: usingBlock :), som er den mest fleksible og generelle (og derfor kompliserte) metoden i NSRegularExpression klasse. Denne metoden kaller en blokk etter hver kamp, ​​slik at du kan utføre hvilken handling du vil.

Ved å sende inn en eller flere matchende regler, bruker du NSMatchingOptions konstanter, kan du sørge for at blokken er påkalt ved andre anledninger. For langvarig drift kan du spesifisere at blokken påberopes periodisk og avslutte operasjonen på et tidspunkt. Med ReportCompletion alternativ, du angir at blokken skal påberopes ved ferdigstillelse.

Blokken har en flaggparameter som rapporterer noen av disse tilstandene, slik at du kan bestemme hvilken handling du skal ta. Ligner på noen andre oppsummeringsmetoder i Fundament rammeverket, kan blokken også avsluttes etter eget skjønn. For eksempel, hvis en lang løpende kamp ikke lykkes eller hvis du har funnet nok tre kamper for å begynne behandling.

I dette scenariet skal vi søke gjennom noen tekst for strenger som ser ut som datoer og sjekke om en bestemt dato er til stede. For å holde eksemplet håndterbart, vil vi forestille oss at datastrengene har følgende struktur:

  • et år med enten to eller fire siffer (for eksempel 09 eller 2009)
  • bare fra det nåværende århundre (mellom 2000 og 2099) så 1982 ville bli avvist, og 16 ville automatisk tolkes som 2016
  • etterfulgt av en separator
  • etterfulgt av et tall mellom 1 og 12 som representerer måneden
  • etterfulgt av en separator
  • avslutter med et tall mellom 1 og 31 som representerer dagen

Enkelt siffer måneder og datoer kan muligens være polstret med en ledende null. Gyldige separatorer er et dash, en periode og et fremoverstrekk. Bortsett fra ovenstående krav, vil vi ikke verifisere om en dato faktisk er gyldig. For eksempel er vi fine med datoer som 2000-04-31 (april har bare 30 dager) og 2009-02-29 (2009 er ikke et springår, noe som betyr at februar har bare 28 dager) som ikke er ekte datoer.

Legg til følgende kode på lekeplassen og la oss gå gjennom dette kodestykket trinnvis.

// (1): typealias PossibleDate = (år: Int, måned: Int, dag: Int) // (2): func dateSearch (tekst: String, _ date: PossibleDate) -> Bool // la datePattern = "\\ b (?: 20)? (\\ d \\ d) [-. /] (0? [1-9] | 1 [0-2]) [-. /] 0-1] | [1-2] [0-9] | 0? [1-9]) \\ b "la dateRegex = prøv! NSRegularExpression (mønster: datePattern, alternativer: []) // (4): var varFound: Bool = false // (5): dateRegex.enumerateMatchesInString (tekst, alternativer: [], rekkevidde: NSRange (plassering: 0, lengde: text.characters.count)) // (6): (match, _, stopp) i var dateArr = [Int] () for n i 1 ... 3 let range = match! .rangeAtIndex (n) la 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

Trinn 1

Datoen hvis eksistens vi ser etter, kommer til å være i et standardisert format. Vi bruker en navngitt tuple. Vi sender bare et tosifret heltall til år, det vil si 16 betyr 2016.

Steg 2

Vår oppgave er å oppsummere gjennom kamper som ser ut som datoer, trekk ut år, måned og dagskomponenter fra dem og sjekk om de stemmer overens med datoen vi passerte inn. Vi vil skape en funksjon for å gjøre alt dette for oss. Funksjonen returnerer ekte eller falsk avhengig av om datoen ble funnet eller ikke.

Trinn 3

Datamønsteret har noen interessante funksjoner:

  • Merk fragmentet (: 20)?. Hvis vi erstattet dette fragmentet med (20)?, forhåpentligvis vil du gjenkjenne at dette medførte at vi har det bra med "20" (som representerer årtusenet) som er til stede i året eller ikke. Parentesene er nødvendige for gruppering, men vi bryr oss ikke om å danne en fangstgruppe med dette par parentesene og det er det som ?: litt er for.
  • De mulige separatorene inne i tegnsettet [-. /] trenger ikke å bli rømt for å representere sine bokstavelige selv. Du kan tenke på det slik. Bindestrek, -, er i starten, så det kan ikke representere en rekkevidde. Og det gir ikke mening for perioden, ., å representere alle tegn i et tegnsett siden det gjør det like godt ute.
  • Vi gjør stor bruk av den vertikale linjen for veksling for å representere de ulike måneds- og datasiffermulighetene.

Trinn 4

Den boolske variabelen ikke funnet vil bli returnert av funksjonen, som indikerer om datoen som er søkt, ble funnet eller ikke.

Trinn 5

De enumerateMatchesInString (_: alternativer: Område: usingBlock :) blir kalt. Vi bruker ikke noen av alternativene, og vi går forbi hele spekteret av teksten som søkte.

Trinn 6

Blokkobjektet, påkalt etter hvert kamp, ​​har tre parametere:

  • kampen (a NSTextCheckingResult)
  • flagg som representerer den nåværende tilstanden til matchingsprosessen (som vi ignorerer her)
  • en boolsk Stoppe variabel, som vi kan sette innenfor blokken for å gå ut tidlig

Vi bruker den boolske til å gå ut av blokken hvis vi finner datoen vi ser siden vi ikke trenger å se nærmere. Koden som trekker ut komponentene til datoen, er ganske lik det forrige eksempelet.

Trinn 7

Vi kontrollerer om de ekstraherte komponentene fra den matchende substringen er lik komponentene på ønsket dato. Vær oppmerksom på at vi tvinger en cast til int, som vi er sikre på, mislykkes ikke fordi vi opprettet de tilsvarende oppfangningsgruppene for å bare matche sifre.

Trinn 8

Hvis en kamp er funnet, setter vi inn ikke funnet til ekte. Vi avslutter blokken ved å sette inn stop.memorytil ekte. Vi gjør dette fordi Stoppe er en pekeren-til-en-boolsk og måten Swift handler om "spiss-til" -minnet er via minneegenskapen.

Vær oppmerksom på at substringen "2015/10/10" i teksten tilsvarer Mulig dato (15, 10, 10), Derfor returnerer funksjonen ekte i det første tilfellet. Imidlertid stemmer ingen streng i teksten til Mulig dato (13, 1, 1), det vil si "2013-01-01" og det andre anropet til funksjonen returnerer falsk.

Konklusjon

Vi har tatt en rolig, men rimelig detaljert, se på hvordan vanlige uttrykk fungerer, men det er mye mer å lære om du er interessert, for eksempel se fremover og se bak påstander, bruk vanlige uttrykk til Unicode-strengene, i tillegg til å se på de ulike alternativene vi skummet over i Foundation API.

Selv om du bestemmer deg for ikke å dype noe dypere, forhåpentligvis har du samlet opp nok her for å kunne identifisere situasjoner der regexes kan komme til nytte, samt noen tips om hvordan du utformer regexes for å løse dine mønstersøkingsproblemer.