Pomoc:Wyrażenia regularne

Dla zaawansowanych
Te informacje dotyczą zarówno edytora wizualnego, jak i edytora wikikodu (Czym to się różni?)
Ikony narzędzi do wyrażeń regularnych: widoczna na pasku edycyjnym edytora wizualnego i nowego edytora wikikodu (po lewej) i na pasku starego edytora wikikodu (po prawej)

Wyrażenia regularne (ang. regular expressions, w skr. regex) pozwalają w trybie edycji wyszukiwać tekst o określonych kryteriach i zmienić go na inny. Ta funkcja poszerza możliwości narzędzia typu „znajdź/zamień”. W edytorze wikikodu jest dostępna tylko dla zalogowanych użytkowników, a w edytorze wizualnym – dla wszystkich.

UruchamianieEdytuj

Jeżeli korzystasz ze starego edytora wikikodu, możesz skorzystać z dwóch narzędzi. Jedno jest dostępne w domyślnym edytorze wikikodu (włączona opcja rozszerzonego paska edycji w preferencjach). Po prawej stronie drugiego rzędu paska narzędzi (powrócić można do niego klikając "zaawansowane") znajduje się ikona z kartką, lupą i ołówkiem. Jej kliknięcie spowoduje wyświetlenie okna dialogowego "Wyszukaj i zamień". Dostępna jest opcja "Traktuj wyszukiwany ciąg jako wyrażenie regularne", którą trzeba zaznaczyć, aby korzystać z omawianych tu wyrażeń regularnych.

Dostępny jest także oddzielny gadżet. Aby go włączyć, należy w swoich preferencjach przejść do zakładki „Gadżety”, tam do sekcji „Edycja stron” i włączyć narzędzie o nazwie „    Wyszukiwanie i zamiana – dodaje dwa przyciski do paska narzędziowego nad oknem edycji, które umożliwiają skorzystanie z nowych funkcji: wyszukiwanie i zamiana oraz zmiana wielkości liter.”.

Przy opisie tego drugiego narzędzia widać dwie ikonki, które od tej pory pojawią się również na pasku narzędziowym nad polem edycji tekstu w trybie wikikodu. Drugie z nich służy do zamiany liter małych i wielkich, pierwsze zaś – opatrzone symbolem lupki – poszerza możliwości edytora w sposób większy, niż można by się tego spodziewać, ponieważ oprócz zwykłego wyszukiwania i zastępowania ciągu znaków pojawia się również możliwość zaawansowanego wyszukiwania. Aby z tego zaawansowanego wyszukiwania skorzystać, należy kliknąć w lupkę, a następnie odnaleźć i zaznaczyć kratkę przy napisie „użyj RegEx”.

Jeżeli korzystasz z edytora wizualnego lub nowego edytora wikikodu (czyli z interfejsem podobnym do edytora wizualnego), to również jest możliwość wyszukiwania i zamiany, także z użyciem wyrażeń regularnych. Podczas edycji należy wcisnąć CTRL + F. Pojawi się na górze pasek wyszukiwania i zamiany. Aby można było w niej wyszukiwać za pomocą wyrażeń regularnych, kliknij ikonkę (.*) po prawej stronie pierwszego pola (służącego do wpisywania tekstu szukanego).

Zalety i wadyEdytuj

Od tej pory niektóre znaki wprowadzane do pól Znajdź i Zamień będą zachowywać się w sposób odbiegający od standardowego. Głównie dotyczy to pola Znajdź, w którym całkiem sporo znaków nabierze nowego, specjalnego znaczenia, ale i w polu Zamień, choć w mniejszym stopniu, kilka znaków będzie inaczej rozumiane przez program. Na przykład kropka w polu Znajdź przestanie być znakiem interpunkcyjnym, a zacznie oznaczać dowolny znak oprócz znaku końca akapitu, z kolei znak plusa będzie oznaczać, że coś może występować jeden lub więcej razy. Jeszcze inaczej będą zachowywać się wszystkie nawiasy, które nie dość, że zaczną służyć do budowania bardziej skomplikowanych sformułowań, to w dodatku będą funkcjonować tylko w parach ze swoimi odpowiednikami otwierający-zamykający.

Prawdopodobnie wiele osób próbowało skorzystać z opcji RegEx, ale przekonawszy się, jak dziwnie i nieobliczalnie się ona zachowuje, przestało się nią interesować. W istocie jest to narzędzie dość trudne do opanowania, poszerza ono jednak ogromnie możliwości wyszukiwania i zamiany ciągów znaków. Wystarczy wspomnieć, że można wyszukać np. ciąg, w skład którego mogą wchodzić tylko wybrane znaki (np. dowolne pojedyncze słowo albo dowolna liczba), można znaleźć każdy inny znak niż zadeklarowana grupa znaków (np. każdy ciąg znaków pomiędzy kreskami, niezależnie od tego, czy jest to dywiz, minus czy półpauza) albo wyszukać alternatywę (np. dowolne z podanych słów: jabłko, gruszka lub śliwka).

Jednak nawet jeśli opanowanie całego zestawu możliwości RegEx, czyli tak naprawdę czegoś, co w świecie matematyki nazywa się wyrażeniami regularnymi, może okazać się trudne, to opanowanie kilku podstawowych funkcji jest w zasięgu możliwości każdego, a wtedy będzie można wydać edytorowi na przykład takie polecenie:

„Znajdź dowolną liczbę dokładnie czterocyfrową, po której może, ale nie musi być odstęp (np. spacja lub encja twardej spacji), a po tym jest któraś z kresek (np. dywiz, półpauza lub znak minusa), po czym znów może, ale nie musi być odstęp i na końcu znów jest liczba dokładnie czterocyfrowa. Teraz zamień to na ciąg: liczba, półpauza i druga liczba”.

Opisana powyżej operacja to popularna czynność poprawienia pisowni zakresów lat czterocyfrowych do standardowej postaci typu 1939–1945 (bez spacji, z półpauzą) i jest to czynność, którą wykonamy teraz bardzo szybko, w jednym przebiegu. Zrobienie tego bez omawianego narzędzia jest bardzo niewygodne, można co prawda próbować różnych pośrednich sztuczek, np. z użyciem po kolei każdej cyfry, ale będzie to trwać dłużej.

