Autor: Paweł Rajewski

Wyrażenia regularne to sposób ogólnego opisu sekwencji znaków spełniających określone kryteria. Często korzystamy z takiej notacji, być może nawet o tym nie wiedząc. W tym artykule przedstawię podstawy i nie tylko wyrażeń regularnych.

Uwaga1: składnia wyrażeń regularnych jest taka sama w językach VBScript i JScript – z jednym wyjątkiem. W JScript wyrażenie regularne jest obiektem i musi być ograniczone znakami łamania. A zatem wyrażeniu „abc” z VBScript odpowiada wyrażenie /abc/ w JScript. Praktyczne zastosowanie wyrażeń różni się w obu językach. JScript jest pod tym względem bogatszy, ale także nieco trudniejszy.

Co to są wyrażenia regularne?

Wyrażenia regularne to sposób ogólnego opisu sekwencji znaków spełniających określone kryteria. Często korzystamy z takiej notacji, być może nawet o tym nie wiedząc. W systemie operacyjnym Windows tę rolę pełnią znaki * i ? – pierwszy oznacza dowolny ciąg znaków (w tym ciąg pusty), drugi – dowolny pojedynczy znak. Aby wybrać pliki tekstowe o nazwach zaczynających się od litery b wystarczy wpisać w okienku „Otwórz”: b*.txt – czyli „pokaż pliki o pierwszej literze b, potem dowolny ciąg dowolnych znaków i rozszerzenie .txt”. Analogicznie b????.* znajdzie wszystkie pliki o nazwach pięcioliterowych zaczynających się na b i dowolnym rozszerzeniu (litera b, potem cztery dowolne znaki i dowolne rozszerzenie). To są właśnie wyrażenia regularne. Uwaga: w VBScript składnia jest inna!

Pierwszy skrypt

Trudno poznawać wyrażenia regularne „na sucho”, lepiej testować je na konkretnym, widzialnym przykładzie. Prostym testerem może być skrypt umieszczony w sekcji BODY (skrypt nr 1):

<SCRIPT TYPE="text/vbscript" LANGUAGE="VBScript" 
ID="skrypt1"> 
Option Explicit 
Dim strLancuch, strWynik, objRExp 
strLancuch="Ala ma kota i kanarka" 
Set objRExp=New RegExp 
objRExp.Pattern="a" 
objRExp.Global=True 
objRExp.IgnoreCase=False 
objRExp.MultiLine=False 
strWynik=objRExp.Replace(strLancuch,"*") 
window.document.write(strLancuch&"<BR>"&strWynik) 
</SCRIPT> 

Po uruchomieniu skrypt wyświetli dwa teksty. Pierwszy, wejściowy, podany w zmiennej strLancuch, i drugi, w którym odnalezione ciągi znaków zostały zamienione na gwiazdki. W powyższym przykładzie znalezione i zamienione zostaną wszystkie litery a.

Obiekt RegExp, metoda Replace()

Za obsługę wyrażeń regularnych odpowiada obiekt RegExp. To on „rozumie” wyrażenie, dokonuje żądanych operacji i zwraca wyniki. Użycie wyrażenia regularnego poza obiektem RegExp nie ma sensu – zostanie potraktowane jak zwykły ciąg znaków bez specjalnego znaczenia. (W języku JScript wyrażenia regularne można stosować nieco szerzej).

Obiekt RegExp nie jest dostępny automatycznie i trzeba uaktywnić go (utworzyć) przed użyciem. Następuje to w linii nr 5 skryptu. Odwołanie do obiektu zostaje przypisane do zmiennej objRExp.

Następnie należy ustawić właściwości obiektu: Pattern, Global, IgnoreCase i MultiLine.

Pattern – określa wzór ciągu, którego poszukujemy. To jest właśnie wyrażenie regularne.

Global – określa, czy przeszukiwanie ma być globalne, czyli do końca łańcucha wejściowego (True), czy tylko do znalezienia pierwszego wystąpienia poszukiwanego ciągu (False). Domyślnie False.

IgnoreCase – określa, czy przy przeszukiwaniu ignorować wielkość liter (True) czy nie (False). W przypadku True litery „duże” i „małe” traktowane będą tak samo, np. wyszukiwanie wyrazu „Ala” odnajdzie zarówno „Ala”, jak i „ala”. Domyślnie False.

