14. Wyrażenia regularne

14. Wyrażenia regularne

Autor: Paweł Kruczkowski

Opublikowano: 12/26/2006, 12:00 AM

Liczba odsłon: 114350

Tematem niniejszego artykułu są wyrażenia regularne, bez których - z punktu widzenia programistów - definiowanie łańcuchów znaków często nie miałoby sensu. Według wikipedii wyrażenia regularne (ang. regular exxpressions) są bowiem wzorcem, który opisuje te łańcuchy. Mogą one opisywać zbiór pasujących łańcuchów, bądź wyszczególniać istotne części łańcucha. Innymi słowy, wyrażenia regularne są nakładane na łańcuchy znaków w celu znalezienia łańcucha pasującego do danego wyrażenia regularnego. W wyniku takiej operacji możemy otrzymać podłańcuch lub nowy łańcuch, który jest modyfikacją oryginalnego łańcucha (jak wiemy, łańcuchy znaków są niezmienne, dlatego nie mogą ulec zmianie w wyniku zastosowania operacji nakładania na nich wyrażeń regularnych).

Zanim przejdziemy do przykładów ilustrujących używanie wyrażeń regularnych w języku C#, zdefiniujemy sobie to pojęcie i nauczymy się podstawowych elementów, które opisują te wyrażenia.

Wyrażenie regularne składa się z dwóch typów znaków: literałów oraz metaznaków. Literał to znak jaki programista chce znaleźć w danym łańcuchu znaków (np. ‘a’, ‘z’, ‘7’ etc.), natomiast metaznak to specjalny symbol, który jest prawidłowo rozumiany przez analizator wyrażeń regularnych (np. ‘^’. ‘?’, ‘*’, ‘$’ etc.).

Aby zacząć je stosować w programach musimy poznać kilka elementów, które opisują wyrażenia:

Nazwa Działanie
‘.’ Dowolny znak oprócz znaku nowego wiersza
‘^’ Początek wiersza
‘$’ Koniec wiersza
‘*’ Zero lub więcej wystąpień danego zestawu znaków (wyrażeń)
‘?’ Zero lub jedno wystąpienie danego zestawu znaków
‘+’ Jeden lub więcej wystąpień danego zestawu znaków
‘[]’ Dowolny znak ze zbioru znajdującego się wewnątrz nawiasów kwadratowych. Przedziały znaków oznacza się: ‘-’ np. [a-c] oznacza wystąpienie liter a, b lub c
‘[^ ]’ Wszystkie znaki oprócz tych z zestawu znajdujących się wewnątrz nawiasów kwadratowych
‘|’ Lub
{x} x-razy wystąpień danego zestawu znaków (wyrażeń)
‘\d’ cyfra
‘\znak’ Oznacza ten znak np. ‘\?’ oznacza znak: ‘?’

Powyższa tabela prezentuje jedynie wybrane, podstawowe elementy z jakich możemy budować wyrażenia regularne.

Zanim przejdziemy dalej, prześledźmy kilka poniższych przykładów:

  1. [^5]+ à oznacza jeden lub więcej znaków różnych niż cyfra 5
  2. [a-z]{2,4} à oznacza od 2 do 4 liter (małych)
  3. ^.+@.+..+$ à sprawdzamy czy dany łańcuch jest emailem

Przykłady te prezentują tworzenie wyrażeń regularnych, które następnie możemy wykorzystywać w naszych programach. W jaki sposób? Bardzo łatwo, wystarczy użyć klasy Regex, która znajduje się w przestrzeni nazw: System.Text. RegularExpressions. Klasa ta udostępnia szereg metod statycznych, chociaż możliwe jest oczywiście stworzenie obiektu klasy Regex.

Napiszmy pierwszy przykład prezentujący sposób obsługi wyrażeń regularnych:

public class WyrRegularne
{
    static void Main()
    {
        //tworzymy obiekt klasy StringBuilder
        StringBuilder sb = new StringBuilder();
        //tworzymy lancuch
        string mojLancuch = "Wiola & Paweł, Rodzice: Sylwester; Urszula";
        //wyswietlamy nasz string
        System.Console.WriteLine("Mój napis to: {0}", mojLancuch);
        //tworzymy obiekt klasy Regex
        Regex regx = new Regex(" & |, |: |; ");
        //tworzymy odpowiednie podlancuchy
        foreach (string s in regx.Split(mojLancuch))
        {
            sb.Append(s + "\n");
        }
        //wyswietlamy uzyskane podlancuchy
        System.Console.Write("\n");
        System.Console.WriteLine(sb);
    }
}

Powyższy przykład prezentuje sposób obsługi wyrażeń regularnych poprzez stworzenie obiektu klasy Regex. Najpierw utworzyliśmy łańcuch mojLancuch, a następnie obiekt regx reprezentujący w programie wyrażenie regularne, które będzie służyło nam do przeszukiwania naszego łańcucha. Klasa Regex udostępnia nam przeciążone konstruktory. Jednym z nich jest konstruktor, który przyjmuje jeden parametr w postaci wyrażenia regularnego:

Regex regx = new Regex(" & |, |: |; ");

Następnie budujemy odpowiednie podłańcuchy poprzez wywołanie metody Split() klasy Regex. Metoda ta jest prawie identyczna do metody Split() klasy String i zwraca tablicę podłańcuchów powstałą w wyniku dopasowania wzorca zdefiniowanego przez obiekt regx.

Po skompilowaniu i uruchomieniu powyższego przykładu otrzymamy następujące wyniki:

Metoda Split() jest metodą przeciążoną. Najprostszą jej wersją jest metoda, jaką użyliśmy w powyższym przykładzie. Istnieje także statyczna metoda Split(), która przyjmuje 2 argumenty: przeszukiwany łańcuch znaków oraz wzorzec.

