Snelle en reguliere expressies snel

In de eerste zelfstudie van deze serie hebben we de basisprincipes van reguliere expressies onderzocht, inclusief de syntaxis om reguliere expressies te schrijven. In deze zelfstudie passen we toe wat we tot nu toe hebben geleerd om gebruik te maken van reguliere expressies in Swift.

1. Reguliere expressies in Swift

Open Xcode, maak een nieuwe speelplaats, noem die RegExTut, En instellen Platform naar OS X. De keuze van het platform, iOS of OS X, maakt geen verschil met betrekking tot de API die we gaan gebruiken.

Voordat we beginnen, is er nog iets dat je moet weten. In Swift moet je twee backslashes gebruiken, \\, voor elke backslash die u gebruikt in een reguliere expressie. De reden heeft te maken met Swift met tekenreeksen in C-stijl. De backslash wordt verwerkt als een teken-escape naast de rol in tekenreeksinterpolatie in Swift. Met andere woorden, je moet aan het ontsnappingspersonage ontsnappen. Als dat raar klinkt, maak je er dan geen zorgen over. Houd er rekening mee dat u twee backslashes in plaats van één gebruikt.

In het eerste, enigszins gekunstelde voorbeeld, stellen we ons voor dat we door een string snuffelen op zoek naar een heel specifiek type e-mailadres. Het e-mailadres voldoet aan de volgende criteria:

  • de eerste letter is de eerste letter van de naam van de persoon
  • gevolgd door een punt
  • gevolgd door de achternaam van de persoon
  • gevolgd door het @ -symbool
  • gevolgd door een naam, die een universiteit in het Verenigd Koninkrijk vertegenwoordigt
  • gevolgd door .ac.uk, het domein voor academische instellingen in het Verenigd Koninkrijk

Voeg de volgende code toe aan de speeltuin en laten we stap voor stap door dit codefragment lopen.

import Cocoa // (1): let pat = "\\ b ([az]) \\. ([az] 2,) @ ([az] +) \\. ac \\. uk \\ b "// (2): laat testStr =" [email protected], [email protected] [email protected], [email protected], [email protected]. uk "// (3): laat regex = proberen! NSRegularExpression (patroon: pat, opties: []) // (4): let op = regex.matchesInString (testStr, opties: [], bereik: NSRange (locatie: 0, lengte: testStr.characters.count))

Stap 1

We definiëren een patroon. Let op de dubbel ontsnapte backslashes. In (normale) regex-weergave, zoals die wordt gebruikt op de RegExr-website, zou dit zijn ([A-z]) \. ([A-z] 2) @ ([a-z] +) \. Ac \ Uk. Let ook op het gebruik van haakjes. Ze worden gebruikt om vanggroepen te definiëren waarmee we de substrings kunnen extraheren die overeenkomen met dat deel van de reguliere expressie.

Je zou moeten kunnen vaststellen dat de eerste capture-groep de eerste letter van de gebruikersnaam vastlegt, de tweede hun achternaam en de derde de naam van de universiteit. Merk ook het gebruik van de backslash op om aan het periodeteken te ontsnappen om de letterlijke betekenis ervan weer te geven. Als alternatief kunnen we het zelf in een karakterset plaatsen ([.]). In dat geval zouden we er niet aan hoeven te ontsnappen.

Stap 2

Dit is de string waarin we naar het patroon zoeken.

Stap 3

We creëren een NSRegularExpression object, het patroon doorlaten zonder opties. In de lijst met opties kunt u opgeven NSRegularExpressionOption constanten, zoals:

  • CASEINSENSITIVE: Met deze optie geeft u aan dat de aanpassing niet hoofdlettergevoelig is.
  • IgnoreMetacharacters: Gebruik deze optie als u een letterlijke overeenkomst wilt gebruiken, wat betekent dat de metatekens geen speciale betekenis hebben en zichzelf als gewone tekens matchen.
  • AnchorMatchLines: Gebruik deze optie als u de ^ en $ ankers om het begin en het einde van regels (gescheiden door regeleinden) in één enkele tekenreeks te plaatsen, in plaats van het begin en het einde van de volledige tekenreeks.

Omdat de initializer wordt gegooid, gebruiken we de proberen trefwoord. Als we een ongeldige reguliere expressie doorgeven, bijvoorbeeld, wordt er een fout gegenereerd.

Stap 4

We zoeken naar overeenkomsten in de testreeks door aan te roepen matchesInString (_: options: range :), doorgeven in een bereik om aan te geven in welk deel van de reeks we geïnteresseerd zijn. Deze methode accepteert ook een lijst met opties. Om de zaken eenvoudig te houden, geven we in dit voorbeeld geen opties door. Ik zal het in het volgende voorbeeld over opties hebben.

