Narzędzia użytkownika

Narzędzia witryny


regex

Różnice

Różnice między wybraną wersją a wersją aktualną.

Odnośnik do tego porównania

Both sides previous revision Previous revision
Next revision
Previous revision
regex [2008/04/16 02:37]
przemoc małe zbliżenie do finalnej wersji
regex [2008/04/17 03:03] (aktualna)
przemoc wersja finalna
Linia 1: Linia 1:
 ====== Wyrażenia regularne oczami programisty C++, czyli Boost.Regex ====== ====== Wyrażenia regularne oczami programisty C++, czyli Boost.Regex ======
  
-__Wersja robocza__+__Przemysław Pawełczyk G1ISI__
  
 W życiu każdego programisty przychodzi czas, w którym musi poznać wyrażenia regularne (z różnych przyczyn), a znając już je, używa ich chętnie i bez oporów, przynajmniej w skryptach shellowych (a de facto w programach typu [[wp>​AWK]],​ [[wp>​grep]] czy [[wp>​sed]]) i w interpretowanych językach jak [[wp>​PHP]],​ [[wp>​Perl]],​ [[wp>​Ruby_(programming_language)|Ruby]] oraz innych. W życiu każdego programisty przychodzi czas, w którym musi poznać wyrażenia regularne (z różnych przyczyn), a znając już je, używa ich chętnie i bez oporów, przynajmniej w skryptach shellowych (a de facto w programach typu [[wp>​AWK]],​ [[wp>​grep]] czy [[wp>​sed]]) i w interpretowanych językach jak [[wp>​PHP]],​ [[wp>​Perl]],​ [[wp>​Ruby_(programming_language)|Ruby]] oraz innych.
  