TerminologiaEdytuj

  • Regex (pisane różnie: RegExp, regexp, RegEx...) to angielski skrót wyrażenia regular expression, co na polski tłumaczy się jako „wyrażenie regularne”. Pierwotnie był to termin matematyczny, później trafił jako element wielu języków programowania. Istnieje też nazwa „grep” (global regular expression print) – jest to program w postaci polecenia podawanego w trybie znakowym, pierwotnie stworzonego dla Unixa, ale będącego obecnie w wielu innych implementacjach. Grep jest też nazwą takiej formy wyszukiwania w programie Adobe InDesign służącym do składu i łamania tekstu. Istnieje wiele odmian regexów i grepa, nieznacznie różniących się od siebie, ta stosowana w Wikipedii oparta jest na wersji z JavaScript (jest to jedna z najuboższych wersji regexów), a jej dokumentacja jest tutaj: https://developer.mozilla.org/pl/docs/Web/JavaScript/Referencje/Obiekty/RegExp. Poeksperymentować z gotowym zapytaniem można natomiast na stronie https://regex101.com/, wybierając z menu Flavor wersję Javascript.
  • Zapytanie to operacja zamiany, czyli wszystko to, co wpisujemy za jednym razem w polach Znajdź i Zamień.
  • Literał to znak rozumiany dosłownie, oznaczający sam siebie, czyli mający takie znaczenie jak w języku naturalnym; są to m.in. wszystkie litery i cyfry oraz niestety tylko niektóre znaki interpunkcyjne. Jeżeli napiszemy „Zosia” lub „4 wiadra”, to problemów z interpretacją tego przez program nie będzie. Jednak szereg innych znaków ma najczęściej znaczenie składniowe, czyli służące do formułowania poleceń, są to m.in.: ()[]{}*+? oraz znak kropki, ale tak naprawdę jest tego jeszcze więcej. Co gorsza, w różnych sytuacjach takie znaki funkcjonują w różny sposób. Ponadto w specyficznych sytuacjach niektóre inne niż wymienione powyżej znaki także nabierają znaczenia składniowego, czyli przestają być literałami tylko w ściśle określonych sekwencjach znaków. Zbiór wszystkich znaków, które częściej bądź rzadziej mogą nie być literałami, to w naszym przypadku:

   ()[]{}*+?!<=>\^.,:-|=$AaBbCcDdEeFfnSstuWwx0123456789,
   ...a dla utrudnienia można by tu jeszcze dorzucić składnię MediaWiki oraz składnię HTML wraz z jej encjami :-).

  • Metaznak – jest to dwuznak, czyli para znaków, z których pierwszy dookreśla drugi. Tym pierwszym jest znak odwrotnego ukośnika, czyli backslash. Po nim następuje jakiś inny, drugi znak, a cała para nabiera nowego znaczenia. Rozwiązanie to służy dwóm celom:
    • Dla pojedynczych znaków niebędących literałami nawraca je do znaczenia podstawowego, np. kropka to symbol dowolnego znaku prócz entera, natomiast zwykła kropka to \.; nawiasy, jak wspomniano, mają swoje różne specyficzne zastosowania, zaś wpisywanie ich jako zwykłe znaki odbywa się tak: \(, \), \[ itd...; pytajnik, aby stał się rzeczywiście pytajnikiem, podajemy w ten sposób \?.
    • Istnieją jednak również inne metaznaki – nie nawracające znaków do ich pierwotnego znaczenia, tylko wręcz odwrotnie, nadające nowe, np. litera „d” to po prostu litera, ale \d to metaznak oznaczający dowolną cyfrę, z kolei \n oznacza tzw. miękki enter, w składni wikipediowej funkcjonujący jako znak końca akapitu.
    • Sam backslash podajemy w znaczeniu podstawowym, jak można się domyślić, za pomocą dwóch backslashów \\.
    • Czasami jednak metaznaki mogą składać się z więcej niż dwóch znaków, końcówki takich dłuższych metaznaków zawierają dwie lub cztery cyfry zapisane w systemie szesnastkowym, np. \u200A. W ten sposób można wstawiać znaki, podając ich numer w Unikodzie (ang. Unicode).
    • Pełna lista metaznaków znajduje się w dziale Metaznaki i inne oznaczenia.

Początek pracy, czyli grupa przechwytującaEdytuj

Kolejnym krokiem, i chyba najistotniejszym dla zrozumienia funkcjonowania składni wyrażeń regularnych, jest poznanie roli, jaką pełni para okrągłych nawiasów.

Wyobraźmy sobie, że w polu Znajdź powiedzieliśmy programowi coś takiego: \d.

Wpisanie tego metaznaku w polu Znajdź jest rozumiane jako dowolna cyfra, nie możemy jednak tego samego zrobić w polu Zamień, bo co prawda komputer jest w stanie znaleźć dowolną cyfrę, ale nie może wstawić dowolnej, wymyślonej przez siebie cyfry, bo nie wie, czego my oczekujemy od programu. W związku z tym musimy zastosować coś, co nazywa się grupą przechwytującą (ang. capturing group). Działa to w taki sposób, że każdą grupę przechwytującą obejmujemy w polu Znajdź parą nawiasów okrągłych, a w polu Zamień podajemy jej odpowiednik w postaci symbolu dolara z kolejnym jednocyfrowym numerem. Działanie tego rozwiązania można opisać jako „wstaw to, co znaleziono”.

W najprostszym użyciu będzie to wyglądało tak:

  • Znajdź: (\d)
  • Zamień: $1

Jeśli pomiędzy kilkoma cyframi chcemy poprawić przecinki na kropki, zrobimy to tak:

  • Znajdź: (\d),(\d),(\d)
  • Zamień: $1.$2.$3

Jeśli zaś będziemy chcieli zamienić kropki na przecinki (a wiemy, że sama kropka ma inne znaczenie – dowolnego znaku prócz końca akapitu), to wpiszemy kropkę metaznakiem. A więc:

  • Znajdź: (\d)\.(\d)\.(\d)
  • Zamień: $1,$2,$3

Oczywiście w nawiasach można podawać więcej niż jedną rzecz, w poniższym przykładzie są to 4 metaznaki dowolnej cyfry:

  • Znajdź: (\d\d\d\d)-(\d\d\d\d)
  • Zamień: $1–$2

Jak widać, pomiędzy liczbami czterocyfrowymi zmieniliśmy dywizy na półpauzy. Nota bene zapis \d\d\d\d można przedstawić też w postaci \d{4} (opisane dalej).

Kolejność „dolarowania” możemy zmieniać, np.:

  • Znajdź: rok (\d\d\d\d) był przed rokiem (\d\d\d\d)
  • Zamień: rok $2 był po roku $1

Jak widać, „dolary” z pola Zamień nie są sztywno przypisane do miejsc swoich pierwowzorów z pola Znajdź. Łatwo się zatem domyślić, że skoro owe „dolary” można przestawiać miejscami, to można z nimi robić także wiele podobnych rzeczy, czyli przesuwać w inne miejsca, używać tych samych „dolarów” wiele razy w różnych miejscach (czyli powielać zawartość nawiasu), a nawet... w ogóle ich nie wstawiać (czyli znalezione rzeczy usuwać). Aczkolwiek..., à propos tego ostatniego, jeśli jakiegoś fragmentu nie „podolarujemy” w ogóle, to też się on skasuje.

Takich różnych „dolarów” (o różnych numerach) możemy wykorzystać maksymalnie 9, czyli od $1 do $9. Zapis np. $10 zostanie przez program zinterpretowany jako $1, po którym będzie zwykła cyfra „0”.

Co prawda w wyrażeniach regularnych istnieje również $0, które oznacza wszystko z pola Znajdź (niezależnie od istnienia tam, bądź nie, nawiasów), ale w wikipediowej wersji nie jest zaimplementowane.

Zagnieżdżanie grupEdytuj

Grupy przechwytujące są numerowane kolejnością występowania ich lewych znaków nawiasu. Informacja ta ma znaczenie, ponieważ można budować wielopiętrowe struktury typu nawias w nawiasie (czyli grupa w grupie).

  • (1...(2...)...(3...)...)...(4...) – 2. i 3. grupa są samodzielnymi grupami (jako $2 i $3), ale jednocześnie są podgrupami II poziomu wchodzącymi w skład grupy 1., stanowiąc wraz z pozostałymi fragmentami tej grupy wspólne $1. Jak widać, łatwo się pogubić, bo druga grupa I poziomu to dopiero $4. W przykładzie znalazł się nawet fragment zapytania nienależący do żadnej z grup (to ten bezpośrednio przed 4.).
  • (1(2...)(3...))(4...) – inny przykład omawiający to samo zagadnienie – tu grupy 2. i 3. są jedynymi składnikami grupy 1.
  • (1(2(3...)(4...))(5...)) – tu całość powyższego przykładu objęliśmy kolejnym nawiasem. Numeracja skoczyła o 1. I poziom – nowa grupa 1.; II poziom – grupy 2. i 5.; III poziom – grupy 3. i 4.

Jeżeli jakaś grupa jest zagnieżdżona, to nie musimy jej dodatkowo „dolarować”, bo ona jest już „podolarowana” jako fragment grupy obejmującej.

  • (1...(2...)...) – całość wraz z grupą zagnieżdżoną jest widziana przez program jako $1. Jeżeli dodatkowo gdzieś w zapytaniu użyjemy $2, to po prostu skopiujemy ten fragment tekstu drugi raz, a jeżeli użyjemy tylko $2 (bez $1), to tekst z grupy obejmującej zostanie skasowany, ale nie cały, bo z $2 zostanie zachowany.

Drobna uwaga: użyte tu określenia takie jak ‘grupa obejmująca’, ‘grupa zagnieżdżona’ czy ‘grupa któregoś poziomu’ nie są fachowymi terminami z wyrażeń regularnych, tylko zwykłymi określeniami z języka ogólnego opisującymi charakter danych grup.

Odwołanie wsteczneEdytuj

Ciekawym rozwiązaniem jest tzw. odwołanie wsteczne (ang. back-reference), czyli możliwość znajdowania cząstkowych wyników szukania jeszcze wewnątrz pola Znajdź (czyli znajdowania jakby powtórzeń). W tym celu stosujemy metaznaki od \1 do \9, w których cyfra jest numerem znajdującej się wcześniej w tym wyrażeniu grupy przechwytującej. Na przykład możemy znaleźć w akapicie drugi identyczny numer roku:

  • (\d\d\d\d).+?\1 – podany tu nawias zawierający 4 dowolne cyfry jest grupą nr 1, potem mamy nieznany jeszcze dla nas fragment .+? (oznaczający dowolny ciąg znaków) i na końcu \1 powtarzające nie grupę nr 1 jako taką, tylko konkretną zawartość w tej grupie znalezioną. W ten sposób zostanie zaznaczony ciąg znaków od pierwszego do drugiego wystąpienia tego samego roku. Można tak wyszukiwać np. powtórzone wikilinki czy powtórzone odsyłacze do tych samych przypisów.
  • \1.+?(\d\d\d\d) – tak nie można, odwołanie nie może poprzedzać grupy, do której się odwołuje.

Inne grupyEdytuj

Tak naprawdę nawiasy służą nie tylko do budowania grup przechwytujących, ale w ogóle do wydzielania fragmentów zapytania w celach organizacyjnych. Grupy przechwytujące są tutaj najważniejsze, bo to one decydują o podstawowej sile wyrażeń regularnych, ale są i inne grupy.

Grupa nieprzechwytującaEdytuj

Nie każda grupa musi być przechwytująca, to znaczy nie każdą grupę będziemy potrzebowali wstawić w polu Zamień (za pomocą odpowiednio numerowanego „dolara”). Grupy, czyli wydzielanie fragmentów zapytania, mogą służyć do wielu innych rzeczy, np. możemy powiedzieć, ile razy ma się dana grupa powtórzyć, albo że taka grupa może być, ale nie musi, albo też możemy powiedzieć coś o alternatywie, czyli wyborze pomiędzy różnymi rzeczami, a wtedy te rzeczy do wyboru też musimy jakoś zgrupować. Ponadto możemy sobie coś wydzielić w kodzie tylko po to, by to lepiej widzieć. Wszystkie takie „zwykłe grupy” nazywa się grupami nieprzechwytującymi (ang. non-capturing group), a my, na nasze potrzeby, możemy je nazwać potocznie grupami „niedolarowanymi”.

Przy bardziej skomplikowanych zapytaniach możemy bardzo szybko osiągnąć limit omówionych wcześniej 9 grup przechwytujących. Wtedy możemy niektóre nawiasy (czyli grupy), oczywiście te, do których nie będziemy potrzebowali się odwoływać w polu Zamień, wyłączyć z „numerowania w dolarach”, stosując grupę nieprzechwytującą: (?:...). Na przykład:

  •   (do usunięcia)(zostaje) – tu oba nawiasy są numerowane odpowiednio $1 i $2,
  • (?:do usunięcia)(zostaje) – tu pierwszy nawias wypadł z numerowania, skutkiem czego drugi zmienił się w $1.

Powyższy przykład jest oczywiście banalny, ale w bardziej skomplikowanych wyrażeniach jest to jedyne wyjście, o ile nie chcemy wykonywać jakiegoś zadania w kilku zapytaniach. Grup nieprzechwytujących można użyć dowolną liczbę. Jednak nawet w prostszych zapytaniach możemy sobie w ten sposób powyłączać niektóre grupy z numerowania, aby mieć czystszy, elegancki, a przy okazji łatwiejszy do liczenia kod (np. kolejno $1$2$3, zamiast jakiegoś mylącego $2$5$9). Zresztą dobrą praktyką podczas pisania zapytania jest od razu numerowanie tylko niezbędnych grup, co bardzo przydaje się przy późniejszej analizie i wykrywaniu błędów, nawet w stosunkowo prostych zapytaniach.

Pod koniec poprzedniego rozdziału omówiono odwołanie wsteczne, czyli metaznaki \1 do \9, które umożliwiają powtórzenie zawartości grupy przechwytującej jeszcze w polu Znajdź. Metaznaki te liczą tylko grupy przechwytujące, a więc działają tylko dla (...), dla (?:...) zaś nie.

Grupy nieprzechwytujące można oczywiście zagnieżdżać tak samo jak przechwytujące, a także mieszać z nimi, np. grupa nieprzechwytująca w przechwytującej lub na odwrót.

Grupa przewidywaniaEdytuj

Grupy można podawać również w taki szczególny sposób, że nie tylko nie będą numerowane, ale nawet nie będą zaznaczać tekstu, bo zaznaczenie będzie się urywać dokładnie przed nimi. Działanie to można określić jako „znajdź przed tą grupą” albo „uwzględnij obecność tej grupy po końcu wyszukiwania”. Angielska nazwa (zostanie omówiona trochę niżej) bywa różnie tłumaczona na język polski, np. jako „wybieganie naprzód”, „dopasowanie do przodu” albo „przewidywanie”. Działa to w ten sposób, że w polu Znajdź podajemy grupę tego typu na końcu, po wszystkich innych poleceniach, a w polu Zamień w ogóle o niej nie wspominamy, a mimo to program będzie o niej pamiętał. Wykorzystanie przewidywania jest różnorodne, można nim niejako rozciągnąć limit 9 „dolarów”, uwzględniając jeszcze jedną grupę w wyszukiwaniu, można w niewielkim stopniu skrócić, czy też uprościć składnię zapytania poprzez nieobciążanie zawartości pola Zamień (tak samo jak grupy nieprzechwytujące), jednak przede wszystkim rozwiązanie to jest wykorzystywane w przypadku nakładania się na siebie dwóch wyników wyszukiwania, a takie coś zachodzi np. na styku akapitów.

Wyobraźmy sobie, że chcemy przeszukać dokładnie cały akapit (od początku do końca). Niestety jedyna metoda, która w wikipediowej składni to umożliwia, polega na zaznaczeniu rzeczywiście całego akapitu wraz z jego końcem, czyli miękkim enterem, ale dla zaznaczenia początku akapitu musimy się posiłkować zaznaczeniem... miękkiego entera z końca poprzedniego akapitu.

Otóż artykuły na Wikipedii są skonstruowane w taki sposób, że kod całego artykułu jest jakby jednym wielkim akapitem, a miękkie entery (ang. line feed, zwane także line break, U+000A) – \n, stosowane w dodatku najczęściej podwójnie, dzielą ten akapit na jakby wirtualne akapity, które widzimy w oknie edycji. Z tego powodu znaki wyrażeń regularnych ^ i $ są mało przydatne, bo zamiast oznaczać miejsca graniczne akapitu, oznaczają tylko początek i koniec kodu całego artykułu. W związku z tym, jeśli chcemy powiedzieć programowi, żeby uwzględnił cały akapit, to musimy podać oba miękkie entery (co w praktyce oznacza zaznaczenie o jeden znak więcej od akapitu).

W przypadku zwykłego akapitu tekstu widzianego przez czytelnika mamy przed nim i po nim w kodzie artykułu po dwa takie entery, więc problemu nie będzie, ale w wielu miejscach kodu, takich jak nagłówki, wyliczenia, wnętrza szablonów czy tabel itp. pomiędzy akapitami jest tylko jeden, niejako wspólny miękki enter. Jeżeli akapit tego typu chcemy zaznaczyć w całości, to musimy to zrobić od jednego do drugiego entera, gdzie pierwszy jest de facto ostatnim znakiem poprzedniego akapitu, a drugi – ostatnim bieżącego. Jeżeli teraz zastosujemy konstrukcję \n...\n, to program zaznaczy nie tylko bieżący akapit, ale również ostatni znak poprzedniego. Nietrudno się domyślić, że tak skonstruowane zapytanie będzie znajdowało co drugi akapit. Jeżeli teraz będziemy się spodziewać, że jakaś operacja zamiany tekstu może się odbyć w dwóch sąsiednich akapitach, to albo będzie trzeba wykonać zapytanie dwa razy, albo, przy wyjątkowo niesprzyjających okolicznościach, w ogóle nie da się tego zrobić, bo co drugi akapit będzie stale pomijany. Wtedy z pomocą przychodzi właśnie grupa przewidywania, która niby nie widzi, a jednak uwzględnia końcówkę zapytania, przez co drugi enter możemy „na chwilę schować”. Robi się to tak:

  • (\n...)(?=\n) – mamy tu tylko jeden „dolar”, a końcówka, czyli w tym wypadku drugi miękki enter, nie została zaznaczona, choć została zobaczona i uwzględniona w wyszukiwaniu.

Na przykład wikipediowe tytuły rozdziałów tworzy się, wstawiając na początku i końcu akapitu co najmniej dwa znaki „=”. Bywa jednak, że obok siebie w sąsiednich akapitach mamy tytuł rozdziału i od razu podrozdziału, a wtedy grupy przechwytujące (lub nieprzechwytujące) nie wystarczą:

== Rozdział 1 == ← tu jest tylko jeden miękki enter
=== Podrozdział 1.1 ===

W przykładzie powyżej pomiędzy oboma nagłówkami w kodzie artykułu jest tylko jeden miękki enter. Jak w takim razie powiedzieć programowi, żeby zobaczył w całości najpierw jeden nagłówek, a potem drugi?

Poniższe rozwiązanie jest złe:

  • (\n)(==+)(treść nagłówka)(==+)(\n) – w polu Zamień odpowiednikami są $1, $2, $3, $4 i $5, ale ten ostatni jest nam potrzebny do opisania następnego nagłówka.

Poniższe rozwiązanie jest dobre:

  • (\n)(==+)(treść nagłówka)(==+)(?=\n) – na swoich miejscach zostały $1, $2, $3 i $4, ale drugi miękki enter nie jest już $5, tylko został umieszczony w grupie przewidywania, przez co znak ten pozostaje do wykorzystania jako $1 na początku drugiego akapitu (czyli następnego nagłówka).

Rodzaje grup przewidywaniaEdytuj

Ogólnie w wyrażeniach regularnych występują cztery rodzaje grup przewidywania, z których dwie są stosowane na początku poleceń i działają do tyłu, a dwie są na końcu i działają w przód, a ponadto każda z nich może mówić albo o szukaniu czegoś, albo szukaniu jego przeciwieństwa. W javascriptowej (a więc i wikipediowej) wersji regexów zaimplementowane są tylko dwie:

  • ...(?=...) – przewidywanie pozytywne do przodu, uwzględnij to, co podano potem (ang. positive lookahead),
  • ...(?!...) – przewidywanie negatywne do przodu, uwzględnij to, czego nie podano potem (ang. negative lookahead),

a tych nie ma:

  • (?<=...)... – przewidywanie pozytywne do tyłu, uwzględnij to, co podano przedtem (ang. positive lookbehind),
  • (?<!...)... – przewidywanie negatywne do tyłu, uwzględnij to, czego nie podano przedtem (ang. negative lookbehind).

O przewidywaniu negatywnym do przodu lepiej zbyt wiele w tym poradniku nie pisać. W ramach grupy tego typu łatwo da się wyszukiwać jedynie pojedynczy znak lub klasę, ale to samo można osiągnąć z wykorzystaniem grupy przewidywania pozytywnego, wstawiając zaprzeczenie (opisane dalej), np.:

  • (?!a) = (?=[^a])

Co prawda, w przewidywaniu negatywnym można podawać również bardziej skomplikowane rzeczy, czyli dłuższe wyrażenia, ale program zadziała w sposób oczywisty i konsekwentny tylko dla naprawdę doświadczonego programisty, natomiast dla osoby mniej doświadczonej zachowanie tej grupy jest całkowicie nieprzewidywalne.

I druga uwaga: powyżej podano, że grupy przewidywania wstawia się na końcu pola Znajdź. W rzeczywistości da się to zrobić też w innym miejscu tego pola, nawet wiele razy, ale uwaga taka sama jak powyżej – lepiej nie próbować, bo działanie jest bardzo skomplikowane, nieintuicyjne i trudne do opanowania.

Podsumujmy sposoby użycia grup:

  • (...) – grupa przechwytująca (tylko ta daje odpowiedniki w „dolarach”) – limit 9,
  • (?:...) – grupa nieprzechwytująca – bez limitu,
  • (?=...) – grupa przewidywania pozytywnego do przodu – zaleca się tylko raz na końcu

oraz

  • (?!...) – negatywny odpowiednik powyższej – używać ostrożnie lub wcale.

PowtórzenieEdytuj

O każdej rzeczy możemy powiedzieć, ile razy ma się powtórzyć:

  • {n} – jest n razy, np. a{2} (dwie litery „a”) albo 0{1000} (tysiąc zer),
  • {n,} – jest co najmniej n razy,
  • {,n} – w tę stronę się nie da, nie ma polecenia „co najwyżej”, ale można napisać {0,n},
  • {n,m} – jest od n do m razy.

Należy jednak pamiętać, że bezpośrednio po znalezionej konkretnej liczbie rzeczy może ich być jeszcze więcej, chyba że temu specjalnie zaprzeczymy (opisane dalej).

KwantyfikatorEdytuj

Można również użyć określeń bardziej ogólnych zwanych kwantyfikatorami:

  • * – może być 0 lub więcej razy, czyli jest, ile chce, lub nie ma wcale – odpowiednik {0,},
  • ? – może być 0 lub jeden raz, czyli jest lub nie ma, może być, ale nie musi – odpowiednik {0,1},
  • + – musi być co najmniej raz, ale może być więcej – odpowiednik {1,}.

Polecenia *, ? i + są bardzo często używane, ale trzeba uważać, bo w pewnych sytuacjach mogą znaleźć więcej, niż chcemy. Na przykład w ciągu znaków „abc (def) ghi (jkl) łmn (opr) stu” sformułowanie \(.+\) znajdzie ciąg „(def) ghi (jkl) łmn (opr)”, czyli przeleci przez wszystkie prawe nawiasy i zatrzyma się dopiero na ostatnim. Dzieje się tak dlatego, że kropka oznacza tu każdy znak inny niż „enter”, a więc oznacza również prawy nawias. Aby zaznaczenie dotarło tylko do najbliższego prawego nawiasu, musimy zastosować kwantyfikator z dołożonym pytajnikiem, czyli napisać \(.+?\). Wtedy program znajdzie najpierw „(def)”, w kolejnym kroku „(jkl)” i wreszcie „(opr)”.

Tak więc mamy nie 3, a 6 kwantyfikatorów:

  • *, ? i + – dochodzi do ostatniego napotkanego (tzw. kwantyfikator zachłanny, ang. greedy quantifier),
  • *?, ?? i +? – dochodzi tylko do najbliższego (tzw. kwantyfikator leniwy, ang. lazy quantifier).

Różnicę pomiędzy oboma rodzajami kwantyfikatorów możemy też zrozumieć, obserwując działanie np. lata? i lata??. Wyszukajmy tekst zawierający m.in. wyrazy „lat”, „lata”, „latami” itp. Leniwy kwantyfikator zainteresuje się tylko pierwszymi 3 znakami każdego z podanych wyrazów, a zachłanny znajdzie wszystkie 4.

Jeszcze wyraźniej odczujemy różnicę w zapytaniach: lat.* i lat.*?

Powielanie grupEdytuj

Powielać w polu Znajdź można nie tylko pojedyncze znaki, ale także całe grupy. Trzeba jednak uważać, aby nie popełnić błędu.

  • (...)+ – tak powiedzieliśmy programowi, żeby znalazł wszystkie powtórzenia grupy, ale $1 wstawi tę grupę tylko raz.
  • ((?:...)+) – dopiero gdy całość obejmiemy kolejną grupą, nowy $1 wstawi szukaną grupę tyle razy, ile ją znalazł (stary $1 zmienił się w grupę nieprzechwytującą).

Klasa znakówEdytuj

Klasa to zbiór znaków do wyboru. Należy ją rozumieć jako „któryś ze zbioru”. Jednak pod pojęciem znaku rozumie się tutaj nie tylko literały, ale także metaznaki mówiące o literałach.

  • [abc] – któryś ze znaków „a”, „b” lub „c”, a także „A”, „B” lub „C”, o ile nie zaznaczyliśmy opcji rozróżniania wielkich liter.
  • [abc]{2} – znajdowane będą wszelkie kombinacje, a więc: „ab”, „ba”, „ac”..., ale także „aa”, „bb” lub „cc”. Przy niezaznaczonym uwzględnianiu wielkości liter ilość kombinacji oczywiście wzrasta.
  • [-–—] – tu wpisano szukanie dywizu, półpauzy lub pauzy. Ważne, by dywiz był na początku lub na końcu.
  • [0-9] – to jest odpowiednik \d, istotna jest kolejność cyfr od zera do dziewiątki, bo tak narastają numery tych znaków w Unikodzie. Dywiz użyty wewnątrz wyrażenia oznacza zakres od–do.
  • [a-f\d] – litery od a do f lub dowolna cyfra.
  • [a-ząćęłńóśźż] – wszystkie polskie znaki, tu kolejność dowolna, byle zakresy podane dywizami były od a do z – małe i wielkie litery zostaną uwzględnione, jeśli przycisk „uwzględniaj wielkość liter” będzie wyłączony.
  • [A-Za-zĄĆĘŁŃÓŚŹŻąćęłńóśźż] – jeśli wspomniany przycisk jest włączony, trzeba podawać i małe, i wielkie litery (jeśli tego potrzebujemy).
  • [,;:.?!…] – wybór znaków interpunkcyjnych, a wśród nich kropka i pytajnik, które normalnie (poza klasą) literałami nie są, jednak składnia klasy jest tak jasna i oczywista, że backslash nie jest konieczny dla zrozumienia zapisu przez program dla tego typu znaków w tym miejscu, to znaczy, jak ktoś chce, może kropkę i pytajnik poprzedzić backslashami – nic się nie zmieni, zresztą gdyby tu były gwiazdka lub plus, to też dla programu byłyby zrozumiałe same.
  • [()[\]{}] – któryś z nawiasów okrągłych, kwadratowych lub klamer. Jak widać, tu również zasadniczo nie potrzeba backslashów. Nawet lewy nawias kwadratowy jest dla programu zrozumiały bez backslasha, bo jego wcześniejszy brat otwiera klasę, tylko prawy nawias kwadratowy musi być poprzedzony backslashem, bo inaczej program przedwcześnie zamknie klasę.
  • [|\\] – znajdzie pionową kreskę lub pojedynczy backslash. Pionowa kreska normalnie służy do tworzenia alternatywy (opisane dalej), więc dla podstawowego znaczenia wymaga zazwyczaj poprzedzającego backslasha, ale klasa to jedna wielka alternatywa, więc w klasie nawet pionowa kreska jest zapisywana jako literał, czyli sama. Z backslashem jest już inaczej, a przy okazji skomplikowanie – dla podstawowego znaczenia wymaga drugiego backslasha nawet wewnątrz klasy. Natomiast gdy postawimy go samego, to w zależności od sąsiedniego znaku, albo stworzy z nim metaznak (o ile taki istnieje), albo program go nie zauważy.
  • [\u00AD] lub [\xAD] – znaki można wstawiać także w postaci numeru Unikodu (opisane dalej)...
  • [-\n\t\da-fxyz\u0381-\u03C9] – jak widać, w klasie można mieszać literały z metaznakami, zakresami i numerami Unikodu. Przykład zawiera m.in. literał dywizu (oczywiście w odpowiednim miejscu) oraz zakres numerów Unikodu.
  • [&nbsp;], [&#160;] lub [&#xa0;] – encji w żadnej postaci wstawiać nie można, program rozpozna to jako zbiór oddzielnych znaków.
  • [^1a] – uwaga na daszek! Daszek postawiony na początku klasy oznacza jej zaprzeczenie (opisane dalej). Aby daszek umieścić w klasie, musi być na dowolnym innym miejscu niż pierwsze, np. [1^a] lub [1a^],
  • [jakieś słowo] – któryś z podanych znaków: a, e, j, i, k, ł, o, s, ś, w lub spacja; w zapisie jest niewielki błąd, bo niepotrzebnie litera „o” występuje 2 razy (bez wpływu na działanie), jednak przede wszystkim niepotrzebnie napisano te znaki w postaci wyrażenia z języka naturalnego, które przypomina 2 wyrazy, co może być mylące, bo w klasie szukamy pojedynczych znaków, lepiej byłoby te znaki uporządkować, czyli napisać tak: [ aeijkłosśw].

Podsumujmy. Wewnątrz klas piszemy:

  • bez backslasha (jako literały) co tylko się da, a więc prawie wszystko, w tym „trudne znaki”:
    • .*?+$()[{}| – w dowolnym miejscu,
    • - – tylko na początku lub końcu, inaczej zbudujemy zakres,
    • ^ – nie na początku,
  • z backslashem tylko wtedy, gdy jest to konieczne, a więc:
    • \b\d\n\t\w\]\\ oraz \unnnn i \xnn.

Klasy można powielać, np. [...]+, [...]?? czy [...]{5}.

Klasy mogą wchodzić w skład wszystkich grup, np. ([...]) czy (?=[...]).

Klasa zaprzeczonaEdytuj

Zasady identyczne jak dla zwykłych klas. Wystarczy po otwierającym nawiasie wstawić daszek:

  • [^\d] – każdy znak, byle nie cyfra,
  • [^-\d] – każdy znak, byle nie dywiz lub cyfra,
  • [^1-4] – każdy znak, byle nie 1, 2, 3 lub 4,
  • [^-1-4] – każdy znak, byle nie dywiz, 1, 2, 3 lub 4,
  • [^1-4-] – to samo co powyżej, bo szukany dywiz można wstawić na początku lub końcu,
  • [^aąeioóuy] – każdy znak, byle nie samogłoska,
  • [^,;:.!?…] – każdy znak, byle nie znak przestankowy (na końcu podano wielokropek, jednak trzech oddzielnych kropek tak się nie da),
  • [^\n ] – każdy znak, byle nie koniec akapitu lub spacja.

Jak łatwo się domyślić, . jest odpowiednikiem [^\n].

Jeżeli chcemy zaprzeczyć daszek, nie stanowi to problemu, wpisujemy go w dowolnym innym miejscu, byle nie pierwszym, np. [^^] lub [^!@#$%^&*()], a nawet na końcu [^!@#$%&*()^].

AlternatywaEdytuj

W opisanych powyżej klasach można podawać tylko wybór pojedynczych znaków. Natomiast jeżeli chcemy podać wybór dłuższych wyrażeń, to stosujemy pionową kreskę oznaczającą alternatywę, a całość najprawdopodobniej będziemy musieli objąć nawiasami:

  • jabłko|gruszka|śliwka – szukamy któregoś z tych słów, do zbudowania alternatywy wystarczy użycie samych pionowych kresek,
  • (jabłko|gruszka|śliwka) – to samo, ale objęte nawiasami, czyli alternatywa w postaci grupy przechwytującej, gdyż zazwyczaj alternatywy będziemy używać w ramach jakiegoś dłuższego zapytania,
  • ( |&nbsp;) – szukamy spacji lub encji twardej spacji (obecność encji spowodowała, że zamiast klasy użyliśmy alternatywy),
  • (…|\.\.\.) – tak samo w przypadku znaku wielokropka lub trzech oddzielnych kropek (pojedynczej kropki program tu nie zauważy), kropki poza klasą piszemy oczywiście jako metaznaki,
  • (…|\.{3}) – alternatywny zapis powyższego, z wykorzystaniem liczby powtórzeń.

Alternatywa, podobnie jak grupa, jest bardzo prostą konstrukcją składniową, a przez to bardzo pojemną, co oznacza, że możemy jej używać do przedstawiania wyboru dowolnych rzeczy, w tym grup, klas, a nawet kolejnych alternatyw:

  • ([\n ]|&nbsp;) – przykład grupy przechwytującej w postaci alternatywy zawierającej m.in. klasę,
  • ([,;:.!?…]|\.{3}) – któryś ze znaków przestankowych (dla wygody zebranych w klasie) lub trzy kropki,
  • (,|;|:|\.|!|\?|…|\.{3}) – aczkolwiek, jak ktoś chce się męczyć, to może powyższe zapisać bez klasy (wtedy kropka i pytajnik jako metaznaki),
  • ((?:jeden|dwa|trzy) to cyfra|(?:iks|ygrek|zet) to litera) – rozbudowany przykład kaskadowego (wielopoziomowego) użycia różnych grup i alternatyw.

Niestety, w odróżnieniu od klas, w alternatywach możemy wyszukiwać tylko to, co podaliśmy, nie da się wyszukiwać przeciwieństwa alternatywy, nie da się alternatywy zaprzeczyć. Zaprzeczyć można tylko klasę (czyli wybór pojedynczych znaków, a nie wybór ciągów znaków). Poniższe przykłady są błędami:

  • [^jabłko|gruszka|śliwka]
  • (^jabłko|gruszka|śliwka)

Jedyną metodą podobną do zaprzeczenia alternatywy jest tzw. przewidywanie negatywne do przodu (opisane wcześniej), ale jego działanie jest tak nietypowe i skomplikowane, że lepiej go nie używać.

W przypadku alternatyw istnieje pewna niezręczność, polegająca na tym, że program czyta zapytanie po kolei i interpretuje to, co widzi w danym momencie, przez co może pominąć fragment znajdujący się dalej. Jeżeli będziemy np. szukać:

  • lat|lata|latach|latami|latom

to program znajdzie we wszystkich szukanych słowach tylko ciąg „lat”, bo wszystkie te wyrazy zaczynają się tak samo jak pierwszy z wyszukiwanych. Zresztą podobnie byłoby z ciągiem „lata” w słowach „lata”, „latach” i „latami”. Zjawisko to na pewno utrudni nam życie, jeśli będziemy szukać tylko alternatywy. Na szczęście zazwyczaj alternatywa jest częścią większego zapytania, gdzie jest jeszcze coś po alternatywie, przez co taka konstrukcja spowoduje, że wczytane zostaną w całości wszystkie elementy alternatywy, ale pewności nie ma. Może się zdarzyć na tyle niekorzystna budowa zapytania, że błąd ujawni się nieoczekiwanie nawet w bardzo skomplikowanej konstrukcji. Dlatego warto zadbać, aby elementy alternatywy wymienić w przemyślanej kolejności gwarantującej uniknięcie nieporozumień.

Można np. ułożyć elementy alternatywy w kolejności od najdłuższego do najkrótszego. W podanym przykładzie najpierw są wymienione 6-znakowe „latach” i „latami”, następnie 5-znakowe „latom”, po czym kolejno „lata” i „lat”:

  • latach|latami|latom|lata|lat

Można też zastosować inną metodę – sortowania alfabetycznego, ale... od końca. Wtedy program też zadziała poprawnie, mimo że słowa nie będą od najdłuższego do najkrótszego:

  • latom|latami|latach|lata|lat

I na koniec drobna uwaga: jak łatwo się domyślić, a|b|c|d|e jest odpowiednikiem [abcde], jak również [a-e].

LokalizacjaEdytuj

Istnieje kilka znaków określających miejsce położenia danego ciągu znaków w pliku. Są to:

  • ^ – początek pliku, czyli początek kodu artykułu (stawia się go na początku zapytania),
  • $ – koniec (analogicznie),
  • \b – miejsce granicy słowa (początek lub koniec); jako słowo rozumie się ciąg znaków składający się z liter łacińskich (bez znaków diakrytycznych), cyfr i podkreślnika (czyli klasa [A-Za-z\d_]); metaznak ma ograniczone zastosowanie, bo nie uwzględnia znaków diakrytycznych ani dywizu.

Istotne jest to, że są to znaki mówiące o położeniu (a nie zawartości), w związku z tym umieszcza się je tylko w polu Znajdź. Możemy myśleć o nich jako o markerach.

Metaznaki i inne oznaczeniaEdytuj

\ służy do budowania metaznaków
\\ znak backslasha, czyli pojedynczy \
. dowolny znak wewnątrz akapitu (czyli inny niż „enter”)
\. znak kropki
| alternatywa
\| znak pionowej kreski, tzw. pipe, czyli |
^ miejsce początku pliku
\^ znak daszka
$ miejsce końca pliku (w polu Znajdź)
$1...$9 wywoływanie grup przechwytujących (w polu Zamień)
\$ znak dolara (w polu Zamień może być konieczne wpisanie znaku kodem: \x24 lub \u0024)
\b miejsce granicy słowa (początek lub koniec); jako znaki słowa rozumiana jest klasa [A-Za-z\d_] (ten sam zestaw znaków uwzględnia metaznak \w)
\n niewidoczny znak „entera” stojący na końcu każdego akapitu, także pustego
\t znak tabulatora (w kodzie artykułu raczej nie stosujemy tabulatorów, zastępując je spacjami)
( ) [ ] { } elementy składniowe różnych poleceń
\( \) \[ \] \{ \} odpowiednio znaki: ( ) [ ] { }
\1...\9 odwołania wsteczne w polu Znajdź
* + ? polecenia powtórzeń: zachłanne kwantyfikatory
*? +? ?? polecenia powtórzeń: leniwe kwantyfikatory
\* \+ \? odpowiednio znaki: * + ?
\d dowolna cyfra arabska
\s dowolny tzw. znak biały (w kodzie wiki są to: tabulator, spacja lub „enter”)
\w dowolny znak alfanumeryczny w rozumieniu: wielka litera łacińska lub mała litera łacińska, lub cyfra, lub znak podkreślnika, czyli [A-Za-z\d_], nie ma tu miejsca na znaki diakrytyczne, nie ma więc Ą, ł, Ç czy ě (ten sam zestaw znaków uwzględnia metaznak \b)
\unnnn tak można podawać znaki w Unikodzie kodem szesnastkowym pełnym – czterocyfrowym, np. \u200B („cyfry” A–F można oczywiście pisać wielką lub małą literą)
\xnn tak można podawać znaki w Unikodzie kodem szesnastkowym skrótowym – dwucyfrowym, ale tylko gdy pierwsze dwie cyfry kodu czterocyfrowego są zerami, np. \u00AD = \xAD

Uwaga: metaznaki \b, \d, \s i \w można zaprzeczać, pisząc je wielką literą, np. \D oznacza każdy inny znak (w tym „enter”) niż cyfra.

PrzykładyEdytuj

Wyrażenia cząstkoweEdytuj

Liczby cyframi rzymskimi

  • [IVX]+ – liczba zapisywana jedną lub kilkoma cyframi rzymskimi (I, V i X).
  • [CDILMVX]+ – tu rozszerzono o zapis dla większych wartości (czyli o rzymskie cyfry C, D, L i M).

Taki zapis będzie jednak wyszukiwać również litery w zwykłych słowach, więc w poniższym przykładzie dodano typowe znaki mogące stać po bokach liczby zapisanej cyframi rzymskimi (i utworzono 3 grupy):

  • ([\n ([{]|&nbsp;)([IVX]+)([\n .,;:!?)\]}]|&nbsp;)

Liczby cyframi arabskimi

  • \d+ – liczba składająca się z jednej lub więcej cyfr.
  • \d+,\d+ – liczba z ułamkiem dziesiętnym po przecinku.
  • \d+\.\d+ – liczba z ułamkiem dziesiętnym po kropce (np. w ortografii anglosaskiej).
  • \d+[,.]\d+ – obie powyższe możliwości naraz.
  • \d+([,.]\d+)? – liczba bez lub z ułamkiem dziesiętnym i jednym z obu separatorów.

Kreski na średniej linii pisma najczęściej spotykane w Unikodzie

  • [-–—÷‒―−] – szuka wielu różnych kresek występujących w Unikodzie, dywiz oczywiście na jednym z końców, kolejność reszty dowolna. Tu są kolejno: dywiz, półpauza, pauza, division sign (U+00F7), figure dash (U+2012), horizontal bar (U+2015), minus sign (U+2212).

Składnia wiki dla konkretnego hasła artykułu

  • \[\[hasło\]\] lub \[{2}hasło\]{2} – znaczy wikilink typu [[hasło]].
  • \[\[hasło\|hasła\]\] lub \[{2}hasło\|hasła\]{2} – znaczy wikilink typu [[hasło|hasła]].

Składnia wiki dla dowolnego hasła artykułu

  • \[\[[^|]+?\]\] lub \[{2}[^|]+?\]{2} – znaczy wikilink typu [[...]].
  • \[\[.+?\|.+?\]\] lub \[{2}.+?\|.+?\]{2} – znaczy wikilink typu [[...|...]].
  • \[\[.+?\]\] lub \[{2}.+?\]{2} – znaczy wikilink typu [[...]] lub [[...|...]].

Powyższe 3 przykłady są proste i działają niemal zawsze, jednak w przypadku szczególnie skomplikowanego kodu artykułu mogą wyłapywać za dużo, np. [[ [[...]]. Nadają się do ręcznego stosowania, jednak do automatyzacji, np. dla bota, trzeba się dodatkowo zabezpieczyć. „Pancerne” odpowiedniki powyższych wyglądają tak:

  • \[\[[^[|\]]+\]\] lub \[{2}[^[|\]]+\]{2} – znaczy wikilink typu [[...]].
  • \[\[[^[|\]]+\|[^[|\]]+\]\] lub \[{2}[^[|\]]+\|[^[|\]]+\]{2} – znaczy wikilink typu [[...|...]].
  • \[\[[^[\]]+\]\] lub \[{2}[^[\]]+\]{2} – znaczy wikilink typu [[...]] lub [[...|...]].

Komentarz HTML
Wyszukiwanie wewnętrznego komentarza w kodzie HTML w postaci <!--...-->:

  • Znajdź: <!.+?>, chyba żeby nam łapało za mało (np. w komentarzu jest znak „>”), to wtedy dla pewności np. <!--.+?-->

Dowolna litera
Niestety nie ma metaznaku dowolnej litery (np. łacińskiej wraz z literami narodowymi). Poniżej zestaw wszystkich znaków z wybranych języków (polski, czeski, francuski, hiszpański i niemiecki):

  • [ÁČĎÉĚIÍŇÓŘŠŤÚŮÝŽáčďéěiíňóřšťúůýž] – czeski
  • [ÀÂÇÉÈÊËÎÏÔÛÙÜŸàâçéèêëîïôûùüÿ] – francuski
  • [Ññ] – hiszpański
  • [ÄÖÜäöüß] – niemiecki
  • [ĄĆĘŁŃÓŚŹŻąćęłńóśźż] – polski

razem:

  • [A-ZÀÁÂÄĄÇĆČĎÈÉÊËĘĚÍÎÏŁŃŇÑÓÔÖŘߌŠŤÙÚÛÜŮÝŸŹŻŽa-zàáâäąçćčďèéêëęěíîïłńňñóôöřßśšťùúûüůýÿźżž].

Sporo tego i niezbyt to wygodne, ale można pójść na skróty i wybrać zakresy Unicode zawierające najczęściej występujące znaki diakrytyczne

  • [A-Za-z\u00C0-\u017F],

gdzie znaki od U+00C0 do U+00FF to druga połówka „Dodatku Latin-1” (ang. Latin-1 Supplement), czyli

  • ÀÁÂÃÄÅÆÇÈÉÊËÌÍÎÏÐÑÒÓÔÕÖ×ØÙÚÛÜÝÞß
    àáâãäåæçèéêëìíîïðñòóôõö÷øùúûüýþÿ,

a znaki od U+0100 do U+017F to cały „Rozszerzony łaciński A” (ang. Latin Extended-A), czyli

  • ĀāĂ㥹ĆćĈĉĊċČčĎďĐđĒēĔĕĖėĘęĚěĜĝĞğ
    ĠġĢģĤĥĦħĨĩĪīĬĭĮįİıIJijĴĵĶķĸĹĺĻļĽľĿ
    ŀŁłŃńŅņŇňʼnŊŋŌōŎŏŐőŒœŔŕŖŗŘřŚśŜŝŞş
    ŠšŢţŤťŦŧŨũŪūŬŭŮůŰűŲųŴŵŶŷŸŹźŻżŽžſ,

co w przeważającej większości przypadków sprawdzi się, pomimo że w tym zakresie oprócz omawianych liter znajdują się tez znaki matematyczne: × (U+00D7) i ÷ (U+00F7).

I tu dochodzimy do finału: znaki łacińskie oraz polskie i innych języków europejskich, bez „gości” matematycznych to:

  • [A-Za-z\u00C0-\u00D6\u00D8-\u00F6\u00F8-\u017F].

Dowolny znak
Dowolny znak to „enter” lub coś wewnątrz akapitu, można to zapisać tak:

  • \n|.

Z pewnością będziemy jednak szukać czegoś więcej, więc musimy to wyrażenie jakoś wydzielić:

  • (\n|.)

Dowolny znak można opisać także jako coś lub jego zaprzeczenie, np.:

  • [\d\D] lub [\w\W], lub [\s\S] – tu użyto klasy przeciwnych metaznaków (ten ostatni przykład jest szczególnie lubiany przez programistów).
  • (Ź|[^Ź]) – tu użyto alternatywy przeciwieństw, przykład jest oczywiście żartobliwy, bo użyto jakiegoś rzadko używanego znaku, a można użyć dowolnego (może być nawet „enter” albo jakaś „chińszczyzna”), chodzi o pokazanie metody.

Pełne zapytaniaEdytuj

Zamień wszystkie wielokrotne spacje na pojedynczą:

  • Znajdź:  +
  • Zamień:  

Zamień wszystkie tabulatory, spacje i encje twardej spacji (także wielokrotne) na pojedynczą spację:

  • Znajdź: ([\t ]|&nbsp;)+
  • Zamień:  

Usuń wszystkie spacje (także wielokrotne) z końca akapitu lub sprzed wybranych znaków interpunkcyjnych:

  • Znajdź:  +([\n,;:.!?)\]}])
  • Zamień: $1

Usuń wszystkie tabulatory, spacje i encje twardej spacji (także wielokrotne) z końca akapitu lub sprzed wybranych znaków interpunkcyjnych:

  • Znajdź: ([\t ]|&nbsp;)+([\n,;:.!?)\]}])
  • Zamień: $2