MultiLine – określa, czy łańcuch wejściowy (przeszukiwany) zawiera znaki nowej linii (True), czy nie (False), co ma wpływ na sposób interpretacji jego początku i końca. Domyślnie False.

Obiekt RegExp dysponuje trzema metodami. W skrypcie-testerze wykorzystano metodę Replace(strA,strB), przeszukującą łańcuch strA (podany jako pierwszy parametr) i zamieniającą każdy odnaleziony ciąg znaków na łańcuch strB (podany jako drugi parametr). W tym przypadku przeszukiwany jest łańcuch zapamiętany w zmiennej strLancuch, a znalezione ciągi zamieniane są na gwiazdki (linia nr 10). Metoda Replace() zwraca wynik – kopię przeszukiwanego łańcucha z dokonanymi zmianami, zapamiętywaną w zmiennej strWynik (oryginalny łańcuch nie zostaje naruszony!). Następnie oba łańcuchy – strLancuch (wejściowy) i strWynik (wynikowy) zostają wyświetlone na ekranie (linia nr 11).

Metoda Replace() działa zgodnie z parametrami ustawionymi dla obiektu RegExp – wyszukiwanie prowadzone jest według podanego wzoru (Pattern), i w sposób określony we właściwościach Global, IgnoreCase i MultiLine.

Składnia wyrażeń regularnych

W języku VBScript wyrażenie regularne jest łańcuchem, w którym zapisujemy wzór poszukiwanego ciągu. Jak każdy łańcuch, wyrażenie zaczyna się i kończy znakiem cudzysłowu.

Traktowanie wyrażenia jak łańcucha jest bardzo wygodne – bez problemu możemy zapisać w nim dowolne znaki korzystając z funkcji Chr(intNr). Np. aby w wyrażeniu „Ala” zapisać poprawne polskie cudzysłowy wystarczy dokonać operacji: Chr(132)&”Ala”&Chr(148), gdzie liczby w nawiasach są numerami odpowiednich znaków w kodzie ASCII. (Jeśli korzystamy ze strony kodowej windows-1250, wszystkie znaki możemy wpisywać wprost z klawiatury metodą: lewy Alt plus czterocyfrowy numer znaku na klawiaturze numerycznej).

