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 naravno 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 zapravo tražimo (obrazac_1
), posle čega, unutar zagrada, sledi drugi obrazac (obrazac_2
), kome prethodi niska ?=
:
obrazac_1(?=obrazac_2)
Rezultat pretrage ulaznog teksta je obrazac_1
, ali - samo ako se neposredno iza pojavljuje obrazac_2
.
Da bismo bolje razumeli prethodno navedene odrednice, pogledajmo sledeći regularni izraz:
meta(?=fizika)
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 zapravo tražimo (obrazac_1
) - kome prethodi drugi obrazac (obrazac_2
).
(?<=obrazac_2)obrazac_1
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 sve 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.
(?<=\d{3}\/)[0-9-]{6,9}
Da pojasnimo dodatno.
Sledeći regularni izraz ....
\d{3}\/
.... pronalazi nisku od tri cifre koja se završava znakom '/', ali - budući da je u primeru takav izraz upotrebljen kao obrazac za pretragu unutar operatora lookbehind - praktična svrha obrasca je da pronađe (označi) početak niske koja je definisana na sledeći način ....
[0-9-]{6, 9}
.... a u pitanju je niska koja predstavlja broj telefona bez pozivnog broja i sastoji se iz cifara i znakova '-', tako da ukupna dužina niske može biti između šest znakova (recimo, kratak broj sa pet cifara i jednom crticom, pr. "62-544"), i devet znakova (standardni broj sa sedam cifara i dve crtice, pr. "21-12-323").
Nažalost, postoji i jedna krajnje bitna razlika između dva operatora o kojima diskutujemo: operator pogleda unazad (u većini implementacija), NE dozvoljava 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 regularni izraz koji definiše nisku koja može sadržati "bilo kakve znakove" je (kao što već znamo): .*
.
Tačka označava "bilo koji znak", a asteriks *
, "bilo koji broj ponavljanja" (a ako želimo da budemo sigurni da nećemo birati prazne <li>
tagove, umesto znaka *
možemo staviti znak +
, koji određuje da se neki od znakova iz navedene klase znakova, mora pojaviti bar jednom) ....
Obrazac za pretragu u operatoru lookbehind biće (doslovno) niska <li>
, dok će obrazac u operatoru lookbehind biti niska </li>
, pa ceo regularni izraz ima sledeći oblik:
(?<=<li>).*(?=</li>)
Pretpostavljamo da je već jasno kako regularni izraz funkcioniše, ali sledi dodatno objašnjenje.
Prvi deo obrasca ....
(?<=<li>).*
.... traži niske (bilo koje dužine i sadržine, s obzirom na to da smo naveli .*
), kojima prethodi niska <li>
, dok obrazac ....
.*(?=</li>)
.... 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), dobijamo 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 ne možemo izvesti kombinacijom operatora lookahead i lookbehind
Pretpostavićemo da bi mnogim čitaocima (shodno onome što smo videli u prethodnom odeljku), moglo pasti na pamet da se operator lookbehind može koristiti 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.*>
) ....
(?=<a.*>).*
.... međutim, kao što smo već nagovestili - u navedenoj situaciji postoji problem.
Operator pogleda unapred (da ponovimo), 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, pa se u takvim okolnostima moramo "dovijati" 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
Operatori pogleda unapred i unazad - pored prethodno navedenih oblika - mogu se definisati i na drugi način (naravno, sa sasvim drugačijim (suprotnim) značenjem).
Ukoliko u navedenim operatorima upotrebimo znak !
- umesto znaka =
- dobijamo isključive ("negativne") operatore pogleda unapred i unazad.
Recimo, sledeći regex (negativni pogled unapred) ....
Petar(?! Jovanović)
.... pronalazi pojave imena "Petar", posle kojih NE sledi prezime "Jovanović".
Za negativni pogled unazad, osvrnućemo se ponovo na primer sa brojevima telefona, pa tako izraz ....
(?<!\d{3}\/)[0-9-]{6,9}
.... ovoga puta traži brojeve telefona kojima nedostaje pozivni broj (to jest, traži 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 ....