Expressions rapides et régulières rapide

Dans le premier tutoriel de cette série, nous avons exploré les bases des expressions régulières, y compris la syntaxe pour écrire des expressions régulières. Dans ce tutoriel, nous appliquons ce que nous avons appris jusqu’à présent pour tirer parti des expressions régulières dans Swift..

1. Expressions régulières en Swift

Ouvrez Xcode, créez un nouveau terrain de jeu, nommez-le RegExTut, Et mettre Plate-forme à OS X. Le choix de la plate-forme, iOS ou OS X, ne fait aucune différence en ce qui concerne l'API que nous allons utiliser.

Avant de commencer, il y a une autre chose que vous devez savoir. Dans Swift, vous devez utiliser deux barres obliques inverses, \\, pour chaque barre oblique inverse que vous utilisez dans une expression régulière. La raison en est que Swift a des littéraux de chaîne de style C. La barre oblique inverse est traitée comme un caractère d'échappement en plus de son rôle dans l'interpolation de chaîne dans Swift. En d'autres termes, vous devez échapper au caractère d'échappement. Si cela semble étrange, ne vous inquiétez pas. N'oubliez pas d'utiliser deux barres obliques inverses au lieu d'une..

Dans le premier exemple, quelque peu artificiel, nous imaginons fouiller dans une chaîne à la recherche d'un type d'adresse électronique très spécifique. L'adresse email répond aux critères suivants:

  • la première lettre est la première lettre du nom de la personne
  • suivi d'une période
  • suivi du nom de famille de la personne
  • suivi du symbole @
  • suivi d'un nom, représentant une université du Royaume-Uni
  • suivi par .ac.uk, le domaine des institutions académiques au Royaume-Uni

Ajoutez le code suivant au terrain de jeu et parcourons pas à pas cet extrait de code.

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): laissez regex = essayer! NSRegularExpression (modèle: pat, options: []) // (4): allumettes = regex.matchesInString (testStr, options: [], plage: NSRange (emplacement: 0, longueur: testStr.characters.count))

Étape 1

Nous définissons un motif. Notez les barres obliques inverses doublement échappées. Dans la représentation regex (normale), telle que celle utilisée sur le site Web RegExr, ce serait ([a-z]) \. ([a-z] 2,) @ ([a-z] +) \. ac \ .uk. Notez également l'utilisation de parenthèses. Ils sont utilisés pour définir des groupes de capture avec lesquels nous pouvons extraire les sous-chaînes correspondant à cette partie de l'expression régulière.

Vous devriez pouvoir vous assurer que le premier groupe de capture capture la première lettre du nom de l'utilisateur, le second son nom de famille et le troisième le nom de l'université. Notez également l'utilisation de la barre oblique inverse pour échapper au caractère point afin de représenter son sens littéral. Alternativement, nous pourrions le mettre dans un jeu de caractères seul ([.]). Dans ce cas, nous n'aurions pas besoin d'y échapper.

Étape 2

C'est la chaîne dans laquelle nous recherchons le motif.

Étape 3

Nous créons un NSRegularExpression objet, en passant dans le modèle sans options. Dans la liste des options, vous pouvez spécifier NSRegularExpressionOption des constantes, telles que:

  • CaseInsensitive: Cette option spécifie que la correspondance est sensible à la casse.
  • Ignorer les métacaractères: Utilisez cette option si vous souhaitez effectuer une correspondance littérale, ce qui signifie que les métacaractères n'ont pas de signification particulière et correspondent à des caractères ordinaires..
  • AnchorMatchLines: Utilisez cette option si vous voulez que le ^ et $ des ancres correspondant au début et à la fin des lignes (séparées par des sauts de ligne) dans une chaîne unique, plutôt que le début et la fin de la chaîne entière.

Parce que l'initialiseur est lancé, nous utilisons le essayer mot-clé. Si nous transmettons une expression régulière non valide, par exemple, une erreur est renvoyée..

Étape 4

Nous recherchons des correspondances dans la chaîne de test en appelant matchesInString (_: options: range :), en passant une plage pour indiquer la partie de la chaîne qui nous intéresse. Cette méthode accepte également une liste d'options. Pour simplifier les choses, nous ne transmettons aucune option dans cet exemple. Je parlerai des options dans l'exemple suivant.

Les correspondances sont renvoyées sous forme de tableau de NSTextCheckingResult objets. Nous pouvons extraire les correspondances, y compris les groupes de capture, comme suit:

