Wyrażenia regularne, a tagi HTML
Jedną z głównych funkcji wyrażeń regularnych wykorzystywanych przy tworzeniu serwisów www jest analiza kodu HTML. Ileż to razy chcielibyśmy znaleźć tekst zawarty między dwoma znacznikami HTML lub zmienić tu i ówdzie wygląd strony, w przypadku gdy zwyczajna podmiana poprzez str_replace nie wchodzi w grę.
Aby znaleźć tekst leżący za tagiem trzeba wpierw umieć ten tag znaleźć, ale nie do końca, bo niekiedy sprawę można stosunkowo uprościć. Dla przykładu jeśli w kodzie
<span class="post">
jakiś tekst
</span>
chcemy odnaleźć frazę znajdującą się w obrębie tagu SPAN i wiemy, że podczas szukania nie natrafimy na żadne znacznik HTML oraz że w szukanej frazie wszystkie znaki specjalne zamienione zostały na necje, to z czystym sumieniem możemy zastosować wzór
'#<span class="post">s*([^<]+)s*</span>#s'
Oczywiście w tym wypadku wzór ten możemy uprościć do postaci
'#>s*([^<]+)s*<#s'
Niekiedy jednak interesuje nas częśc tagu lub dowolny tag razem ze swoim wnętrzem. W takim wypadku trzeba wiedzieć z czego składa się znacznik HTML. Generalnie tag można przedstawić w następujący sposób
'#<(w+)[^>]*>(.*?)</\1>#s'
Powyższy wzór zajmie w tekście pierwszy napotkany znacznik, zachowa jego wnętrze i zakończy szukanie w momencie napotkania znacznika kończącego ów tag. Ta metoda sprawdza się tylko w przypadku, gdy interesuje nas pierwszy napotkany tag i gdy jesteśmy pewni, że w jego wnętrzu nie znajdziemy skoncentrowanych bliźniaczych tagów. Dla przykładu w kodzie
<span class="post"> jakiś tekst <span class="quote">cytat</span> dalszy tekst </span>
wzór zakończy swoją pracę po frazie 'cytat</span>', ponieważ rozkazaliśmy mu aby zatrzymał się po napotkaniu pierwszego zamknięcia szukanego tagu.
Jeśli interesuje nas zagnieżdżenie tagów, to zmuszeni będziemy użyć rekurencji. Rekurencję stosujemy poprzez zastosowanie konstrukcji (?1), gdzie liczba 1, to numer podzapytania, którego chcemy użyć do wywołania rekurencji. W przypadku zastosowania konstrukcji (?R) lub (?0) rekurencji podlega całe wyrażenie.
Poniżej przedstawiam prowizoryczny kod, który pozwala na znalezienie wyrażeń zawierających zagnieżdżone tagi.
'#<(w+)[^>]*>(([^<]*)|(?R))+</\1>#s'
Ów kod zapewne wymaga paru wyjaśnień, więc do rzeczy. Generalnie całość można by zamknąc w wyrażeniu
'#<(w+)[^>]*>[^<]*</\1>#s'
Niestety w takim wypadku znajdujemy tylko jeden stopień zagnieżdżenia, a w zasadzie nie stopień, bo system bezwzględnie zakończy wyszukiwanie po napotkaniu pierwszego znacznika zamykającego tag.
Problem ten możemy rozwiązać przez lekką modyfikację wzoru. Zamieniamy '[^<]*' na '(([^<]*)|(?R))'. W tym wypadku używamy alternatywy, ponieważ między tagami spodziewamy się ciągu znaków lub rekurencji, którą będzie kolejna para tagów i tak dalej. Niestety i to rozwiązanie nie możemy zostawić jako ostateczne, ponieważ rozwiązuje ono problem zagnieżdżonych tagów, ale nijak ma się do tagów równoległych. Zgodnie z poniższym przykładem
<b> tekst <b> tekst </b> tekst </b> - tagi zagnieżdżone
<b> tekst <b> tekst </b> tekst <b> tekst </b> tekst </b> - tagi zagnieżdżone równoległe
Aby rozwiązać problem równolegle zagnieżdżonych tagów musimy zwieloktrotnić wnętrze naszego tagu rodzica i tak '(([^<]*)|(?R))' zamieniamy na '(([^<]*)|(?R))+', co w konsekwencji daje nam '#<(w+)[^>]*>(([^<]*)|(?R))+</\1>#s'.
To tyle na temat zagnieżdżenia. Mam nadzieję, że w miarę jasno opisałem problem. Jeśli z czymś były by problemy, to bardzo proszę opisać je w komentarzach.
Ostatnim problemem związanym z wyrażeniami regularnymi i ich zastosowaniem w interpretacji kodu HTML jest pełne rozpoznanie znacznika. Niekiedy zapis '<w+[^>]>' do rozpoznania tagu nie wystarcza, ponieważ może się zdażyć, że szukany tag we swoich właściwościach zawierał będzie znak ">". Poniżej przykładowy kod, który może "wykrzaczyć" nasz wzór
<a href="http://moja.nazwa.pl?wartosc=<b>tekst</b>">
Po sparsowaniu tego tekstu względem wzoru '<w+[^>]>' otrzymamy
<a href="http://moja.nazwa.pl?wartosc=<b>
Dzieje się tak, ponieważ w HTMLu możemy z czystym sercem umieścić znak ">" wewnątrz właściwości znacznika, ale ta opcja nie została uwzględniona w naszym zapytaniu. Aby napisać poprawny wzór musimy wpierw zastanowić się jak zbudowane są znaczniki HTML, dlatego przykład zamieściłem pod spodem
<nazwa właściwość="wnętrze właściwości" disabled>
Powyższy przykład zostanie prawidłowo sparsowany przez poniższy wzór
<((w+)(s|w+s*(=s*"[^"]*")?)*)>
W tym wypadku bierzemy pod uwagę najważniejsze fakty, czyli że:
- tag musi rozpoczynań się od słowa
- między właściwościami może być dowolna liczba znaków oddzielających
- między nazwą właściwośći, a znakiem "=" oraz treścią właściwości może być dowolna ilość spcji
- właściwość może nie mieć swojej treści
W tym uproszczeniu nie bierzemy natomiast pod uwagę faktu, że możemy mieć do czynienia z tagiem kończącym lub, że nasz tag może być jednocześnie tagiem początkowym i tagiem kończącym. I to byłby w zasadzie koniec :)
Proszę zważać na to co piszecie w komentarzach. Wpisy zawierające idiotyczne, obraźliwe, wulgarne, ubogie merytorycznie lub niezgodne z polskim prawem treści będą usuwane.