Popraw parę najbliższych cudzysłowów prostych na typograficzne (ważne, aby zamiast + było +?, bo inaczej skoczy do ostatniego cudzysłowu w akapicie):

  • Znajdź: "(.+?)"
  • Zamień: „$1”

Porządki z cudzysłowami możemy rozszerzyć o pozostałe znaki cudzysłowu, a nawet o podwójne przecinki i podwójne apostrofy. Tu już musimy użyć trzech grup przechwytujących, więc z powyższego $1 zrobiło się $2:

  • Znajdź: (["„”“]|,,)(.+?)(["„”“]|’’)
  • Zamień: „$2”

Chyba że użyjemy grup nieprzechwytujących, wtedy $1 zostanie na swoim miejscu:

  • Znajdź: (?:["„”“]|,,)(.+?)(?:["„”“]|’’)
  • Zamień: „$1”

Wadą tego zapytania jest jednak to, że skoro podaliśmy wszelkie możliwe znaki cudzysłowu, to program będzie poprawiał również poprawną parę znaków (na siebie samą), co może oznaczać dużą liczbę zbędnych kliknięć. Można to rozwiązać, ale problemy się piętrzą. Przede wszystkim szukamy dwóch alternatyw, z których jedna zawiera wszystkie znaki cudzysłowu bez poprawnego lewego, a druga bez poprawnego prawego. Do pola Zamień wprowadzamy elementy z obu alternatyw jednocześnie, czyli zarówno $4, jak i $7, co wydaje się pozornie nielogiczne. Kruczek leży w tym, że, program znajdzie tylko jeden z tych „dolarów”, przez co drugi zostanie niewykorzystany. Pomysł jest dobry, ale program może wyszukać za dużo, bo także od cudzysłowu zamykającego poprzedniej pary do otwierającego następnej pary. Trzeba więc wprowadzić szereg dodatkowych zabezpieczeń, przez co z trudem mieścimy się w limicie 9 grup przechwytujących. Wprowadzono m.in. konieczność występowania typowych znaków przed lub po wyrażeniu w cudzysłowie, ale nie użyto tam znaku wielokropka, bo jego zastosowanie jest zbyt szerokie.

  • Znajdź: ([\n /([{|]|&nbsp;)((["”“]|,,)([^\n„]+?)(["„”“]|’’)|(["„”“]|,,)([^\n”]+?)(["„“]|’’))([\n ,;:.!?/<)\]}]|&nbsp;)
  • Zamień: $1„$4$7”$9