pour match en match pour n en 0… 

L'extrait ci-dessus parcourt chaque élément NSTextCheckingResult objet dans le tableau. le numberOfRanges propriété pour chaque match dans l'exemple a une valeur de 4, un pour toute la sous-chaîne appariée correspondant à une adresse électronique (par exemple, [email protected]) et les trois restants correspondent aux trois groupes de capture dans la correspondance ("a", "khan" et "surrey " respectivement).

le rangeAtIndex (_ :) La méthode retourne la plage des sous-chaînes de la chaîne afin que nous puissions les extraire. Notez qu'au lieu d'utiliser rangeAtIndex (0), vous pouvez aussi utiliser le intervalle propriété pour le match entier.

Clique le Afficher le résultat bouton dans le panneau de résultats à droite. Cela nous montre "Surrey", la valeur de testStr.substringWithRange (r) pour la dernière itération de la boucle. Cliquez avec le bouton droit sur le champ de résultat et sélectionnez Histoire de la valeur montrer une histoire de valeurs.

Vous pouvez modifier le code ci-dessus pour faire quelque chose de significatif avec les correspondances et / ou les groupes de capture.

Il existe un moyen pratique d'effectuer des opérations de recherche et remplacement en utilisant une chaîne de modèle dotée d'une syntaxe spéciale pour représenter les groupes de capture. En reprenant l'exemple, supposons que nous voulions remplacer chaque adresse électronique correspondante par une sous-chaîne de la forme "nom, initial, université", nous pourrions procéder comme suit:

let replaceStr = regex.stringByReplacingMatchesInString (testStr, options: [], étendue: NSRange (emplacement: 0, longueur: testStr.characters.count), avecTemplate: "(2, 1, 3)")

Noter la $ n syntaxe dans le modèle, qui agit comme un espace réservé pour le texte du groupe de capture n. Garde en tête que 0 $ représente le match entier.

2. Un exemple plus avancé

le matchesInString (_: options: range :) La méthode est l’une des méthodes de commodité qui reposent sur enumerateMatchesInString (_: options: range: usingBlock :), qui est la méthode la plus souple et la plus générale (et donc la plus compliquée) de la NSRegularExpression classe. Cette méthode appelle un bloc après chaque match, vous permettant d’effectuer l’action de votre choix..

En passant une ou plusieurs règles de correspondance, en utilisant NSMatchingOptions constantes, vous pouvez vous assurer que le bloc est invoqué à d’autres occasions. Pour les opérations de longue durée, vous pouvez spécifier que le bloc est appelé périodiquement et mettre fin à l'opération à un moment donné. Avec le ReportCompletion option, vous spécifiez que le bloc doit être appelé à la fin.

Le bloc a un paramètre flags qui signale l’un de ces états afin que vous puissiez décider de l’action à prendre. Similaire à d'autres méthodes de dénombrement dans le Fondation cadre, le blocage peut également être résilié à votre discrétion. Par exemple, si une correspondance longue ne réussit pas ou si vous avez trouvé suffisamment de correspondances pour commencer le traitement.

Dans ce scénario, nous allons rechercher dans le texte des chaînes qui ressemblent à des dates et vérifier si une date particulière est présente. Pour que l'exemple soit gérable, nous imaginons que les chaînes de date ont la structure suivante:

  • une année avec deux ou quatre chiffres (par exemple, 09 ou 2009)
  • seulement à partir du siècle actuel (entre 2000 et 2099), donc 1982 serait rejetée et 16 serait automatiquement interprétée comme 2016
  • suivi d'un séparateur
  • suivi d'un nombre entre 1 et 12 représentant le mois
  • suivi d'un séparateur
  • se terminant par un nombre compris entre 1 et 31 représentant le jour