-Ogromne możliwości przetwarzania tekstu, jakie dają nam potocznie nazywane regexpy, można też wykorzystać w językach, w których nie wbudowano obsługi wyrażeń regularnych,​ czyli np. w języku C++. Nie mówię tu o własnoręcznej implementacji np. niedeterministycznego automatu skończonego Thompsona, choć to też byłoby pewne wyjście. Programiści,​ z natury jednostki leniwe, często lubią korzystać z gotowych komponentów do budowy własnego oprogramowania. Niestety, brak obsługi wyrażeń regularnych w standardowej bibliotece C++ w standardzie C++98 (ISO/IEC 14882:1998) czy ostatnim C++03 (ISO/IEC 14882:​2003). Sytuację poprawia dopiero [[http://​www.open-std.org/​jtc1/​sc22/​wg21/​docs/​papers/​2005/​n1836.pdf|TR1]],​ który będzie częścią standardu C++0x, a którego ostateczne ujęcie i ratyfikacja nastąpią w najbliższych latach, optymistycznie:​ już w przyszłym roku.+Ogromne możliwości przetwarzania tekstu, jakie dają nam potocznie nazywane regexpy, można też wykorzystać w językach, w których nie wbudowano obsługi wyrażeń regularnych,​ czyli np. w języku C++. Nie mówię tu o własnoręcznej implementacji np. niedeterministycznego automatu skończonego Thompsona, choć to też byłoby pewne wyjście. Programiści,​ z natury jednostki ​dość ​leniwe, często lubią korzystać z gotowych komponentów do budowy własnego oprogramowania. Niestety, brak obsługi wyrażeń regularnych w standardowej bibliotece C++ w standardzie C++98 (ISO/IEC 14882:1998) czy ostatnim C++03 (ISO/IEC 14882:​2003). Sytuację poprawia dopiero [[http://​www.open-std.org/​jtc1/​sc22/​wg21/​docs/​papers/​2005/​n1836.pdf|TR1]],​ który będzie częścią standardu C++0x, a którego ostateczne ujęcie i ratyfikacja nastąpią w najbliższych latach, optymistycznie:​ już w przyszłym roku.
  
 Co więc trafiło do przestrzeni ''​std::​tr1''?​ To, co dziś znamy jako biblioteka [[http://​www.boost.org/​doc/​libs/​1_35_0/​libs/​regex/​index.html|Boost.Regex]],​ której autorem jest [[http://​ourworld.compuserve.com/​homepages/​John_Maddock/​|John Maddock]]. Co więc trafiło do przestrzeni ''​std::​tr1''?​ To, co dziś znamy jako biblioteka [[http://​www.boost.org/​doc/​libs/​1_35_0/​libs/​regex/​index.html|Boost.Regex]],​ której autorem jest [[http://​ourworld.compuserve.com/​homepages/​John_Maddock/​|John Maddock]].
Linia 30: Linia 30:
 </​code>​ </​code>​
  
-Korzystając z wyrażeń regularnych, ​w naszym przypadku ​Boost.Regex,​ "​wyłuskiwanie"​ tematu jesteśmy w stanie zrealizować deklaratywnie (pomijając istnienie niezbędnej pętli), a więc opisując czego szukamy, a nie jak:+Korzystając z wyrażeń regularnych,​ w naszym przypadku ​poprzez ​Boost.Regex,​ "​wyłuskiwanie"​ tematu jesteśmy w stanie zrealizować deklaratywnie (pomijając istnienie niezbędnej pętli), a więc opisując ​to, czego szukamy, a nie jak:
 <code cpp> <code cpp>
 std::string line; std::string line;
Linia 53: Linia 53:
  
 Wyobraźmy sobie teraz, że mamy maile wygenerowane przez program Outlook Express, który lubi dodawać więcej przedrostków ''​Re:'',​ aniżeli jest to potrzebne w dłuższych konwersacjach. Dodatkowo weźmy pod uwagę, że niektóre OE mogły być w polskiej wersji językowej i dodawały ''​Odp:''​ zamiast ''​Re:''​. \\ Wyobraźmy sobie teraz, że mamy maile wygenerowane przez program Outlook Express, który lubi dodawać więcej przedrostków ''​Re:'',​ aniżeli jest to potrzebne w dłuższych konwersacjach. Dodatkowo weźmy pod uwagę, że niektóre OE mogły być w polskiej wersji językowej i dodawały ''​Odp:''​ zamiast ''​Re:''​. \\
-Poprawione wyrażenie regularne będzie wówczas wyglądać np. tak "''​^Subject:​ (Re: |Odp: )*(.*)''"​ i to jedyna zmiana niezbędna w rozwiązaniu deklaratywnym. Czytelnik zapewne z wielkim zapałem samodzielnie usprawni rozwiązanie imperatywne...+Poprawione wyrażenie regularne będzie wówczas wyglądać np. tak "''​^Subject:​ (Re: |Odp: )*(.*)''"​ i jest to jedyna zmiana niezbędna w rozwiązaniu deklaratywnym. Czytelnik zapewne z wielkim zapałem samodzielnie usprawni rozwiązanie imperatywne...
  
 ===== Przypomnienie składni wyrażeń regularnych ===== ===== Przypomnienie składni wyrażeń regularnych =====
Linia 60: Linia 60:
   * Perl (domyślna),​   * Perl (domyślna),​
   * POSIX rozszerzona (z odmianami używanymi w ''​egrep''​ czy ''​awk''​),​   * POSIX rozszerzona (z odmianami używanymi w ''​egrep''​ czy ''​awk''​),​
-  * POSIX podstawowe ​(z odmianami używanymi w ''​grep''​ czy ''​emacs''​).+  * POSIX podstawowa ​(z odmianami używanymi w ''​grep''​ czy ''​emacs''​).
  
 Omówię pokrótce tylko najważniejsze elementy domyślnej składni. Omówię pokrótce tylko najważniejsze elementy domyślnej składni.
Linia 80: Linia 80:
   |                        | alternatywa   |                        | alternatywa
   [znaki^odrzuty] ​         | dopuszczalny znaki i niedopuszczalne odrzuty   [znaki^odrzuty] ​         | dopuszczalny znaki i niedopuszczalne odrzuty
 +  \<  \>   ​\b ​             | początek, koniec słowa i którekolwiek z nich
   \d  <​=> ​ [[:​digit:​]] ​    | cyfra   \d  <​=> ​ [[:​digit:​]] ​    | cyfra
   \l  <​=> ​ [[:​lower:​]] ​    | mała litera   \l  <​=> ​ [[:​lower:​]] ​    | mała litera
   \s  <​=> ​ [[:​space:​]] ​    | spacja   \s  <​=> ​ [[:​space:​]] ​    | spacja
   \u  <​=> ​ [[:​upper:​]] ​    | duża litera   \u  <​=> ​ [[:​upper:​]] ​    | duża litera
-  \w  <​=> ​ [[:​word:​]] ​     | znak mogący być elementem wyrazu +  \w  <​=> ​ [[:​word:​]] ​     | znak "​słowny"​ (alfanumeryczny lub _) 
-  \D  <​=> ​ [^[:​digit:​]] ​   | nie cyfra +  \D  <​=> ​ [^[:​digit:​]] ​   | niecyfra 
-  \L  <​=> ​ [^[:​lower:​]] ​   | nie mała litera +  \L  <​=> ​ [^[:​lower:​]] ​   | niemała litera 
-  \S  <​=> ​ [^[:​space:​]] ​   | nie spacja +  \S  <​=> ​ [^[:​space:​]] ​   | niespacja 
-  \U  <​=> ​ [^[:​upper:​]] ​   | nie duża litera +  \U  <​=> ​ [^[:​upper:​]] ​   | nieduża litera 
-  \W  <​=> ​ [^[:​word:​]] ​    | nie znak mogący być elementem wyrazu+  \W  <​=> ​ [^[:​word:​]] ​    | nie :znak "​słowny"​
   (?#​komentarz) ​           | ignorowany (komentarz)   (?#​komentarz) ​           | ignorowany (komentarz)
-  (?:wzorzec) ​             | "​zjada"​ 0 znaków tylko jeżeli wzorzec się zgadza+  (?=wzorzec) ​             | "​zjada"​ 0 znaków tylko jeżeli wzorzec się zgadza
   (?​!wzorzec) ​             | "​zjada"​ 0 znaków tylko jeżeli wzorzec nie pasuje   (?​!wzorzec) ​             | "​zjada"​ 0 znaków tylko jeżeli wzorzec nie pasuje
  
-Wszystkie operatory powtórzenia są domyślnie zachłanne, czyli starają się pokryć jak największy obszar tekstu. Odwrócenie tego zachowania możemy uzyskać poprzez dodanie ''?''​ na końcu takiego operatora, uzyskując np. ''​*?''​ lub ''​{n,​}?''​.+Wszystkie operatory powtórzenia są domyślnie zachłanne ​(ang. //greedy//), czyli starają się pokryć jak największy obszar tekstu. Odwrócenie tego zachowania możemy uzyskać poprzez dodanie ''?''​ na końcu takiego operatora, uzyskując np. ''​*?''​ lub ''​{n,​}?''​.
  
-Na koniec nie sposób nie wspomnieć o odwołaniach wstecznych (ang. //​backreferences//​),​ które stosujemy poprzez poprzedzenie znakiem backslash (\) numeru grupy znaczonej,+Na koniec nie sposób nie wspomnieć o odwołaniach wstecznych (ang. //​backreferences//​),​ które stosujemy poprzez poprzedzenie znakiem backslash (\) numeru grupy znaczonej, ​dzięki którym możemy jeszcze bardziej złożone wyrażenia regularne budować. Wyrażenie regularne odpowiedzialne za wyszukiwanie powtórzeń w tekście mogłoby wyglądać tak: 
 +  (\w) \1
  
 ===== Zaczynamy zabawę ===== ===== Zaczynamy zabawę =====
 +
 +==== Niezbędnik ====
  
 Do skorzystania z dobrodziejstw Boost.Regex niezbędne jest dołączenie pliku nagłówkowego:​ Do skorzystania z dobrodziejstw Boost.Regex niezbędne jest dołączenie pliku nagłówkowego:​
Linia 104: Linia 108:
 #include <​boost/​regex.hpp>​ #include <​boost/​regex.hpp>​
 </​code>​ </​code>​
 +Przy linkowaniu musimy natomiast dołączyć bibliotekę Boost.Regex,​ co w systemach *nixowych odbywa się za pomocą parametru ''​-lboost_regex''​.
  
-Wyrażeniu regularnemu odpowiada szablon klasy ''​boost::​basic_regex'',​ który parametryzowany jest m.in. typem znaku. ​Metody ​tego szablonu to:+Tak jak w przypadku innych bibliotek Boosta, wszystkie stałe, klasy i funkcje zewnętrzne znajdują się przestrzeni nazw ''​boost''​ i dla przejrzystości nie będę tego pisał przy przytaczaniu fragmentów definicji podstawowych elementów Boost.Regex. 
 + 
 +==== Wyrażenie regularne ==== 
 + 
 +Wyrażeniu regularnemu odpowiada szablon klasy ''​boost::​basic_regex'',​ który parametryzowany jest typem znaku i klasą cech tego typu (która domyślnie jest definiowana poprzez szablon ''​regex_traits''​ parametryzowany wcześniej podanym typem znaku) 
 +<code cpp> 
 +template < class charT, 
 +           class traits = regex_traits<​charT>​ > 
 +class basic_regex { /*...*/ }; 
 +</​code>​ 
 + 
 +Dla wygody zdefiniowane są nasŧepujące typy: 
 +<code cpp> 
 +typedef basic_regex<​char> ​    ​regex;​ 
 +typedef basic_regex<​wchar_t> ​ wregex; 
 +</​code>​ 
 + 
 +Ważniejsze metody ​tego szablonu to:
 <code cpp> <code cpp>
-bool empty() const; ​         // zwraca true jeżeli ​obiekt nie zawiera żadnego poprawnego wyrażenia regularnego+bool empty() const; ​         // zwraca true jeżeli obiekt nie zawiera żadnego poprawnego wyrażenia regularnego
 unsigned mark_count() const; // zwraca liczbę znaczonych podwyrażeń w wyrażeniu regularnym unsigned mark_count() const; // zwraca liczbę znaczonych podwyrażeń w wyrażeniu regularnym
 flag_type flags() const; ​    // zwraca maskę bitową zawierającą flagi trybu przetwarzania wyrażenia regularnego flag_type flags() const; ​    // zwraca maskę bitową zawierającą flagi trybu przetwarzania wyrażenia regularnego
 </​code>​ </​code>​
  
-Dla wygody, wewnątrz przestrzeni ''​boost''​ zdefiniowane są nasŧepujące typy:+Najważniejszy jest jednak konstruktor:
 <code cpp> <code cpp>
-typedef ​basic_regex<​char>​ regex; +explicit ​basic_regex(const charT* p, flag_type f = regex_constants::​normal);
-typedef basic_regex<​wchar_t>​ wregex;+
 </​code>​ </​code>​
 +który przyjmuje sekwencję znaków zawierającą wyrażenie regularne oraz tryb przetwarzania tego wyrażenia.
 +W przypadku niepoprawnego wyrażenia regularnego rzucany jest wyjątek ''​boost::​regex_error''​ (dawniej ''​boost::​bad_expression''​ lub ''​boost::​bad_pattern'',​ które są teraz aliasami do ''​boost::​regex_error''​ dla wstecznej kompatybilności),​ chyba że przekazano flagę ''​regex_constants::​no_except''​.
  
 +Mamy następujące podstawowe (nieuzależnione od wybranej składni) tryby przetwarzania:​
 +<code cpp>
 +namespace regex_constants {
 +    typedef implementation-specific-bitmask-type syntax_option_type;​
 +
 +    static const syntax_option_type normal; ​              // Domyślna składnia RE języka Perl
 +    static const syntax_option_type ECMAScript = normal; ​ // i jej aliasy...
 +    static const syntax_option_type JavaScript = normal;
 +    static const syntax_option_type JScript = normal;
 +    static const syntax_option_type perl = normal;
 +    static const syntax_option_type basic; ​               // Składnia POSIX podstawowa
 +    static const syntax_option_type sed = basic; ​         // i jej najpopularniejszy alias
 +    static const syntax_option_type grep;                 // oraz odmiana.
 +    static const syntax_option_type extended; ​            // Składnia POSIX rozszerzona
 +    static const syntax_option_type awk;                  // z dwiema odmianami.
 +    static const syntax_option_type egrep;
 +    static const syntax_option_type icase; ​               // Ignorowanie wielkości liter 
 +                                                          // w dopasowaniach
 +    static const syntax_option_type nosubs; ​              // Zaniechanie tworzenia podwyrażen
 +                                                          // w dopasowaniach
 +    static const syntax_option_type optimize; ​            // Optymalizacja poźniejszego dopasowywania
 +                                                          // nawet kosztem dłuższego tworzenia obiektu.
 +    static const syntax_option_type collate; ​             // Zakresy znaków przekazywane w nawiasach
 +                                                          // kwadratowych powinny uwzględniać lokalizm.
 +}
 +</​code>​
 +
 +Dla wygody powyższe stałe zdefiniowane są także w obrębie szablonu klasy ''​basic_regex''​.
 +
 +==== Dopasowania ====
 +
 +Oprócz klasy reprezentującej wyrażenie regularne potrzebujemy także klasy przechowującej znalezione dopasowania w zadanym tekście. Służy do tego szablon klasy ''​boost::​match_results''​ parametryzowany typem iteratora obsługującego sekwencję wejściową:​
 +<code cpp>
 +template < class BidirectionalIterator,​
 +           class Allocator = std::​allocator<​sub_match<​BidirectionalIterator>​ >
 +class match_results { /*...*/ };
 +</​code>​
 +
 +Podobnie jak wcześniej, zdefiniowano następujące typy dla wygodniejszego użycia:
 +<code cpp>
 +typedef match_results<​const char*> ​             cmatch;
 +typedef match_results<​const wchar_t*> ​          ​wcmatch;​
 +typedef match_results<​string::​const_iterator> ​  ​smatch;​
 +typedef match_results<​wstring::​const_iterator> ​ wsmatch;
 +</​code>​
 +
 +Dopasowania to tak naprawdę kolekcje poddopasowań ''​boost::​sub_match'',​ do których dostęp mamy jedynie poprzez ''​operator[]''​ klasy ''​boost::​match_results''​. Obiekt klasy ''​boost::​sub_match''​ posiada kilka przydatnych składowych:​ boolowską ''​matched''​ informującą o tym czy dane znaczone podwyrażenie brało udział w dopasowaniu i jeżeli tak, to iteratory ''​first'',​ ''​second''​ wskazujące na początek oraz koniec (z wyłączeniem) w ciągu, który pokrył to dopasowanie.
 +
 +Mając obiekt tej klasy, nazwijmy go m, odwołania do poszczególnych elementów wyglądają następująco:​
 +^ Boost.Regex ^ Perl ^ Opis ^
 +| m.prefix() ​ | $`   | treść poprzedzająca dopasowanie ​ |
 +| m[0]        | $& ​  | cała dopasowana treść ​           |
 +| m[1]        | $1   | pierwsze znaczone podwyrażenie ​  |
 +|         ​... ​      || kolejne znaczone podwyrażenia ​   |
 +| m[n]        | $n   | n-te znaczone podwyrażenie ​      |
 +| m.suffix() ​ | $' ​  | treść następująca po dopasowaniu |
 +
 +Szablon klasy ''​match_results''​ posiada także metody
 +<code cpp>
 +const_iterator begin() const; ​ // Zwraca startowy iterator znaczonych podwyrażeń.
 +const_iterator end() const; ​   // Zwraca terminujący iterator znaczonych podwyrażeń.
 +</​code>​
 +które ułatwiają realizację wygodnego iterowania po znaczonych podwyrażeniach.
 +
 +Dostępne są ponadto różne tryby dopasowań, przekazywane w funkcjach niżej omówionych. Przykłady to:
 +<code cpp>
 +namespace regex_constants {
 +    typedef implemenation-specific-bitmask-type match_flag_type;​
 +    ​
 +    static const match_flag_type match_default = 0;      // Zachowanie domyślne (zgodne z ECMA-262)
 +    /* ... */
 +    static const match_flag_type match_not_null; ​        // Dopasowanie nie może być puste.
 +    /* ... */
 +    static const match_flag_type match_single_line; ​     // ^ odpowiada za początek tekstu,
 +                                                         // a $ - koniec tekstu.
 +    /* ... */    ​
 +    static const match_flag_type format_no_copy; ​        // Sekcje tekstu niepasujące do wyrażenia
 +                                                         // regularnego nie są kopiowane do
 +                                                         // wyjściowego łańcucha znaków.
 +    /* ... */
 +    static const match_flag_type format_all; ​            // Aktywuje wszystkie dostępne rozszerzenia
 +                                                         // składni, włączając w to warunkowe podstawienia.
 +}
 +</​code>​
 ===== Możliwości Boost.Regex ===== ===== Możliwości Boost.Regex =====
 +
 +Same szablony klas ''​boost::​basic_regex''​ oraz ''​boost::​match_results''​ byłyby nic nie warte bez funkcji, które na nich operują realizując podstawowe przypadki użycia wyrażen regularnych. ​
 +
 +Poniżej omówione szablony funkcji parametryzowane są różnymi elementami w zależności od wersji (pominę dokładne deklaracje tych szablonów),​ ale zawsze jest to m.in. typ znaku.
  
 ==== Dopasowywanie (matching) - regex_match ==== ==== Dopasowywanie (matching) - regex_match ====
 +
 +<code cpp>
 +// Wersja bez zapisywania odnalezionych dopasowań.
 +template </* */>
 +bool regex_match(const basic_string</​* */>& s,
 +                 const basic_regex</​* */>& e,
 +                 ​match_flag_type flags = match_default);​
 +
 +// Wersja zapisująca odnalezione dopasowania w obiekcie klasy match_results.
 +template </* */>
 +bool regex_match(const basic_string</​* */>& s,
 +                 ​match_results</​* */>& m,
 +                 const basic_regex</​* */>& e,
 +                 ​match_flag_type flags = match_default);​
 +
 +// Wersja główna, wołana także przez powyższe
 +template <class BidirectionalIterator,​ class Allocator, class charT, class traits>
 +bool regex_match(BidirectionalIterator first, BidirectionalIterator last,
 +                 ​match_results<​BidirectionalIterator,​ Allocator>&​ m,
 +                 const basic_regex<​charT,​ traits>&​ e,
 +                 ​match_flag_type flags = match_default);​
 +</​code>​
 +
 +Chyba najważniejsze w tej funkcji jest to, że dopasowaniu musi ulec cały tekst, dlatego też asercje użyte na początku i końcu wyrażenia regularnego (^ i $ odpowiednio) nie mają najmniejszego znaczenia (o czym już wcześniej wspomniałem w mikropokazie).
 +
 +Najczęściej używana jest do badania poprawności przekazanych danych.
 +
 +<code cpp>
 +#include <​iostream>​
 +#include <​string>​
 +#include <​boost/​regex.hpp>​
 +
 +namespace validations {
 +    // Wyrażenie regularne zgodne z formatem IBAN
 +    // IBAN = International Bank Account Number
 +    static const boost::​regex ibanFormat("​[A-Z]{2}\\d{2} ?​[A-Z\\d]{4}(?:​ ?​\\d{4}){1,​} ?​\\d{1,​4}"​);​
 +
 +    // Sprawdź czy string ma format IBAN
 +    bool hasIbanFormat(const std::​string&​ s)
 +    {
 +        return regex_match(s,​ ibanFormat);​
 +    }    ​
 +}
 +
 +// Wczytaj linię z wejścia i sprawdź czy to może być IBAN,
 +// wówczas zwróć 1, w przeciwnym przypadku - 0.
 +int main() {
 +    std::string line;
 +    std::​getline(std::​cin,​ line);
 +    std::cout << validations::​hasIbanFormat(line) << std::endl;
 +    return 0;
 +}
 +</​code>​
  
 ==== Przeszukiwanie (searching) - regex_search ==== ==== Przeszukiwanie (searching) - regex_search ====
 +
 +<code cpp>
 +// Wersja bez zapisywania odnalezionych dopasowań.
 +template </* */>
 +bool regex_search(const basic_string</​* */>& s,
 +                 const basic_regex</​* */>& e,
 +                 ​match_flag_type flags = match_default);​
 +
 +// Wersja zapisująca odnalezione dopasowania w obiekcie klasy match_results.
 +template </* */>
 +bool regex_search(const basic_string</​* */>& s,
 +                 ​match_results</​* */>& m,
 +                 const basic_regex</​* */>& e,
 +                 ​match_flag_type flags = match_default);​
 +
 +// Wesja główna, wołana także przez powyższe
 +template <class BidirectionalIterator,​ class Allocator, class charT, class traits>
 +bool regex_search(BidirectionalIterator first, ​
 +                  BidirectionalIterator last,
 +                  match_results<​BidirectionalIterator,​ Allocator>&​ m,
 +                  const basic_regex<​charT,​ traits>&​ e,
 +                  match_flag_type flags = match_default);​
 +</​code>​
 +
 +Prosty przykład użycia korzystający również z iteratora po wszystkich dopasowaniach wyrażenia regularnego.
 +<code cpp>
 +#include <​iostream>​
 +#include <​string>​
 +#include <​boost/​regex.hpp>​
 +
 +// Callback dla funkcji std::​for_each operujący na wynikach dopasowania.
 +bool printRep(const boost::​smatch&​ what)
 +{
 +    std::cout << what[1] << " "; ​ // Wyświetl na standardowe wyjście pierwsze podwyrażenie.
 +    return true;                  // Nie zatrzymuj wykonywania pętli for_each.
 +}
 +
 +// Wczytaj linię z wejścia i sprawdź czy są w niej powtórzenia "​słów",​
 +// a jeżeli są, podaj jakie.
 +int main() {
 +    // Wyrażenie regularne reprezentujące powtarzające się słowa.
 +    boost::​regex rep("​(\\<​\\w+\\>​)(?:​\\s+\\<​\\1\\>​)+"​);​
 +    std::string line;
 +    std::​getline(std::​cin,​ line);
 +    // Początkowy iterator dopasowań do wyrażen regularnych.
 +    boost::​sregex_iterator begin(line.begin(),​ line.end(), rep);
 +    // Terminalny iterator dopasowań do wyrażen regularnych.
 +    boost::​sregex_iterator end;
 +    // Dla każdego dopasowania wywołaj funkcję printRep.
 +    std::​for_each(begin,​ end, &​printRep);​
 +    std::cout << std::endl;
 +    return 0;
 +}
 +</​code>​
  
 ==== Zastępowanie (replacing) - regex_replace ==== ==== Zastępowanie (replacing) - regex_replace ====
 +
 +<code cpp>
 +// Wersja wygodna
 +template </* */>
 +basic_string</​* */> regex_replace(const basic_string</​* */>& s,
 +                                  const basic_regex</​* */>& e,
 +                                  const basic_string</​* */>& fmt,
 +                                  match_flag_type flags = match_default);​
 +
 +// Wersja główna
 +template <class OutputIterator,​ class BidirectionalIterator,​ class traits, class charT>
 +OutputIterator regex_replace(OutputIterator out,
 +                             ​BidirectionalIterator first,
 +                             ​BidirectionalIterator last,
 +                             const basic_regex<​charT,​ traits>&​ e,
 +                             const basic_string<​charT>&​ fmt,
 +                             ​match_flag_type flags = match_default);​
 +</​code>​
 +
 +W domyślnej składni stringu formatującego (przy braku flagi ''​regex_constants:​literal''​) zdefiniowane są sekwencje specjalne odnoszące się do podmienianego dopasowania takie jak w Perlu (zaprezentowane zostały wcześniej w tabelce [[#​Dopasowania]]). Aby uzyskać na wyjściu znak dolara ($), trzeba napisać go dwukrotnie.
 +
 +Jako przykład zastosowania przedstawię kod aplikacji wyświetlającej na wyjście przekazany w 1. argumencie plik CSV (Comma-Separated Values) z zamienionymi miejscami pierwszymi dwiema kolumnami, a kolumny te są oddzielone przecinkiem.
 +<code cpp>
 +#include <​iostream>​
 +#include <​fstream>​
 +#include <​string>​
 +#include <​boost/​regex.hpp>​
 +
 +// Załaduj strumień wejściowy do stringa.
 +void loadFile(std::​string&​ s, std::​istream&​ is)
 +{
 +    s.erase();
 +    s.reserve(is.rdbuf()->​in_avail());​
 +    char c;
 +    while (is.get(c))
 +    {
 +        if (s.capacity() == s.size())
 +            s.reserve(s.capacity() * 3);
 +        s.append(1, c);
 +    }
 +}
 +
 +// Wyświetl plik CSV (o nazwie przekazanej w 1. argumencie) z zamienionymi pierwszymi 2 kolumnami
 +int main(int argc, const char** argv)
 +{
 +    // Wyrażenie regularne reprezentujące pierwsze dwie kolumny (druga może być pusta)
 +    boost::​regex re("​^([^,​\n]*),?​([^,​\n]*)"​);​
 +    // Format stringu odpowiadający podmienianemu dopasowaniu.
 +    std::string fmt("​\\2,​\\1"​);​
 +    try {
 +        std::​ifstream fs(argv[1]);​
 +        std::string in;
 +        loadFile(in,​ fs);
 +        // Wyświetl na wyjściu string in z zamienionymi dopasowaniami re na string formatujący fmt.
 +        std::cout << boost::​regex_replace(in,​ re, fmt);
 +    }
 +    catch(...) {
 +        return -1;
 +    }
 +    return 0;
 +}
 +</​code>​
  
 ==== Dzielenie (tokenizing) - regex_token_iterator ==== ==== Dzielenie (tokenizing) - regex_token_iterator ====
 +
 +''​boost::​regex_token_iterator''​ to już nie funkcja a szablon klasy będącej adapterem iteratora.
 +
 +<code cpp>
 +template <class BidirectionalIterator, ​
 +          class charT = iterator_traits<​BidirectionalIterator>::​value_type,​
 +          class traits = regex_traits<​charT>​ >
 +class regex_token_iterator {
 +    /* ... */
 +    regex_token_iterator(BidirectionalIterator a, 
 +                         ​BidirectionalIterator b, 
 +                         const basic_regex<​charT,​ traits>&​ re, 
 +                         int submatch = 0, 
 +                         ​match_flag_type m = match_default);​
 +   ​regex_token_iterator(BidirectionalIterator a, 
 +                        BidirectionalIterator b, 
 +                        const basic_regex<​charT,​ traits>&​ re, 
 +                        const std::​vector<​int>&​ submatches, ​
 +                        match_flag_type m = match_default);​
 +    /* ... */
 +};
 +</​code>​
 +
 +Po 2 przykłady użycia odsyłam na sam dół strony z dokumentacją [[http://​www.boost.org/​doc/​libs/​1_35_0/​libs/​regex/​doc/​html/​boost_regex/​ref/​regex_token_iterator.html|regex_token_iterator]].
  
 ===== Boost.Xpressive - relatywnie młody konkurent ===== ===== Boost.Xpressive - relatywnie młody konkurent =====
  
-Ważne innowacje:+Ważne innowacje ​w stosunku do Boost.Regex:
   * obsługa tworzenia (obok dynamicznych,​ podobnych w zapisie do Boost.Regex) statycznych wyrażeń regularnych,​ podobnych w zapisie do Boost.Spirit,​ przez przeciążanie operatorów i zastosowanie techniki zwanej szablonami wyrażeniowymi (ang. //​expression templates//​) - owa statyczność daje wiele możliwości,​ m.in. kontrolę poprawności takiego wyrażenia już w czasie kompilacji,   * obsługa tworzenia (obok dynamicznych,​ podobnych w zapisie do Boost.Regex) statycznych wyrażeń regularnych,​ podobnych w zapisie do Boost.Spirit,​ przez przeciążanie operatorów i zastosowanie techniki zwanej szablonami wyrażeniowymi (ang. //​expression templates//​) - owa statyczność daje wiele możliwości,​ m.in. kontrolę poprawności takiego wyrażenia już w czasie kompilacji,
   * możliwość mieszania statycznych i dynamicznych wyrażeń regularnych,​   * możliwość mieszania statycznych i dynamicznych wyrażeń regularnych,​
-  * możliwość osadzania wyrażeń regularnych poprzez referencje.+  * możliwość osadzania wyrażeń regularnych ​w sobie poprzez referencje.
  
 ===== Kiedy co używać? ===== ===== Kiedy co używać? =====
Linia 145: Linia 449:
 ^ Statyczność - brak nowych deklaracji w trakcie działania |  tak  |  tak   ​| ​   tak    | ^ Statyczność - brak nowych deklaracji w trakcie działania |  tak  |  tak   ​| ​   tak    |
 ^ Wyczerpujące przeszukiwanie z nawrotami ​                 |  tak  |   ​- ​   |    tak    | ^ Wyczerpujące przeszukiwanie z nawrotami ​                 |  tak  |   ​- ​   |    tak    |
 +
 +Małego komentarza może wymagać wyczerpujące przeszukiwanie z nawrotami (ang. //​exhaustive backtracking//​). Chodzi o to, że przymiotnik "​wyczerpujące"​ nie opisuje samego procesu przeszukiwania z nawrotami, które w istocie jest wyczerpujące,​ a konsekwencję w jego stosowaniu, która jest wymagana od wyrażeń regularnych. Boost.Spirit stosuje backtracking,​ jednak gdy znajduje jakieś częściowe dopasowanie,​ to nie "cofa się", żeby możliwe było zrealizowanie reszty reguły, tylko uznaje, że dopasowanie się nie powiodło. Dlatego poniższy przykład nigdy nie zadziała:
 +<code cpp>
 +*chlit_p('​a'​) >> chlit_p('​a'​);​
 +</​code>​
regex.1208306250.txt.gz · ostatnio zmienione: 2008/04/16 02:37 przez przemoc