Przy omawianiu znaków cudzysłowu zawsze trzeba pamiętać, że jest to wyjątkowo trudna do automatyzacji grupa znaków, więc testy są tu szczególnie wskazane. A poza tym... powyższe zadanie wykonane w dwóch zapytaniach na pewno byłoby dużo łatwiejsze.

Zamień dywiz na półpauzę w wyrażeniach liczbowych, np. w: 0-1, 1939-1945, 1,12-1,13 itp.:

  • Znajdź: (\d)-(\d)
  • Zamień: $1–$2

Taka prosta podmiana będzie jednak zaznaczać wiele miejsc niepożądanych, jak różnego rodzaju symbole, kody, odpowiednio sformatowane zapisy dat, fragmenty adresów internetowych i wiele innych. Wszystkiego wykluczyć się nie da, ale sporo można ograniczyć. Poniżej całość bloku liczbowego wyizolowano z otoczenia, ale wprowadzono możliwość występowania liczb z separatorami dziesiętnymi (kropką lub przecinkiem):

  • Znajdź: ([\n ([{„"|]\d+(?:[,.]\d+)?)[-—÷‒―−](\d+(?:[,.]\d+)?[,.]*(?:[\n ;:!?…<'’"”)\]{|}]|&nbsp;))
  • Zamień: $1–$2

