Swift och Regular Expressions Swift

I den första handledningen av denna serie undersökte vi grunderna för reguljära uttryck, inklusive syntaxen för att skriva regelbundna uttryck. I den här handledningen tillämpar vi vad vi hittills har lärt oss för att utnyttja reguljära uttryck i Swift.

1. Vanliga uttryck i Swift

Öppna Xcode, skapa en ny Lekplats, namnge den RegExTut, och ställa in Plattform till OS X. Valet av plattformen, iOS eller OS X, spelar ingen roll i förhållande till det API som vi ska använda.

Innan vi börjar finns det en annan sak du behöver veta om. I Swift måste du använda två backslashes, \\, för varje backslash du använder i ett reguljärt uttryck. Orsaken har att göra med Swift som har C-stil strängbokstav. Bakslaget behandlas som ett teckenflöde utöver dess roll i stränginterpolering i Swift. Med andra ord måste du undkomma flykt karaktären. Om det låter konstigt, oroa dig inte om det. Kom bara ihåg att använda två backslashes istället för en.

I det första, något konstruerade exemplet föreställer vi oss att vi rysar genom en sträng som letar efter en mycket specifik typ av e-postadress. E-postadressen uppfyller följande kriterier:

  • första bokstaven är första bokstaven i personens namn
  • följt av en period
  • följt av personens efternamn
  • följt av @ -symbolen
  • följt av ett namn som representerar ett universitet i Storbritannien
  • följd av .ac.uk, domänen för akademiska institutioner i Storbritannien

Lägg till följande kod på lekplatsen och låt oss gå igenom det här kodavsnittet steg för steg.

importera kakao // (1): låt pat = "\\ b ([az]) \\. ([az] 2 @ ([az] +) \\. ac \\. uk \\ b "// (2): låt testStr =" [email protected], [email protected] [email protected], [email protected], [email protected]. uk "// (3): låt regex = försök! NSRegularExpression (mönster: pat, alternativ: []) // (4): låt matchar = regex.matchesInString (testStr, alternativ: [], intervall: NSRange (plats: 0, längd: testStr.characters.count))

Steg 1

Vi definierar ett mönster. Observera de dubbelsvängda backslashesna. I (normal) regex-representation, som den som används på RegExr-webbplatsen, skulle detta vara ([A-z]) \. ([A-z] 2,) @ ([a-z] ^) \. Ac \ .uk. Notera också användningen av parenteser. De används för att definiera fångstgrupper med vilka vi kan extrahera substrängarna matchade med den delen av det reguljära uttrycket.

Du borde kunna ta reda på att den första fångstgruppen fångar första bokstaven i användarens namn, den andra ena deras efternamn och den tredje namnet på universitetet. Notera också användningen av backslash för att undvika periodtecknet för att representera dess bokstavliga betydelse. Alternativt kan vi uttrycka det i ett teckenuppsättning av sig själv ([.]). I så fall skulle vi inte behöva fly den.

Steg 2

Detta är strängen i vilken vi söker efter mönstret.

Steg 3

Vi skapar en NSRegularExpression objekt, passerar i mönstret utan alternativ. I listan över alternativ kan du ange NSRegularExpressionOption konstanter, såsom:

  • Fallet okänslig: Det här alternativet anger att matchningen är okänslig för fallet.
  • IgnoreMetacharacters: Använd det här alternativet om du vill utföra en bokstavlig match, vilket betyder att metateklarna inte har någon särskild betydelse och matchar sig som vanliga tecken.
  • AnchorMatchLines: Använd det här alternativet om du vill ha ^ och $ ankare för att matcha början och slutet av raderna (separerade av rader) i en enda sträng, snarare än början och slutet av hela strängen.

Eftersom initieraren kasta, använder vi Prova nyckelord. Om vi ​​skickar in ett ogiltigt regelbundet uttryck, kastas ett fel.

Steg 4

Vi söker efter matcher i teststrängen genom att åberopa matchesInString (_: alternativ: range :), passerar i ett intervall för att ange vilken del av strängen vi är intresserade av. Denna metod accepterar också en lista med alternativ. För att hålla det enkelt, passerar vi inte några alternativ i det här exemplet. Jag kommer att prata om alternativ i nästa exempel.

Matcherna returneras som en uppsättning av NSTextCheckingResult objekt. Vi kan extrahera matcherna, inklusive fångstgrupperna, enligt följande:

