Pierwsze spojrzenie
Niniejszy artykuł zacznę od problemu:
Mianowicie zamierzasz napisać program który będzie wyszukiwał w pewnym pliku kodów pocztowych, a resztę odrzucał. I teraz myślisz i myślisz, i myślisz... Cholera jasna, przecież ktoś już musiał wymyślić rozwiązanie! - nagle krzykniesz. I tutaj właśnie z pomocą przychodzą wyrażenia regularne. Co? Nie zaprzątaj mi głowy takimi bzdurami! - pomyślisz i już będziesz chciał zamykac tę kartę w przeglądarce. Zaczekaj.
Wyrażenie regularne to taki wzorzec do którego da się dopasować tylko pewien tekst.
|
Przykładem takiego wzorca będzie na przykład "\w* to \w*"
Zaczniesz się drapać po głowie, aż w końcu powiesz: "Nic z tego nie rozumiem!". Całe szczęście, bowiem zamierzam cię oświecić:
Do poniższego wzorca można na przykład dopasować: "janek to głupek", "kuba to cymbał", "irena to mądrala", ale nie "janek nowak to inteligent". Bynajmniej nie z powodu pozytywnego znaczenia. Po prostu "nowak" nie pasuje do wzorca "to"
Piszemy program
W tym przykładzie posłużę się wzorcem przedstawionym na początku. Najpierw kodzik, potem wyjaśnienia. W całym artykule w programach będzie używana biblioteka boost::regex, która moim zdaniem jest najlepsza. Istnieją inne implementacje wyrażeń regularnych, lecz nie będziemy sie w to zagłębiać.
#include <iostream>
#include <string>
#include <regex>
using namespace std;
int main()
{
string tekst;
int line = 0;
regex wzorzec( "\\w* to \\w*" );
cout << "Witaj!\n\007";
cout << "Pisz. Aby skończyć wćiśnij [Ctrl] + [z] w nowym wierszu\n\n";
while( getline( cin, tekst ) )
{
smatch wynik;
++line;
if( regex_search( tekst, wynik, wzorzec ) )
cout << "Linia " << line << " : " << wynik[ 0 ] << '\n';
cin >> tekst;
}
system( "pause" );
return 0;
}
Wyjaśnienia
#include <iostream>
#include <conio.h>
#include <string>
#include <regex>
using namespace std;
Załączamy potrzebne nagłówki. W tym artykule używamy implementacji boost, która wchodzi w standard C++0x. Jeśli mamy starszy kompilator,
musimy sami ją zainstalować.
int main()
{
string tekst;
int line = 0;
regex wzorzec( "\\w* to \\w*" );
Deklarujemy zmienne. Tworzymy wzorzec, którego klasa ma taka samą nazwę jak jego biblioteka. W konstruktorze podajemy argument
typu string. A czemu te podwójne ukośniki zamiast pojedyńczych? Ano temu, że w C++ znaki które poprzedza \ są zarezerwowane, więc piszemy ukośnik podwójny.
cout << "Witaj!\n\007";
cout << "Pisz. Aby skończyć wćiśnij [Ctrl] + [z] w nowym wierszu\n\n";
Powitania i informacje.
while( getline( cin, tekst ) )
{
smatch wynik;
++line;
if( regex_search( tekst, wynik, wzorzec ) )
cout << "Linia " << line << " : " << wynik[ 0 ] << '\n';
}
Oto rdzeń programu, czyli pętla while.
Najpierw pobieramy wiersz tekstu. Teraz ważny moment. Tworzymy zmienną typu smatch, która będzie przechowywała wyniki.
Następnie zwiększamy licznik liń i sprawdzamy czy coś znaleźliśmy za pomocą funkcji regex_search(). Jest dosyć
skomplikowana, więc podam prototyp:
bool regex_search( string &, smatch &, regex & );
Wracając, jeśli wynik jest pozytywny, to wypisujemy na ekranie linię i co znaleziono.
Zauważ, że nie piszemy
tylko
Narazie zawsze dawaj 0, później to się zmieni.
system( "pause" );
return 0;
}
I kończymy program.
To jest podejście podstawowe, teraz zajmiemy się bardziej zaawansowanymi wzorcami.
Więcej o wzorcach
Znaki specjalne
Oto zbiór znaków specjalnych, które można stosowac we wzorcach:
(wyr = wyrażenie)
Przykład
np. "(.+) | (ads){2}" Wzorec ten oznacza "zero lub więcej znaków, albo ciąg 'adsads' "
Rodzaje znaków
Przykład
np. "\l\l\d" czyli "dwie małe litery, po których następuje cyfra dziesiętna"
Powtórzenia
Przykład
np. "0x*", co znaczy "cyfra '0', po której następuje jedna lub więcej liter 'x' "
Grupowanie
Aby utworzyć grupę, należy użyć operatora (), np. "(fre)|(gul)" - "ciąg 'fre' lub ciąg 'gul' "
Gdyby usunąć nawiasy znaczyło by to "ciąg 'fr' i litera 'e' lub 'g', po czym jest ciąg 'ul' "
Alternatywa
Znak | określa alternatywę. Np. "(FW:|Re:)?(.*)" co oznacza "opcjonalny ciąg 'FW' lub 'Re', po którym następuje 0 lub więcej znaków"
Ten wzorzec pasuje do: "FW: Siema", "Co u ciebie?", "Re: U mnie wszystko gra", ale nie "Re o co ci chodzi?"
Nie dozwolona jest pusta alternatywa,np. "(|uno)". Można natomiast określic kilka opcji: "(jeden|dwa|trzy|cztery)" czyli "ciąg 'jeden' lub ciąg 'dwa'
lub ciąg 'trzy', lub ciąg 'cztery' "
Zbiory i przedziały
Istnieją wbudowane zbiory, czyli \w,\u,\d itd., ale można stworzyć też własne:
Użyliśmy znaku ^, który oznacza mniej więcej to samo, co operator ! w C++
Nie można jednak napisać "[c-a]", a dlaczego, to chyba sam się domyślisz.
Klasy znakowe
Znaki sterujące to tak naprawdę skróty od klas znakowych.
Jeśli chcemy użyć jakiejś klasy piszemy "[[:nazwa_klasy:]]"
Można także używac operatora ^, np. "[[:^alpha:]]", co oznacza "dowolny znak, który nie jest w alfabecie"
UWAGA: Polskie znaki (ę,ą,ś,ń,ć,ź,ż) nie są w alfabecie.
Błędy
Co się stanie, gdy spróbujemy utworzyć niepoprawny wzorzec, np. "[c-a]", "(|bum)" ?
Otóż, konstruktor klasy regex zgłasza wyjątek typu bad_expression. Takie błędy pojawiają się najczęściej, gdy wzorzec jest pobierany od użytkownika.
Podwzorce
Dawno, dawno temu powiedzieliśmy, żeby wydrukować wynik poszukiwań piszemy
Ale czemu akurat 0 ? Otóż musze ci najpierw wytłumaczyć co to są podwzorce.
Podwzorzec to każde wyrażenie, które znajduje się w nawiasach. |
np. we wzorcu "\w*( )?\d*" jest tylko jeden podwzorzec, "( )"
Co to ma wspólnego z tym 0 ? Ma, bowiem jeśli piszemy wynik[0], to mamy na myśli cały wzorzec, a gdy piszemy wynik[1], mamy na myśli pierwszy jego
podwzorzec. Tak też wynik[2], wynik[3] itd. oznaczają kolejne podwzorce we wzorcu.
A co gdy nawiasy są zagnieżdżone, np. "((\w\w\d )|(\d\d\w ))*" ? A więc tak, wynik[1] reprezentuje "((\w\w\d )|(\d\d\w ))", wynik[2] - "(\w\w\d )", a wynik[3]
"(\d\d\w )"
Ale jak sprawdźić, ile jest podwzorców? Dzięki temu, że typ smatch to w sumie wektor, można zastosować metodę size(), która zwraca liczbę elementów,
czyli w tym przypadku podwzorców + 1 (wynik[0] też się liczy). Np. we wzorcu "\w* (\d*)?", wynik.size() == 2.
Składamy wszystko w całość
Dość teorii, teraz pokażemy przykładowy porgram:
#include <iostream>
#include <string>
#include <fstream>
#include <regex>
#include <sstream>
#include <conio.h>
using namespace std;
void exit( void )
{
getch();
exit( 1 );
}
int main()
{
string swzorzec;
regex wzorzec;
string in;
ifstream inf;
cout << "Witam\n";
cout << "Podaj wzorzec:";
getline( cin, swzorzec );
try
{
wzorzec = swzorzec;
cout << "Wzorzec: " << swzorzec << endl;
}
catch(...)
{
cerr << endl << swzorzec << " nie jest poprawnym wzorcem";
exit();
}
cout << "Podaj plik wejsciowy:";
cin >> in;
inf.open( in );
if( !inf )
{
cerr << "Zly plik";
exit();
}
int lineo = 0;
string line;
while( getline( inf, line ) )
{
++lineo;
smatch wynik;
if( regex_search( line, wynik, wzorzec ) )
{
cout << "Wiersz " << lineo << " : " << line;
for( int i; i < wynik.size(); ++i )
cout << "\tpasuje.[" << i << "]: " << wynik[ i ] << endl;
}
else
cout << "nie pasuje.\n";
}
return 0;
}
I to by było na tyle.