Dorabianie setek i tysięcy w zakresach lat, np. 1939–45 na 1939–1945 (w polu Zamień 2 razy użyto tego samego $2):

  • Znajdź: ([^\d])(\d{2})(\d{2})–(\d{2}[^\d])
  • Zamień: $1$2$3–$2$4

Usuwanie spacji pomiędzy inicjałami imion, np. „A. B. Kowalski” na „A.B. Kowalski”. Uwzględnia inicjały 1- i 2-literowe (zarówno „S.”, jak i „St.”). Uwzględnia wielkie litery cyrylicy (0410–042f). Trzeba zaznaczyć przycisk uwzględniający rozróżnianie wielkich i małych liter:

  • Znajdź: ([\n '(>=|][A-ZĄĆĘŁŃÓŚŹŻ\u0410-\u042f][a-ząćęłńóśźż]?\.)(?: |&nbsp;)([A-ZĄĆĘŁŃÓŚŹŻ\u0410-\u042f][a-ząćęłńóśźż]?\.)(?:(?: |&nbsp;)([A-ZĄĆĘŁŃÓŚŹŻ\u0410-\u042f][a-ząćęłńóśźż]?\.))?([\n ,;:')|])
  • Zamień: $1$2$3$4

Co ciekawe, w tym pojedynczym zapytaniu program usuwa spacje zarówno wtedy, gdy występują tylko dwa inicjały, jak i wtedy, gdy inicjałów jest aż trzy, co teoretycznie powinniśmy zrobić w dwóch oddzielnych zapytaniach. A więc mamy tutaj jakby „dwa w jednym”. Rozwiązano to w ten sposób, że grupa obejmująca spację i trzeci inicjał jest opatrzona kwantyfikatorem ?, czyli może być, ale nie musi. Drugą ciekawostką jest to, że grupa ta, pomimo że jest I poziomu, jest nieprzechwytująca, a grupa z inicjałem (jako $3) jest dopiero w jej wnętrzu, czyli na II poziomie. Jak widać, grupa przechwytująca może być wewnątrz nieprzechwytującej (czyli „dolarowana” może być w „niedolarowanej”).