De overeenkomsten worden geretourneerd als een reeks van NSTextCheckingResult voorwerpen. We kunnen de matches, inclusief de capture-groepen, als volgt uitpakken:

voor match in wedstrijden voor n in 0 ... 

Het bovenstaande fragment itereert door elk fragment NSTextCheckingResult object in de array. De numberOfRanges eigenschap voor elke overeenkomst in het voorbeeld heeft de waarde van 4, één voor de volledige overeenstemmende substring die overeenkomt met een e-mailadres (bijvoorbeeld [email protected]) en de resterende drie komen overeen met de drie capture-groepen binnen de match ("a", "khan" en "surrey" "respectievelijk).

De rangeAtIndex (_ :) methode retourneert het bereik van de substrings in de tekenreeks, zodat we deze kunnen extraheren. Merk op dat, in plaats van gebruiken rangeAtIndex (0), je zou ook de reeks eigendom voor de hele wedstrijd.

Klik op de Laat resultaat zien knop in het resultatenvenster aan de rechterkant. Dit toont ons "Surrey", de waarde van testStr.substringWithRange (r) voor de laatste iteratie van de lus. Klik met de rechtermuisknop op het resultaatveld en selecteer Waarde geschiedenis om een ​​geschiedenis van waarden te tonen.

Je kunt de bovenstaande code aanpassen om iets zinvols te doen met de matches en / of de capture-groepen.

Er is een handige manier om zoek-en-vervangbewerkingen uit te voeren, met behulp van een sjabloonreeks met een speciale syntaxis voor het weergeven van vanggroepen. Ga door met het voorbeeld, stel dat we elk gekoppeld e-mailadres willen vervangen door een subtekenreeks van het formulier 'achternaam, initiaal, universiteit', we kunnen het volgende doen:

laten vervangenStr = regex.stringByReplacingMatchesInString (testStr, options: [], bereik: NSRange (locatie: 0, lengte: testStr.characters.count), metTemplate: "($ 2, $ 1, $ 3)")

Merk op $ n syntaxis in de sjabloon, die fungeert als tijdelijke aanduiding voor de tekst van de capture-groep n. Onthoud dat $ 0 vertegenwoordigt de hele wedstrijd.

2. Een meer geavanceerd voorbeeld

De matchesInString (_: options: range :) methode is een van de vele gemaksmethoden die vertrouwen op enumerateMatchesInString (_: options: range: usingBlock :), wat de meest flexibele en algemene (en daarom gecompliceerde) methode is in de NSRegularExpression klasse. Deze methode roept na elke wedstrijd een blok aan, zodat je elke gewenste actie kunt uitvoeren.

Door een of meer overeenkomende regels door te geven, gebruikt u NSMatchingOptions constanten, je kunt ervoor zorgen dat het blok ook bij andere gelegenheden wordt aangeroepen. Voor langlopende bewerkingen kunt u opgeven dat het blok periodiek wordt aangeroepen en de bewerking op een bepaald moment beëindigt. Met de ReportCompletion optie, geeft u op dat het blok na voltooiing moet worden opgeroepen.

Het blok heeft een vlaggenparameter die een van deze toestanden rapporteert, zodat u kunt beslissen welke actie moet worden ondernomen. Vergelijkbaar met sommige andere opsommingsmethoden in de fundament kader, kan het blok naar eigen goeddunken ook worden beëindigd. Bijvoorbeeld als een langlopende wedstrijd niet lukt of als u genoeg overeenkomsten hebt gevonden om met de verwerking te beginnen.

In dit scenario gaan we wat tekst doorzoeken op tekenreeksen die op datums lijken en controleren of een bepaalde datum aanwezig is. Om het voorbeeld beheersbaar te houden, stellen we ons voor dat de datumreeksen de volgende structuur hebben:

  • een jaar met twee of vier cijfers (bijvoorbeeld 09 of 2009)
  • alleen uit de huidige eeuw (tussen 2000 en 2099), dus 1982 zou worden afgewezen en 16 zouden automatisch worden geïnterpreteerd als 2016
  • gevolgd door een scheidingsteken
  • gevolgd door een getal tussen 1 en 12 dat de maand voorstelt
  • gevolgd door een scheidingsteken
  • afsluitend met een getal tussen 1 en 31 dat de dag vertegenwoordigt

Maanden en datums van enkele cijfers kunnen mogelijk opgevuld worden met een voorloopnul. Geldige scheidingstekens zijn een liggend streepje, een punt en een schuine streep naar voren. Afgezien van de bovenstaande vereisten, zullen we niet verifiëren of een datum daadwerkelijk geldig is. We zijn bijvoorbeeld goed met datums zoals 2000-04-31 (april heeft slechts 30 dagen) en 2009-02-29 (2009 is geen schrikkeljaar, wat betekent dat februari slechts 28 dagen heeft), maar dat zijn geen echte datums.

Voeg de volgende code toe aan de speeltuin en laten we stap voor stap door dit codefragment lopen.

// (1): typealias PossibleDate = (jaar: Int, maand: Int, dag: Int) // (2): func dateSearch (text: 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 "laat dateRegex = probeer! NSRegularExpression (pattern: datePattern, options: []) // (4): var wasFound: Bool = false // (5): dateRegex.enumerateMatchesInString (text, options: [], bereik: NSRange (locatie: 0, lengte: text.characters.count)) // (6): (match, _, stop) in var dateArr = [Int] () voor n in 1 ... 3 let range = match! .rangeAtIndex (n) laat 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

Stap 1

De datum waarvan we het bestaan ​​controleren, zal in een gestandaardiseerd formaat zijn. We gebruiken een benoemd tuple. We geven alleen een geheel getal van twee cijfers door aan het jaar, dat wil zeggen, 16 betekent 2016.

Stap 2

Het is onze taak om op te tellen door wedstrijden die op datums lijken, de jaar-, maand- en dagcomponenten eruit te halen en te controleren of ze overeenkomen met de datum die we hebben doorgegeven. We zullen een functie maken om dit alles voor ons te doen. De functie retourneert waar of vals afhankelijk van of de datum is gevonden of niet.

Stap 3

Het datumpatroon heeft een aantal interessante kenmerken:

  • Let op het fragment (? 20)?. Als we dit fragment hebben vervangen door (20)?, hopelijk zou je erkennen dat dit betekent dat we het goed vinden dat de "20" (die het millennium vertegenwoordigt) aanwezig is in het jaar of niet. De haakjes zijn nodig voor groeperen, maar we geven er niet om een ​​vangstgroep te vormen met dit paar haakjes en dat is wat de ?: bit is voor.
  • De mogelijke scheidingstekens in de tekenset [-. /] hoeven niet te worden ontsnapt om hun letterlijke zelf weer te geven. Je kunt het zo denken. Het streepje, -, staat aan het begin, dus het kan geen bereik vertegenwoordigen. En het klopt niet voor de periode, ., om elk teken in een tekenset weer te geven, omdat het dat net zo goed doet buiten.
  • We maken intensief gebruik van de verticale balk voor afwisseling om de verschillende maand- en datumcijferopties weer te geven.

Stap 4

De Booleaanse variabele niet gevonden wordt door de functie geretourneerd om aan te geven of de gezochte datum is gevonden of niet.

Stap 5

De enumerateMatchesInString (_: options: range: usingBlock :) wordt gebeld. We gebruiken geen van de opties en we geven het hele bereik door van de tekst die wordt doorzocht.

Stap 6

Het blokobject, dat na elke match wordt aangeroepen, heeft drie parameters:

  • de wedstrijd (a NSTextCheckingResult)
  • vlaggen die de huidige status van het koppelingsproces weergeven (die we hier negeren)
  • een boolean hou op variabele, die we binnen het blok kunnen instellen om vroegtijdig te verlaten

We gebruiken de boolean om het blok te verlaten als we de datum vinden die we zoeken, omdat we niet verder hoeven te zoeken. De code die de componenten van de datum extraheert, lijkt veel op het vorige voorbeeld.

Stap 7

We controleren of de geëxtraheerde componenten van de gematchte substring gelijk zijn aan de componenten van de gewenste datum. Merk op dat we een cast forceren Int, waarvan we zeker weten dat ze niet zullen falen omdat we de corresponderende capture-groepen hebben gemaakt om alleen cijfers te evenaren.

Stap 8

Als er een overeenkomst wordt gevonden, stellen we in niet gevonden naar waar. We verlaten het blok door in te stellen stop.memorynaar waar. We doen dit omdat hou op is een pointer-to-a-boolean en de manier waarop Swift omgaat met het "punt-naar" geheugen is via de geheugeneigenschap.

Merk op dat de subtekenreeks "2015/10/10" in onze tekst overeenkomt met PossibleDate (15, 10, 10), daarom keert de functie terug waar in het eerste geval. Er komt echter geen tekenreeks in de tekst overeen PossibleDate (13, 1, 1), dat wil zeggen, "2013-01-01" en de tweede oproep naar de functie retourneert vals.

Conclusie

We hebben rustig en toch redelijk gedetailleerd bekeken hoe reguliere expressies werken, maar er is nog veel meer te leren als je geïnteresseerd bent, zoals vooruit kijken en achterom kijken beweringen, reguliere expressies toepassen op Unicode-reeksen, naast de verschillende opties bekijken die we in de Foundation-API hebben overgesmeerd.

Zelfs als je besluit om niet dieper te graven, hopelijk heb je hier genoeg opgepikt om situaties te identificeren waarin regexes van pas kunnen komen, evenals een paar tips voor het ontwerpen van regexes om je patroonzoekproblemen op te lossen..