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:
- [^5]+
à oznacza jeden lub więcej
znaków różnych niż cyfra 5
- [a-z]{2,4}
à oznacza od 2 do 4 liter (małych)
- ^.+@.+..+$
à 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ć”.