OptymalizacjaEdytuj

Czas wykonania jednego, nawet skomplikowanego, zapytania na dużym pliku zazwyczaj nie stanowi problemu, jednak w przypadku pisania skryptów zawierających wiele zapytań, wydajność zaczyna mieć znaczenie. Co prawda niniejszy poradnik nie omawia łączenia zapytań w skrypty wykonywane jednym kliknięciem, ale warto poznać podstawowe formy przyspieszania pracy programu.

Niepotrzebne powtórkiEdytuj

Tak zamieniamy wiele spacji na jedną, ale w poniższym zapytaniu wpisano „znajdź JEDNĄ lub więcej spacji”, przez co nawet pojedyncza spacja zostanie przez program znaleziona i w konsekwencji... zamieniona na samą siebie, co zabiera kolejne ułamki sekundy:

  • Znajdź:  +
  • Zamień:  

Po dodaniu drugiej spacji zapytanie nabierze brzmienia „znajdź DWIE lub więcej spacji”, przez co program zainteresuje się tylko drugą i ew. następnymi spacjami, wykonując znacznie mniej operacji:

  • Znajdź:   +
  • Zamień:  

Szczerze mówiąc, większość podstawowych metod optymalizacji daje trudne do zauważenia oszczędności czasowe, jednak znaki spacji występują tak często, że akurat w tym przypadku możliwe jest dostrzeżenie różnicy.

