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.

@jkornat

Wyrażenia regularne w Swift – czyli NSRegularExpression

Dodaj komentarz

Twój adres email nie zostanie opublikowany. Pola, których wypełnienie jest wymagane, są oznaczone symbolem *