Tutorijal - Regex - Kako pronaći unutrašnji sadržaj HTML elemenata
Uvod
U uvodnom članku o regularnim izrazima pisali smo o osnovnim odlikama regularnih izraza i opštoj upotrebnoj vrednosti regularnih izraza u pronalaženju tekstualnih obrazaca.
Ovoga puta, pozabavićemo se konkretnim primerom pronalaženja unutrašnjeg sadržaja HTML tagova (prosto rečeno - u pitanju je "innerHTML" određenog elementa), a za početak ćemo se upoznati sa operatorima (sa kojima se nismo prethodno upoznali), koji omogućavaju da se navedeni zahvat sprovede u delo.
Operatori pogleda unapred i pogleda unazad
Operatori pogleda unapred (eng. lookahead) i pogleda unazad (eng. lookbehind), omogućavaju pronalaženje obrazaca koji se nalaze neposredno pre, ili neposredno posle, pojave drugog obrasca.
Operator pogleda unapred (lookahead) - (?=)
Operator pogleda unapred pronalazi u tekstu određeni tekstualni obrazac, posle koga sledi drugi tekstualni obrazac (koji je neposredno vezan za operator pogleda unapred).
Regularni izraz formira se navođenjem obrasca koji se zapravo pretražuje (obrazac_1
), posle čega, unutar zagrada, sledi drugi obrazac (obrazac_2
), kome prethodi niska ?=
:
Rezultat pretrage ulaznog teksta je obrazac_1
, ali - samo ako se neposredno iza pojavljuje obrazac_2
.
Da bismo bolje razumeli prethodno navedene smernice, pogledajmo sledeći regularni izraz:
Pod uslovom da u tekstu postoji podniska koja odgovara obrascu za pretragu:
- rezultat pretrage je prva niska "meta" posle koje sledi niska "fizika"
- niska "fizika" nije deo rezultata
- u pretrazi će biti zanemarena bilo koja pojava niske "meta" posle koje ne sledi niska "fizika"
Najprostije rečeno, operator lookahead pronalazi nisku čiji sam kraj predstavlja početnu poziciju druge niske, koja je (takođe) navedena u obrascu za pretragu.
Operator pogleda unazad (lookbehind) - (?<=)
Operator pogleda unazad (u idejnom smislu, veoma slično kao operator pogleda unapred, ali - 'u obrnutom smeru'), pronalazi obrazac koji se zapravo pretražuje (obrazac_1
) - kome prethodi drugi obrazac (obrazac_2
).
Rezultat pretrage ulaznog teksta je (ponovo) obrazac_1
, ali, samo ako se neposredno ispred pojavljuje obrazac_2
.
I ovoga puta ćemo iskoristiti konkretan primer da otklonimo eventualne nedoumice: kreiraćemo regularni izraz koji praktično pronalazi ceo broj telefona (sa pozivnim brojem), ali - tako da se kao rezultat vraća samo deo posle pozivnog broja.
Da pojasnimo dodatno.
Sledeći regularni izraz ....
.... pronalazi nisku od tri cifre posle koje sledi znak '/', ali - budući da je u primeru takav izraz upotrebljen kao obrazac za pretragu unutar operatora lookbehind - praktična svrha obrasca je pronalaženje (tj. označavanje) početka niske koja je definisana na sledeći način ....
.... i u pitanju je niska koja predstavlja broj telefona bez pozivnog broja.
Nažalost, postoji i jedna krajnje bitna razlika između dva operatora o kojima diskutujemo: operator pogleda unazad ne dozvoljava (u većini implementacija), upotrebu obrazaca proizvoljne širine u svojstvu kriterijuma za pretragu, već samo obrazaca fiksne širine.
Pogledajmo sada i (prilično uobičajen u svakodnevnoj praksi), primer koji smo najavili.
Primer - Prepoznavanje unutrašnjeg sadržaja HTML tagova ("innerHTML")
Pretpostavićemo da nam je na raspolaganju HTML lista, i da je potrebno kopirati elemente liste u određeni dokument (neki drugi dokument), ali, kao što smo nagovestili - bez <li>
tagova.
Sama niska unutar <li>
tagova (smatraćemo tako), može sadržati bilo kakve znakove, a kao što znamo, regularni izraz koji definiše nisku koja može sadržati "bilo koji broj bilo kakvih znakova", zapisuje se na sledeći način: .*
.
Obrazac za pretragu u operatoru lookbehind, biće (doslovno) niska <li>
, dok će obrazac u operatoru lookahead, biti niska </li>
, i stoga ceo regularni izraz ima sledeći oblik:
Pretpostavljamo da je već jasno kako regularni izraz funkcioniše, ali, sledi dodatno objašnjenje.
Prvi deo obrasca ....
.... traži niske (bilo koje dužine i sadržine, s obzirom na to da smo naveli .*
), kojima prethodi niska <li>
, dok obrazac ....
.... traži niske (ponovo: bilo koje dužine i sadržine, s obzirom na to da smo naveli .*
), koje se završavaju niskom </li>
.
Kada spojimo dva obrasca (onako kako smo već videli), dobija se obrazac za pronalaženje niski koje počinju posle otvarajućeg <li>
taga i završavaju se pre početka zatvarajućeg </li>
taga.
Šta se ne može izvesti kombinacijom operatora lookahead i lookbehind
Shodno primerima koje smo videli u prethodnom odeljku, pretpostavićemo da bi mnogim čitaocima moglo pasti na pamet da se operator lookbehind takođe može koristiti i za prepoznavanje tagova bilo kog tipa i sadržine, sa navedenim id-ovima, klasama i atributima (recimo, nešto nalik na obrazac koji bi inače prepoznavao otvarajuće <a>
tagove - <a.*>
) ....
.... međutim, kao što smo ranije nagovestili - u navedenoj situaciji postoji problem.
Operator pogleda unapred (da se podsetimo), ne dozvoljava navođenje obrazaca proizvoljne širine (već samo obrazaca fiksne širine).
Navedeno ograničenje je (nažalost) i dalje aktuelno u većini implementacija regex-a, i stoga je u takvim okolnostima potrebno "dovijati" se na druge načine (čime ćemo se takođe uskoro pozabaviti, ali, u nekom od narednih članaka).
Za kraj, pogledajmo i alternativne verzije operatora pogleda unapred i unazad.
Odrednica "!" - negativni pogled unapred i unazad
Pored prethodno navedenih oblika, operatori pogleda unapred i unazad mogu se definisati i na drugi način (naravno, sa sasvim drugačijim (tj. suprotnim) značenjem).
Ukoliko se u operatorima upotrebi znak !
- umesto znaka =
, rezultat su isključivi (tj. "negativni") operatori pogleda unapred i unazad.
Recimo, sledeći regex (negativni pogled unapred) ....
.... pronalazi pojave imena "Petar" - posle kojih NE sledi prezime "Jovanović".
Za negativni pogled unazad, osvrnućemo se ponovo na primer sa brojevima telefona.
Sledeći izraz ....
.... ovoga puta traži brojeve telefona kojima nedostaje pozivni broj (to jest, pretražuju se kombinacije cifara i crtica, dužine 6 do 9 znakova - kojima NE prethodi kombinacija od tri cifre koja se završava kosom crtom).
(U praksi, navedeni regularni izraz bi se mogao iskoristiti za pronalaženje nepravilno formatiranih brojeva telefona.)
Za kraj ....
Operatori koje smo ukratko opisali u ovom članku predstavljaju veoma korisnu dopunu u odnosu na operatore o kojima smo već diskutovali u uvodnom članku, čime je (u praktičnom smislu), uvod u tematiku regularnih izraza zaokružen.
Pravu vrednost (da ponovimo), regularni izrazi pokazuju onda kada počnete da ih koristite u svakodnevnom radu sa editorima, ili, još bolje - u programima koje pišete - i upravo će upotreba regularnih izraza u programskim jezicima biti tema nekih od budućih članaka ....