Kolejność alternatywEdytuj

Być może mikroskopijne oszczędności osiągniemy również tu (?: |&nbsp;), zamiast tego (?:&nbsp;| ), niewątpliwie bowiem spacja występuje dużo częściej od encji twardej spacji.

Skracanie alternatywEdytuj

Podanie odmiany wyrazu „rok” we wszystkich przypadkach liczby mnogiej zapisane w najprostszej formie nie jest optymalne, bo niektóre fragmenty słów się powtarzają, po skróceniu wszystkich końcówek fleksyjnych składania zapewne jest mniej przejrzysta (i wymagająca przetestowania), ale zadziała szybciej:

  • (lat|lata|latach|latami|latom)(lat(?:a(?:ch|mi)?|om)?)

Tu również oszczędność czasu będzie minimalna, bo podano bardzo prosty przykład, ale sam mechanizm warto znać, bo przy dużo bardziej skomplikowanych zapytaniach nabiera realnego wymiaru.

Co ciekawe, przy takim skróceniu kodu zastosowano kwantyfikatory zachłanne (opisane wcześniej), przez co ustał problem kolejności wymieniania elementów alternatywy od najdłuższego do najkrótszego.

Symbole wielkich liczb można skrócić do:

  • (mln|mld|bln|bld)([bm]l[dn])

