Wyrażenia regularne – NSRegularExpression
8 Października 2017
Cześć!
Dzisiaj chciałbym napisać co nieco na temat obsługi wyrażeń regularnych w Swift 4. Dlaczego? Powiem szczerze ze zawsze temat wyrażeń regularnych sprawiał mi trudność. Umówmy się – regex nigdy nie był zbyt intuicyjnym standardem. Szczególnie początkujący, widząc te gonitwy szlaczków układających się w conajmniej wątpliwe estetycznie wzroce, mogą poczuć się odrobinę zagubieni. Ale spokojnie, nie taki 👺 straszny.
W ostatnim czasie prowadziłem mały projekt, o którym napiszę w którymś przyszłym wpisie na tym blogu. W skrócie – aplikacja w głównej mierze odpowiada za analizowanie i modyfikację stringów. Cóż może lepiej nadawać się do tego typu operacji, jak nie właśnie regex!
Ale dla porządku -> od początku.
Czym są wyrażenia regularne?
Wyrażenia regularne (w skrócie regex) są to zapisane w bardzo ustrukturyzowany sposób wzorce, które opisują łańcuch symboli. Słuzą one do określania zbiorów łańcuchów pasujących do zdefiniowanego wzorca.
Hm… brzmi groźnie. Ale w ostateczności nie jest aż takie straszne. Regex to inaczej wzór, na podstawie którego mozemy znajdować kawałki tekstu, które pasują do wcześniej zdefiniowanego wzorca. Dzięki wyrażeniu regularnemu, możemy pasujący kawałek większej całości tekstu wyróznić, zamienić, usunąć, whatever 👐🏼.
Wzorzec wyrażenia regularnego można tworzyć dla więszkości prostych gramatyk, używając do tego specjalnych symboli.
Nim przejdziemy do budowania wyrażeń regularnych w języku Swift, najpierw warto zapoznać się z zasadami budowania regex’a w ogóle.
Budowa wyrażeń regularnych
Regex budujemu poprzez ciąg poszczególych znaków i symboli, z których niektóre są jedynie częścią wzroca (np. ^[a]+$ -> szukamy litery a w wyrażeniu), inne zaś pełnią w ciągu jakąś funkcję (np . – oznacza dowolny znak). Zatem przyjżyjmy się znaczeniu najważniejszych znaków:
. $ ^ { [ ( | ) ] } * + ?
Są to tzw. znaki specjalne, którymi możemy zastępować występujące w tekście zależności, i/lub zatępować pewne kategorie znaków, np znaki liczbowe, litery alfabetu itp.
W całym tekście ważne jest również to, gdzie należy szukać danego wzroca. Przykładowo, jeżeli chcemy odszukać fragment występujący na początku wzroca, skorzystamy z symbolu ^, na końcu zaś użyjemy symbolu $
Aby odpowiedniu określić składnie, należy podać też liczności występujących znaków. Do tego również istenieje określona grupa symboli. Możemy zatem założyć, iż dany symbol ma wystąpić 0 lub więcej razy (*), jeden lub więcej razy (+), zero lub raz (?) lub podać dokładną ilość wystąpień ({n}). Po dokładne opisy znaczenia poszczególnych symboli odsywałm do fajnego miejsca 😅, gdzie można nie tylko dowiedzieć się o znaczeniu symboli, ale też potestować co nieco.
Przykłady
Poznaliśmy już podstawy tworzeni wyrażeń regularnych. Przed rozpoczęciem zabawy oczywiście zalecam Ci zapoznanie się z dokumentacją, jeżeli nie pracowałeś wcześniej z wyrażeniami regularnymi.
Dla wizualizacji pokażę Ci parę przykładów regex’a. Zacznijmy od czegoś łatwego:
Sprawdź poprawność kodu pocztowego.
Ok, rozłóżmy to na części.
- przykład kodu pocztowego: 01-234
- kod składa się z dwóch cyfr, znaku – oraz kolejnych trzech cyfr.
Mamy założenia. Najpierw dodajemy początek i koniec naszego wzorca:
^$
Następnie sprawdzamy pierwsze dwie cyfry:
^d{2}$
potem średnik i kolejne trzy cyfry.
^d{2}-d{3}$
I już! It was easy! ✅ Teraż coś trudniejszego, np. adres www:
^www.[-w]+(.[-w]+)+$
To już było trochę trudniejsze, ale na spokojnie, wyjaśniam -> string musi rozpoczynać się od www, to zrozumiałe. Potem mamy znak “”, który w wyrażeniu regularnym oznacza tzw. ucieczkę. To znaczy, że likwiduje znaczenie symboli. Zatem “.” likwiduje znaczenie “.” jako dowolnego znaku. “.” ocznacza dosłownie kropkę. Tutaj bardzo ważna uwaga! W Swift 4 symbol również oznacza ucieczkę. Zatem jeśli chcemy we wzorcu umieścić znak , to z kolei jego poprzedzić musimy kolejnym znakiem . Koniec uwagi. Ok, idąc dalej, mamy
[-w]+
– co oznacza zbiór dowolnych znaków typu cyfra, litera, podkreślnik oraz znak – dodajacy średnik do możliwych znaków( średnik nie jest częścią wyrażenia w
). następnie mamy grupowanie drugiej części wyrażenia w nawiasy, a w nich powonie kropkę oraz jeden lub więcej znaków spełniających warunek
[-w]+
Wyrażenie w nawiasie również kończy się symbolem +, bo przecież takich cząstek może wystąpić więcej, np w adresie: www.cosfajnego.swiftly.pl <- Nic tam nie ma…. narazie 😝 .
Jeżeli wcześniej nie miałeś styczności z regex lub dopiero się uczysz, warto zaglądać do dokumentacji i przede wszystkim pokonstruować trochę trudniejsze wzorce, żeby dojść do wprawy. Trening, trening i jeszcze raz… regex 🏋️
NSREgularExpression – Regex w Swift
Tyle teorii, czas na praktykę. Tworzenie wyrażeń regularnych w Swift odbywa się za pomocą klasy
NSREgularExpression
Na początku musimy mieć źródło, które będziemy przeczesywać w poszukiwaniu fragmentów pasujących do naszego wzorca. Załóżmy zatem, że jako programiści, chcemy w kodzie źródłowym wyszukać wszystkie metody. Na początek tworzymy nasze źródło:
// Sprawdź, czy w kodzie są jakieś metody
var codeSource = "Klasa.medota() n obiekt.funkcja{ (obiekt) in return nill }"
Widzimy, że w źródle są dwie metody, o nazwach metoda i funkcja. Jak wyróżnić metody w kodzie Swift? Na początek założenia:
Metoda:
- zaczyna się kropką;
- następuję po nazwie klasy bądź obiektu;
- może kończyć się spacją, lub (, ale może także kończyć się ) (np. kiedy jest wywołana wewnątrz konstruktora, itp.), {, }, [, ].
Ok, teraz czas ułożyć odpowiedni wzorzec. Wzorzec ten w Swift zapisujemy po po prostu jako zmienna typu String:
// Wzorzec
let methodRegexPattern = "\.([A-Za-z0-9]*[\s+]?([\(//)\{]))"
Następnie tworzymy wyrażenie regularne i dopasowujemy je do wzorca:
// Wyrażenie regularne
let methodRegex = try! NSRegularExpression(pattern: methodRegexPattern, options: .caseInsensitive)
// Dopasowanie napisu do wzorca
let matches = methodRegex.matches(in: codeSource, range: NSMakeRange(0, codeSource.utf16.count))
Ok. Ostatnia rzecz, jaka została, to wyciągnięcie wszystkich metod z naszego kodu źródłowego:
let methods = matches.map { result -closedTag String in
let methodRange = result.range(at: 1)
let start = String.Index.init(encodedOffset: methodRange.location)
let end = String.Index.init(encodedOffset: methodRange.location + methodRange.length - 1)
return String(codeSource[start..❬end])
}
// Pokaż metody
print(methods) // "["metoda", "funkcja"]"
I już! Work like a charm!
Co dalej?
No tak, znaleźliśmy wszytskie wyrażenia pasujące do wzorca, i co dalej? Możemy np. zastąpić wszystkie wystąpienia danego ciągu w inny. Załóżmy, że chcemy otoczyć naszą metodę w znacznik html
span
i dodać metodę CSS “method”, która pokoloruje nam metodę na dany kolor. Użyjemy do tego metody replacingOccurrences która zastąpi na wszystkie wystąpienia danego stringa. Zatem:
let res = String(codeSource[start..❬end])
codeSource = codeSource.replacingOccurrences(of: res, with: "(res)")
Podsumowanie
Uff, jest już koniec 🏁. Krótko podsumowaując – to nie jest jeszcze wszystko 😁. Wyrażenia regularne w Swift posiadają parę innych ciekawych metod, które mogą być użyteczne w zależności od tego, co potrzebujesz zrobić. Sam regex choć przy pierwszym kontakcie wydaje się skomplikowany, to w paru przypadkach jego użycie może okazać się nieuniknione, a przynajmniej ułatwiające życie.
Aby zgłębić ten temat, jak zawsze odsyłam do dokumentacji dotyczącej wyrażeń regularnych, jak i do dokmentacji dotyczącej NSRegularExpression w Swift 4.
Na dzisiaj wystarczy, jeżeli doczytałaś/doczytałeś aż tutaj, to proszę zostaw po sobie jakiś znak w postaci komentarza, albo chociaż daj znać na twitterze @jkornat czy się podobało, co warto zmienić, a może o czym chciałabyś/chciałbyś przeczytać w następnym wpisie. Dziękuję 🙏 i do… przeczytania (?) następnym razem.