för match i matchningar för n i 0 ... 

Ovanstående kodning iterates genom varje NSTextCheckingResult objekt i arrayen. De numberOfRanges egendom för varje match i exemplet har ett värde av 4, en för hela substring matchad motsvarande en e-postadress (till exempel [email protected]) och de återstående tre motsvarar de tre fångstgrupperna inom matchen ("a", "khan" och "surrey "respektive).

De rangeAtIndex (_ :) Metoden returnerar intervallets intervall i strängen så att vi kan extrahera dem. Observera att istället för att använda rangeAtIndex (0), du kan också använda räckvidd egendom för hela matchen.

Klicka på Visa resultat knappen i resultatpanelen till höger. Detta visar oss "Surrey", värdet av testStr.substringWithRange (r) för den sista iterationen av slingan. Högerklicka på resultatfältet och välj Värdeshistoria för att visa en historia av värden.

Du kan ändra ovanstående kod för att göra något meningsfullt med matchningarna och / eller fångstgrupperna.

Det finns ett bekvämt sätt att utföra hitta och ersätta operationer, med hjälp av en mallsträng som har en särskild syntax för att representera fångstgrupper. Om vi ​​fortsätter med exemplet, anta att vi ville ersätta varje matchad e-postadress med en substring av formen "efternamn, inledande, universitet", kunde vi göra följande:

låt ersättStr = regex.stringByReplacingMatchesInString (testStr, alternativ: [], intervall: NSRange (plats: 0, längd: testStr.characters.count), medTemplate: "($ 2, $ 1, $ 3)")

Notera $ n syntax i mallen, som fungerar som en platshållare för texten i fångstgruppen n. Tänk på att $ 0 representerar hela matchen.

2. Ett mer avancerat exempel

De matchesInString (_: alternativ: range :) Metoden är en av flera bekvämlighetsmetoder som är beroende av enumerateMatchesInString (_: alternativ: range: usingBlock :), vilket är den mest flexibla och generella (och därför komplicerade) metoden i NSRegularExpression klass. Denna metod kallar ett block efter varje match, så att du kan utföra vilken åtgärd du vill ha.

Genom att passera i en eller flera matchande regler, använder du NSMatchingOptions konstanter, du kan se till att blocket åberopas vid andra tillfällen. För långvarig drift kan du ange att blocket åberopas periodiskt och avsluta operationen vid någon tidpunkt. Med ReportCompletion Alternativ, du anger att blocket ska åberopas vid slutförandet.

Blocken har en flaggparameter som rapporterar någon av dessa tillstånd så att du kan bestämma vilken åtgärd du ska ta. Liknande några andra uppräkningsmetoder i fundament ram kan blocket också avslutas efter eget gottfinnande. Om en lång löpande matchning inte lyckas eller om du har hittat tillräckligt många matchningar för att börja bearbeta.

I detta scenario ska vi söka igenom en del text för strängar som ser ut som datum och kontrollera om ett visst datum är närvarande. För att hålla exemplet hanterbart föreställer vi oss att datumsträngarna har följande struktur:

  • ett år med antingen två eller fyra siffror (till exempel 09 eller 2009)
  • bara från det nuvarande århundradet (mellan 2000 och 2099) så 1982 skulle avvisas och 16 tolkas automatiskt som 2016
  • följt av en separator
  • följt av ett tal mellan 1 och 12 som representerar månaden
  • följt av en separator
  • avslutas med ett tal mellan 1 och 31 som representerar dagen

Enciffriga månader och datum kan eventuellt vara vadderade med en ledande noll. Giltiga separatorer är ett streck, en period och ett framåt snedstreck. Bortsett från ovanstående krav kommer vi inte att verifiera huruvida ett datum är faktiskt giltigt. Till exempel är vi bra med datum som 2000-04-31 (april har bara 30 dagar) och 2009-02-29 (2009 är inte ett springår, vilket betyder att februari bara har 28 dagar) som inte är riktiga datum.

Lägg till följande kod på lekplatsen och låt oss gå igenom det här kodavsnittet steg för steg.