Les mois et les dates à un chiffre peuvent éventuellement être complétés par un zéro non significatif. Les séparateurs valides sont un tiret, un point et une barre oblique. Outre les exigences ci-dessus, nous ne vérifierons pas si une date est réellement valide. Par exemple, nous sommes bien avec des dates telles que 2000-04-31 (avril ne compte que 30 jours) et 2009-02-29 (2009 n'est pas une année bissextile, ce qui signifie que février ne compte que 28 jours) sans dates réelles.

Ajoutez le code suivant au terrain de jeu et parcourons pas à pas cet extrait de code.

// (1): typealias PossibleDate = (année: Int, mois: Int, jour: Int) // (2): func dateSearch (texte: 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 = try! NSRegularExpression (pattern: datePattern, options: []) // (4): var wasFound: Bool = false // (5): dateRegex.enumerateMatchesInString (texte, options: [], plage: NSRange (emplacement: 0, longueur: text.characters.count)) // (6): (match, _, stop) dans var dateArr = [Int] () pour n dans 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

Étape 1

La date dont nous vérifions l'existence va être dans un format normalisé. Nous utilisons un tuple nommé. Nous passons seulement un nombre entier à deux chiffres à l'année, c'est-à-dire 16 signifie 2016.

Étape 2

Notre tâche consiste à énumérer des correspondances qui ressemblent à des dates, à en extraire les composants année, mois et jour et à vérifier si elles correspondent à la date que nous avons passée. Nous allons créer une fonction pour faire tout cela à notre place. La fonction retourne vrai ou faux selon que la date a été trouvée ou non.

Étape 3

Le modèle de date a quelques caractéristiques intéressantes:

  • Notez le fragment (?: 20)?. Si nous remplaçons ce fragment par (20)?, J'espère que vous reconnaîtrez que cela signifie que cela signifie que nous sommes d'accord avec le "20" (représentant le millénaire) présent ou non dans l'année. Les parenthèses sont nécessaires pour le regroupement, mais nous ne nous soucions pas de former un groupe de capture avec cette paire de parenthèses et c’est ce que le ?: bit est pour.
  • Les séparateurs possibles à l'intérieur du jeu de caractères [-. /] n'ont pas besoin d'être échappés pour se représenter eux-mêmes. Vous pouvez penser à ça comme ça. Le tableau de bord, -, est au début, il ne peut donc pas représenter une plage. Et ça n'a pas de sens pour la période, ., pour représenter n'importe quel caractère à l'intérieur d'un jeu de caractères car il le fait aussi bien à l'extérieur.
  • Nous utilisons beaucoup la barre verticale d’alternance pour représenter les différentes possibilités de chiffres de mois et de date..

Étape 4

La variable booléenne pas trouvé sera retourné par la fonction, indiquant si la date recherchée a été trouvée ou non.

Étape 5

le enumerateMatchesInString (_: options: range: usingBlock :) est appelé. Nous n'utilisons aucune des options et nous transmettons toute la plage du texte recherché.

Étape 6

L'objet de bloc, appelé après chaque correspondance, a trois paramètres:

  • le match (un NSTextCheckingResult)
  • drapeaux représentant l'état actuel du processus de correspondance (que nous ignorons ici)
  • un booléen Arrêtez variable, que nous pouvons définir dans le bloc pour sortir plus tôt

Nous utilisons le booléen pour sortir du bloc si nous trouvons la date que nous cherchons car nous n'avons pas besoin de chercher plus loin. Le code qui extrait les composants de la date est assez similaire à l'exemple précédent.

Étape 7

Nous vérifions si les composants extraits de la sous-chaîne correspondante sont égaux aux composants de la date souhaitée. Notez que nous forçons un casting à Int, nous sommes sûrs que cela ne va pas échouer car nous avons créé les groupes de capture correspondants pour ne faire correspondre que les chiffres.

Étape 8

Si une correspondance est trouvée, nous définissons pas trouvé à vrai. Nous sortons du bloc en mettant arrêter la mémoireà vrai. Nous faisons cela parce que Arrêtez est un pointeur sur un booléen et la façon dont Swift traite la mémoire "pointée sur" via la propriété memory.

Notez que la sous-chaîne "2015/10/10" dans notre texte correspond à Date possible (15, 10, 10), c'est pourquoi la fonction retourne vrai dans le premier cas. Cependant, aucune chaîne dans le texte ne correspond à Date possible (13, 1, 1), c'est-à-dire "2013-01-01" et le deuxième appel de la fonction retourne faux.

Conclusion

Nous avons examiné de manière ludique, mais assez détaillée, le fonctionnement des expressions régulières, mais vous avez encore beaucoup à apprendre si vous êtes intéressé, par exemple regarder devant et regarde derrière des assertions, en appliquant des expressions régulières aux chaînes Unicode, en plus d'examiner les différentes options que nous avons survolées dans l'API Foundation.

Même si vous décidez de ne pas aller plus loin, espérons que vous en aurez suffisamment ramassé ici pour pouvoir identifier les situations dans lesquelles les expressions rationnelles pourraient être utiles, ainsi que quelques conseils sur la façon de les concevoir pour résoudre vos problèmes de recherche.