Poniższy przykład jest identyczny jak poprzedni, ale użyjemy w nim statycznej metody Split() przyjmąjącej wspomniane dwa argumenty:

public class Lancuchy
{
    static void Main()
    {
        //tworzymy obiekt klasy StringBuilder
        StringBuilder sb = new StringBuilder();
        //tworzymy lancuch
        string mojLancuch = "Wiola & Paweł, Rodzice: Sylwester; Urszula";
        //wyswietlamy nasz string
        System.Console.WriteLine("Mój napis to: {0}", mojLancuch);
        //stosowanie statycznej metody Split()
        foreach (string s in Regex.Split(mojLancuch, " & |, |: |; "))
        {
            sb.Append(s + "\n");
        }
        //wyswietlamy uzyskane podlancuchy
        System.Console.Write("\n");
        System.Console.WriteLine(sb);
    }
}

Po uruchomieniu powyższego przykładu otrzymamy identyczne wyniki jak poprzednio. Jedyną różnicą w kodzie w porównaniu z poprzednim programem jest to, że nie tworzymy tutaj egzemplarza klasy Regex, a wywołujemy statyczną metodę Split() przyjmującą jako pierwszy argument – przeszukiwany łańcuch znaków, a jako drugi – wyrażenie regularne określające szukany wzorzec.

W przestrzeni System.Text.RegularExpressions znajduje się bardzo ciekawy typ zwracanej kolekcji. MatchCollection, bo o nim mowa może zawierać lub nie zawierać obiekty typu Match. Po co nam te klasy? Przede wszystkim po to, aby móc powtarzać przeszukiwanie łańcuchów a wyniki wrzucać do kolekcji. Poniższy przykład prezentuje sposób definiowania i stosowania kolekcji MatchCollection oraz obiektów typu Match:

public class Lancuchy
{
    static void Main()
    {
        //tworzymy lancuch znakow
        string lancuch = "Visual Studio 2005 Express Edition i C# ";
        //tworzymy wyrazenie regularne
        Regex rgx = new Regex(@"(\d+)\s");
        //tworzymy kolekcje znalezionych pasujacych znakow
        MatchCollection mch = rgx.Matches(lancuch);
 
        foreach (Match m in mch)
        {
            Console.WriteLine("Łańcuch: {0} o długości: {1}", m.ToString(), m.Length);
        }
    }
}

W powyższym przykładzie stworzyliśmy wyrażenie regularne rgx określające szukany wzorzec. Wzrocem tym jest jedna bądź kilka cyferek (\d+), po których występuje znak odstępu (\s).

I tak stworzony wzorzec wyszukujemy w naszym łańcuchu. Jedynym pasującym jest łańcuch: ‘2005’, który jest wrzucany do kolekcji MatchColecction. Po kolekcji tej bardzo łatwo można „przejść” za pomocą pętli foreach, ponieważ kolekcja ta przechowuje obiekty Match. Obiekty te posiadają właściwość Length, dzięki którym możemy uzyskac długość danego obiektu Match, a co za tym się kryje pożądanego łańcucha znaków.

Po uruchomieniu powyższego przykładu otrzymamy następujące wyniki:

Używając wyrażeń regularnych programiści bardzo często wykorzystują klasę Group, dzięki której możliwe jest grupowanie znalezionych łańcuchów. Wyrażenie grupujące nadaje po prostu nazwę grupie i dodaje do niej każdy podłańcuch pasujący do wzorca określonego przez wyrażenie regularne.

Poznana wyżej klasa Match dziedziczy po klasie Group i zawiera kolekcję Groups, w której przechowywane są wszystkie utworzone grupy. Prześledźmy poniższy przykład:

public class Lancuchy
{
    static void Main()
    {
        //tworzymy lancuch
        string lancuch = "Wiola wiola@wiola.pl";
        //tworzymy wyrazenia regularne reprezentujace okreslone grupy
        Regex rgx = new Regex(@"(?<imie>(\S)+)\s" + @"(?<mail>.+@.+..+$)");
        //tworzymy kolekcje z pasujacymi lancuchami
        MatchCollection mch = rgx.Matches(lancuch);
        //przechodzimy po kolekcji
        foreach (Match m in mch)
        {
            Console.WriteLine("Imię: {0}", m.Groups["imie"]);
            Console.WriteLine("E-mail: {0}", m.Groups["mail"]);
        }
    }
}

W powyższym przykładzie utworzyliśmy wyrażenie regularne, które zawiera w sobie pewne grupy (grupę o nazwie: imie oraz grupę: mail). Wszystko co się znajduje między nawiasami okrągłymi to anonimowa grupa. Nazwę grupie nadaje łańcuch: ?<imie> (w przypadku grupy imie), oraz łańcuch ?<mail> (w przypadku grupy mail).

Wyświetlenie odpowiednich grup jest bardzo łatwe. Używamy kolekcji Groups (obiektu typu Match) podając w nawiasach kwadratowych odpowiednią nazwę wcześniej utworzonej grupy. Po skompilowaniu i uruchomienu powyższego programiku otrzymamy następujące wyniki:

Niniejszy artykuł wprowadził nas w świat wyrażeń regularnych w języku C# 2.0. Oczywiście to tylko podstawy, które miejmy nadzieję są dla nas wszystkich zrozumiałe i mogą w przyszłości przyczynić się do pogłębiania wiedzy w tym kierunku.

Za tydzień odejdziemy od tematyki związanej z łańcuchami znaków i nauczymy się obsługiwać wyjątki, bez których żaden programista nie może „żyć”.