// (1): typalias PossibleDate = (år: Int, månad: Int, dag: Int) // (2): func dateSearch (text: String, _ date: PossibleDate) -> Bool // låt datePattern = "\\ b (?: 20)? (\\ d \\ d) [-. /] (0? [1-9] | 1 [0-2]) [-. /] 0-1] | [1-2] [0-9] | 0? [1-9]) \\ b "låt dateRegex = försök! NSRegularExpression (mönster: datePattern, options: []) // (4): var wasFound: Bool = false // (5): dateRegex.enumerateMatchesInString (text, alternativ: [], intervall: NSRange (plats: 0, längd: text.characters.count)) // (6): (matcha, stoppa) i var dateArr = [Int] () för n i 1 ... 3 let range = match! .rangeAtIndex (n) låt 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

Steg 1

Dagen vars existens vi söker efter kommer att vara i ett standardiserat format. Vi använder en namngiven tupel. Vi skickar bara ett tvåsiffrig heltal till år, det vill säga 16 betyder 2016.

Steg 2

Vår uppgift är att räkna upp genom matchningar som ser ut som datum, extrahera år, månad och dagskomponenter från dem och kontrollera om de matchar det datum vi passerade in. Vi skapar en funktion för att göra allt detta för oss. Funktionen returnerar Sann eller falsk beroende på om datumet hittades eller inte.

Steg 3

Datummönstret har några intressanta funktioner:

  • Notera fragmentet (:? 20)?. Om vi ​​bytte ut detta fragment med (20)?, Förhoppningsvis skulle du erkänna att det här innebar att vi har det bra med "20" (som representerar årtusendet) att vara närvarande i året eller inte. Parenteserna är nödvändiga för gruppering, men vi bryr oss inte om att bilda en fångstgrupp med detta par parentes och det är vad de ?: bit är för.
  • De möjliga separatorerna inuti teckenuppsättningen [-. /] behöver inte släppas för att representera sina bokstavliga själar. Du kan tänka på det så här. Bindestrecket, -, är i början så att den inte kan representera en räckvidd. Och det är inte meningsfullt för perioden, ., att representera alla tecken inom en teckenuppsättning eftersom det gör det lika bra utanför.
  • Vi använder kraftigt av den vertikala linjen för växling för att representera olika månads- och datumciffror.

Steg 4

Den booleska variabeln hittades inte kommer att returneras av funktionen, vilket anger om datumet söktes hittades eller ej.

Steg 5

De enumerateMatchesInString (_: alternativ: range: usingBlock :) kallas. Vi använder inte något av alternativen och vi passerar hela sortimentet av texten som söks.

Steg 6

Blockobjektet, som åberopas efter varje match, har tre parametrar:

  • matchen (a NSTextCheckingResult)
  • flaggor som representerar det aktuella tillståndet för matchningsprocessen (som vi ignorerar här)
  • en booleska sluta variabel, som vi kan ställa in i blocket för att avsluta tidigt

Vi använder den booleska för att lämna kvarteret om vi hittar datumet vi söker eftersom vi inte behöver titta längre. Koden som extraherar komponenterna i datumet är ganska lik det föregående exemplet.

Steg 7

Vi kontrollerar om de extraherade komponenterna från den matchade substringen motsvarar komponenterna på det önskade datumet. Observera att vi tvingar en cast till int, vilket vi är säkra på kommer inte att misslyckas eftersom vi skapade motsvarande fångstgrupper för att bara matcha siffror.

Steg 8

Om en match hittas ställer vi in hittades inte till Sann. Vi lämnar kvarteret genom att ställa in stop.memorytill Sann. Vi gör det här eftersom sluta är en pekare-till-en-boolean och hur Swift handlar om "spetsig" -minne är via minnesegenskapen.

Observera att substring "2015/10/10" i vår text motsvarar Möjligt datum (15, 10, 10), vilket är anledningen till att funktionen återvänder Sann i det första fallet. Ingen sträng i texten motsvarar emellertid Möjligt datum (13, 1, 1), det vill säga "2013-01-01" och det andra samtalet till funktionen returnerar falsk.

Slutsats

Vi har tagit ett lugnt men ändå ganska detaljerat, titta på hur vanliga uttryck fungerar, men det finns mycket mer att lära om du är intresserad, till exempel se framåt och Kolla bakom påståenden, tillämpa regelbundna uttryck på Unicode-strängar, förutom att titta på de olika alternativen vi skummade över i Foundation API.

Även om du bestämmer dig för att inte gräva någon djupare, hoppas du förhoppningsvis tillräckligt här för att kunna identifiera situationer där regexes kan komma till nytta, liksom några tips om hur man utformar regexes för att lösa dina problem med sökmönster..