W wyrażeniach regularnych istnieje znak o szczególnym znaczeniu – jest nim backslash (\). Backslash zmienia znaczenie znaku następującego bezpośrednio po nim. Np. b oznacza zwykłą literę b, ale \b oznacza brzeg wyrazu. ( oznacza początek nawiasu grupującego wyrażenia, ale \( oznacza zwykły znak otwarcia nawiasu. Jak w takim razie zapisać w wyrażeniu sam znak backslash? Poprzedzić go… znakiem backslash. \ oznacza, że kolejny znak będzie miał zmienione znaczenie, a zatem \\ oznacza zwyczajny znak \ do wpisania do łańcucha.

Wyszukiwanie znaków

Na początek zmieńmy łańcuch strLancuch na: „Ala ma kotaaa i kanarka. Zażółć gęślą jaźń! [40+50=90] [7-6=1] [2^3=8] ala@serwer.pl | HTML3.2 HTML4.0 HTML4.1 XHTML”. Taki ciąg zawiera wszystkie polskie litery i kilka kombinacji znaków, które przydadzą się przy testowaniu wyrażeń.

Jeśli chcemy odnaleźć w ciągu określony znak, podajemy go we właściwości Pattern (linia 6 skryptu):

objRegExp.Pattern="a" 

– odszuka wszystkie litery a w badanym ciągu. (Gdybyśmy zmienili właściwość IgnoreCase na True, wyszukane byłyby wszystkie litery a oraz A. Gdybyśmy zmienili właściwość Global na False, wyszukana byłaby tylko pierwsza litera a).

Jeśli chcemy znaleźć kilka liter np. a oraz k, podajemy je po kolei w nawiasie prostokątnym oznaczającym grupę znaków. Np.:

"[ak]"

– wyszuka wszystkie litery a i wszystkie litery k w badanym ciągu. Uwaga: to jest grupa osobnych znaków, a nie łańcuch „ak”! Znaków nie należy rozdzielać spacją, ponieważ w takim przypadku znalezione zostaną także spacje.

Jeśli chcemy znaleźć znaki z określonego zakresu, np. litery od a do k możemy wpisać je wszystkie:

"[abcdefghijk]"

…albo zastosować skrót:

"[a-k]"

Minus oznacza w tym przypadku zakres kolejnych znaków „od-do”. W ten sposób:

"[0-9]"

– wyszuka wszystkie cyfry.

W przypadku cyfr można stosować znak specjalny \d oznaczający dowolną cyfrę. A zatem:

"\d"

…to to samo co:

Mezzmo - filmy i muzyka z PC na TV i DLNA
"[0-9]"

…i to samo co:

"[0123456789]"

Używając zakresów trzeba pamiętać, że kolejność znaków odpowiada kolejności ich kodów, a nie kolejności alfabetycznej w języku polskim. A zatem „[a-z]” nie obejmie polskich liter, ponieważ są one umieszczone w tablicy znaków powyżej podstawowych znaków A-Z i a-z. Wyrażenie:

objRegExp.Pattern="[a-z]" 
objRExp.Global=True 
objRegExp.IgnoreCase=True 

odszuka więc wszystkie litery pomijając polskie (oraz cyfry i inne znaki „nieliterowe” występujące w danym ciągu). Jak sobie z tym poradzić? Najprościej dopisać polskie litery do wyrażenia:

"[a-ząćęłńóśżź]"

– taki zapis odnajdzie wszystkie litery, łącznie z polskimi.

Jeżeli używamy zakresu i chcielibyśmy jednocześnie znaleźć w łańcuchu znak minus, można umieścić go na pierwszym lub ostatnim miejscu poszukiwanej grupy znaków – w takiej sytuacji nie będzie traktowany jak oznaczenie zakresu, lecz jak zwykły znak do wyszukania. Np. aby znaleźć cyfry 0-9 i znak minus można napisać:

"[0-9-]"

Gdy nie ma takiej możliwości, należy skorzystać ze znaku backslash:

"[0\-9]"

– wyszuka wszystkie zera, minusy i dziewiątki.

Podobnie postępujemy w przypadku nawiasu kwadratowego, jeśli chcemy znaleźć taki znak, a nie traktować go jak znak zakresu:

"[\[\]]"

– wyszuka wszystkie znaki otwarcia i zamknięcia nawiasu kwadratowego (wewnątrz nawiasu oznaczającego grupę znaków znajdują się dwa znaki: \[ oraz \], z których zdjęto znaczenie specjalne przy pomocy znaków backslash).

Jeśli chcemy odnaleźć znaki inne niż podane (np. inne niż litery od b do k), jako pierwszy znak do odszukania podajemy „daszek” ^. Oznacza on negację całej zawartości nawiasu kwadratowego. Np.:

"[^b-k]"

– wyszuka wszystkie znaki inne niż b, c, d, e, f, g, h, i, j, k – w tym także spacje, cyfry, znaki „nieliterowe” (uwaga: właściwość IgnoreCase musi być ustawiona na False, w przeciwnym razie pominięte będą także duże litery wskazane w zakresie).

A jeśli chcemy wyszukać w łańcuchu znak „daszka”? Najprościej wpisać go na dalszej pozycji (nie pierwszej) – wówczas będzie traktowany jak zwykły znak do wyszukania. Gdy nie ma takiej możliwości, należy skorzystać ze znaku backslash np.:

"[\^b-k]"

– wyszuka litery od b do k i daszki.

Istnieje kilka specjalnych znaków i miejsc w łańcuchach, które również możemy wyszukiwać. Najbardziej użyteczne to:

\n – znak nowej linii.
\t – znak tabulatora.
\w – dowolna litera, cyfra lub znak podkreślenia (odpowiada więc zapisowi: [a-zA-Z0-9_]). Uwaga: nie uwzględnia polskich liter! (patrz uwagi powyżej).
\W – dowolny znak inny niż litera, cyfra lub znak podkreślenia (uwaga jw.).
\s – dowolny znak niedrukowany – spacja, tabulator, znak nowej linii itp.
\S – dowolny znak drukowany (przeciwieństwo \s).
\d – dowolna cyfra (odpowiada więc zapisowi: [0-9]).
\D – dowolny znak inny niż cyfra (odpowiada zapisowi: [^0-9]).
\xnn – znak, którego szesnastkowy kod wynosi nn. Np.: \x70 odpowiada literze p.
\unnnn – znak, którego kod w standardzie Unicode wynosi nnnn. Np. \u0070 odpowiada literze p.
. – (kropka) dowolny znak inny niż znak nowej linii. Aby znaleźć w łańcuchu kropkę należy zastosować znak backslash.
^ – („daszek”) jeśli nie pełni roli negacji, oznacza początek łańcucha.
$ – poza przypadkami wskazanymi w dalszej części artykułu, oznacza koniec łańcucha.

Wyszukiwanie ciągów

Gdy chcemy znaleźć w łańcuchu ciąg (np. Ala) wpisujemy go wprost we właściwości Pattern:

objRegExp.Pattern="Ala" 
objRExp.Global=True 
objRegExp.IgnoreCase=False 

– wyszuka wszystkie ciągi Ala (podczas gdy „[Ala]” wyszukałoby wszystkie litery A, l oraz a). Warto zwrócić uwagę, że odnaleziony ciąg traktowany jest jako całość i zastępowany jedną gwiazdką, a nie trzema.

Gdy chcemy wyszukać kilka ciągów np. ala oraz arka, stosujemy kreskę pionową oznaczającą LUB (OR):

"ala|arka"

– wyszuka wszystkie ciągi ala oraz ciągi arka (czyli ciągi, które pasują do wzoru: „ala LUB arka”). Oczywiście, aby wyszukać znak kreski pionowej, należy zdjąć z niego znaczenie specjalne:

"\|"

– wyszuka kreski pionowe.

Powiedzmy, że interesują nas jedynie pełne słowa. Co zrobić, aby ciąg arka nie był odnajdywany gdy jest fragmentem słowa kanarka? Można wykorzystać znak specjalny \b oznaczający granicę wyrazu. Wyrażenie:

"\barka\b"

odnajdzie tylko ciągi, które stanowią pełen wyraz (na początku i na końcu mają granicę wyrazu). Za granicę wyrazu uznaje się punkt pomiędzy znakiem a brzegiem łańcucha, spacją, znakiem interpunkcyjnym, myślnikiem i innym znakiem „nieliterowocyfrowym”. Jeśli podamy wyrażanie:

"\b"

gwiazdki pojawią się wszędzie tam, gdzie zostanie wykryta „granica wyrazu”. Warto zauważyć, że za granicę przyjmowane są także polskie litery – niestety, nie wchodzą w skład podstawowego alfabetu i dlatego traktowane są jak inne znaki.

Istnieje również znak \B odpowiadający „wewnątrzwyrazowej granicy” czyli pozycjom pomiędzy znakami, które nie są uznawane za granicę wyrazu. Można go wykorzystać do poszukiwania wyrazów leżących wewnątrz innych wyrazów i nie będących osobnymi wyrazami (chyba stosuje się to rzadko). Jeśli uruchomimy wyrażenie:

"\B"

…zobaczymy gwiazdki w miejscach „wewnątrzwyrazowych granic”. Uwagi dotyczące polskich liter jw.

Wyszukiwanie ciągów można łączyć z wyszukiwaniem pojedynczych znaków. Np. zapis:

"a[ln]a"

wyszuka ciągi, w których pierwsza litera to a, następnie litera z podanej grupy (l albo n) i następnie znowu litera a. W naszym przykładzie znaleziony zostanie ciąg ala w adresie mailowym i ana w słowie kanarka.

Można też wyszukiwać ciągi w zależności od dalszej części ciągu. W takiej sytuacji możliwe „dalsze ciągi” wyrazu należy zgrupować w nawiasie zaczynającym się od znaków ?= Np. wyrażenie:

"HTML(?=3\.2|4\.1)"

wyszuka ciąg HTML w wyrazach HTML3.2 i HTML4.1, ale nie w HTML4.0 czy XHTML. Trzeba zauważyć, że znajdowany jest tylko ciąg HTML, a dalsza część wyrazu jest jedynie sprawdzana i nie jest „wliczana” do odnalezionego ciągu.

Odwrotnie zachowuje się nawias rozpoczęty znakami ?! – oznacza wykluczone „dalsze ciągi”. Np. zmiana powyższego wyrażenia na:

"HTML(?!3\.2|4\.1)"

spowoduje wyszukanie wszystkich ciągów HTML, których dalszy ciąg nie brzmi 3.2 ani 4.1.

c.d.n.

Paweł Rajewski

Skasowane dane to nie zawsze tragedia - ściągnij program i odzyskaj dane