Podsumowanie budowy wyrażeń regularnychEdytuj

Spróbujmy określić zbiór podstawowych zagadnień związanych z wyrażeniami regularnymi. Wbrew pozorom nie ma tego tak dużo, a spojrzenie na całość może przydać się do usystematyzowania wiedzy. Tak więc:

  1. znaki mogą występować jako:
    1. literały (znaki rozumiane dosłownie, większość),
    2. metaznaki (backslash i coś po nim),
    3. kilka pojedynczych znaków o specyficznym znaczeniu (daszek, dolar i kropka),
    4. grupa znaków służąca do budowania poleceń: głównie .+?()[]{}| oraz w specyficznych sytuacjach także kilka innych im towarzyszących;
  2. struktury składniowe są tylko trzy:
    1. grupy:
      1. przechwytujące (tylko te grupy można przenosić „dolarami” do pola Zamień, tylko te grupy mogą mieć odwołania wsteczne),
      2. nieprzechwytujące (mają działanie pomocnicze),
      3. przewidywania (są widziane jakby obok zapytania);
    2. klasy (normalne i zaprzeczone),
    3. alternatywy;
  3. zależności pomiędzy strukturami:
    1. klasy mogą zawierać tylko znaki (opisane w różny sposób),
    2. grupy mogą zawierać: znaki, klasy, grupy i alternatywy,
    3. alternatywy mogą być pomiędzy: znakami, klasami i grupami,
    4. alternatywy zazwyczaj umieszcza się w grupie,
    5. wielopoziomowe struktury buduje się grupami obejmującymi,
    6. o każdym znaku, klasie lub grupie można powiedzieć ile razy ma lub może wystąpić.

Rozrysowywanie zapytańEdytuj

Dla orientacji w strukturze bardziej skomplikowanych zapytań możemy sobie pomóc, przedstawiając je w postaci graficznej. Weźmy np. taką zawartość pola Znajdź: (lat(?:a(?:ch|mi)?|om)?). Można ją rozrysować na wiele sposobów. Na przykład:

(
   lat
   (?:
      a
      (?:
         ch|mi
      )?
      |
      om
   )?
)

Problemem będą oczywiście znaki spacji (w tym przykładzie ich nie ma), o których musimy albo pamiętać, śledząc zachowanie kursora, albo doraźnie zamienić je np. na podkreślniki.

Uwagi końcoweEdytuj

Napisanie sensownej formułki mającej zamienić coś w bardziej skomplikowany sposób jest dość długie, zanim osiągniemy zadowalający wynik, ale warto ten czas poświęcić, a następnie gotową formułkę zapisać sobie w pliku tekstowym i trzymać w przystępnym miejscu, np. na pulpicie komputera. W poniższym przykładzie niektóre wikilinki do hektara lub jego symbolu zmieniają się na zwykłe niepodlinkowane „ha”, a robi się to tak:

  • Znajdź: (powierzchni[aąę]?(?: ok\.)?(?: |&nbsp;)?[-–—− \d,.]+(?: |&nbsp;)?(?:tys\.|mln|mld|bln|bld)?(?: |&nbsp;)?)\[{2}hektar(?:\|ha)?\]{2}(?:ami|ach|a|em|owi|om|ów|y|ze)?
  • Zamień: $1ha

Istnieje prawdopodobieństwo, że niektórzy wikipedyści mogą nie zapamiętać od razu całego tego ciągu znaków :).

Tyle podstaw regexów. Nasuwa się jeden wniosek – narzędzie jest niesłychanie pożyteczne, przyspiesza pracę w sposób niewyobrażalny, dając właściwie nową jakość naszej pracy, ale... NIE KLIKAJ W PRZYCISK „ZAMIEŃ WSZYSTKIE”, a jeśli już musisz, to sprawdzaj potem wszystko dokładnie, bo można zmienić o wiele więcej, niż chcemy.

Zobacz teżEdytuj