Regularni izrazi - napredna pretraga teksta
Uvod
Većina zadataka koji su vezani za pretragu i izmenu teksta u editorima, može se obaviti na jednostavan način, pomoću osnovnih alata (koji omogućavaju da se uneti tekst iz polja za pretragu traži u otvorenoj datoteci, ili, svim otvorenim datotekama (odnosno, svim datotekama u folderu/projektu)), međutim, osnovni mehanizmi pretrage (bez regularnih izraza) - nisu u stanju da u ulaznom tekstu prepoznaju tekstualne obrasce.
Regularni izraz * je posebno formatirana niska znakova koja omogućava prepoznavanje i/ili validaciju tekstualnih obrazaca, to jest (da pojasnimo dodatno), regularni izraz je posebno formatirana niska preko koje se definiše skup (konkretnih, "običnih") niski koje će biti pretraživane u ulaznom tekstu. **
Pojmovi 'pretraga' i 'prepoznavanje', čitaocima su svakako jasni (i poznati od ranije), a validacija podrazumeva proveru određene niske, pri čemu se obraća pažnja na to da li se niska poklapa sa traženim obrascem (tipičan primer: proveriti da li je email adresa, uneta npr. preko formulara, formatirana na propisan način).
Svi editori o kojima smo pisali u članku o editorima i uređivanju teksta podržavaju regularne izraze i (naravno), regularni izrazi su podržani i u svim popularnim programskim jezicima (preko različitih biblioteka).
Kako se formiraju regularni izrazi
Za početak, verujemo da prvi susret sa tipičnim regularnim izrazom ....
.... ne može biti previše prijatan (ali, "eto", "bacili smo vas odmah u vatru"). :)
Neintuitivna gomila "kojekakvih" znakova, ne deluje kao nešto što ima previše smisla, a popularna pošalica koja navodi da je za dobijanje regularnih izraza dovoljno pustiti mačku da šeta po tastaturi * - kao da je stvorena da se u ovom trenutku pojavi i objasni poreklo izraza koji smo prethodno videli.
Međutim, ako ostavimo pošalice po strani, prilično smo ubeđeni da ćete, uz pažljivo čitanje članka pred vama i uloženi trud, uspeti da u potpunosti razumete: i prethodni izraz (a usput, naravno, i da saznate čemu zapravo služi), kao i sve druge regularne izraze sa kojima ćete se susretati (jer zaista nije previše teško). :)
Za početak (kako i dolikuje), razmotrićemo jednostavne primere, koji se jedva razlikuju od obične pretrage (ali, videćemo da čak i najjednostavniji regularni izrazi, omogućavaju sasvim zanimljive pretrage), a potom ćemo polako povećavati kompleksnost izraza sa svakim novim primerom (i u svemu se truditi da svaki regularni izraz sagledamo "sa svih strana").
Podešavanje editora
Preporučujemo da pokrenete editor teksta koji koristite i (uporedo sa čitanjem članka), isprobavate primere koje ćemo prikazivati (otvorite i prozor za pretragu i aktivirajte korišćenje regularnih izraza).
Pre svega, regularni izrazi dozvoljavaju da se pretraga obavlja na način koji je skoro isti kao i pretraga bez regularnih izraza (pretraga "znak-po-znak") - pod uslovom da se ne koriste znakovi kao što su (pre svih), tačka, apostrof i kosa crta, koji u regularnim izrazima imaju specijalnu namenu (a ima i drugih znakova specijalne namene, o čemu ćemo više govoriti u nastavku).
Napišite rečenicu u kojoj je (negde otprilike u sredini), navedeno Vaše ime i prezime, i potom unesite ime i prezime u polje za pretragu (u nekim editorima pronađena niska će automatski biti označena u prozoru sa otvorenom datotekom, dok je u drugim programima potrebno da se pretraga pokrene "ručno").
Regularni izrazi su "osetljivi" na velika i mala slova ('po default-u'), ali, podešavanja u editorima često poništavaju navedenu karakteristiku, i stoga, da bismo mogli da se upoznamo sa regularnim izrazima u njihovom "prirodnom stanju", potrebno je podesiti opcije u editoru:
Sada smo spremni.
Osnovni regularni izrazi i klase znakova
Prvi "pravi" regularni izraz koji ćemo prikazati, poslužiće za prepoznavanje različitih rodova prideva beli (beli, bela, belo).
Prva tri slova su ista (u sve tri reči), poslednje slovo se menja, a regularni izraz koji obuhvata sve niske koje želimo da pretražujemo, ima sledeći oblik:
Znakovi uneti u veliku zagradu tretiraju se kao zasebne pojave svakog znaka - u kombinaciji sa niskom koja prethodi.
Da budemo skroz precizni: pretraga podrazumeva pronalaženje niske dužine 4, koja počinje sa "bel", posle čega se na 4. poziciji može pojaviti bilo koji od znakova iz velike zagrade ('i', 'a' ili 'o').
Na ovom mestu (pre nego što nastavimo da se upoznajemo sa osnovnim 'tehnikalijama'), ukratko ćemo se osvrnuti i na pojavu delimičnih poklapanja.
Delimična poklapanja
Ako za trenutak zamislimo da u tekstu (koji se pretražuje), postoje reči kao što su "belilo", "belina" i sl, navedene reči se neće poklopiti sa obrascem pretrage, međutim, budući da se prva četiri znaka u obe reči poklapaju sa obrascem pretrage, do grešaka može doći ukoliko se regularni izraz koristi (npr), u nekom od programskih jezika, u okviru funkcija za zamenu ili uklanjanje delova niske.
Problem se rešava korišćenjem dodatne regex sintakse (sa kojom ćemo se uskoro upoznati), preko koje se preciziraju granice reči, tj. niski.
Klase znakova
Niska koja stoji u velikim zagradama (na ranije prikazan način), i označava da se na datom mestu može pojaviti bilo koji pojedinačni znak iz velike zagrade - nosi naziv klasa znakova.
Za najčešće korišćene klase znakova, predviđeni su i skraćeni načini zapisa:
Ugrađene klase znakova (tj. "prečice"), svakako dobro dođu, ali, ako određena situacija zahteva dodatnu preciznost - moramo biti precizniji.
Na primer, ako je potrebno osmisliti regex koji prepoznaje polja iz šahovske notacije, gornji izraz ("znak-cifra"), neće biti od pomoći (znamo da šahovska polja "M8", "Z6" i sl. - ne postoje), i mnogo je pravilnije navesti raspon mogućih znakova:
Sa pogodnostima upotrebe prečica za klase znakova, upoznaćemo se u budućim primerima, a da bismo se dalje upoznali sa klasama znakova koje korisnici mogu definisati samostalno (tj. da bismo se što bolje upoznali sa tim šta se može, a šta ne može postići preko osnovnih regularnih izraza), razmotrićemo još jedan jednostavan primer.
Dodatni primer upotrebe jednostavnih regularnih izraza
Početna postavka je sledeća: otvorena datoteka u kojoj je upisano svih sedam padežnih oblika imenice "Petar" ....
.... a zadatak je: osmisliti regularni izraz koji pronalazi (tj. obuhvata), svih sedam reči, u celosti (naravno, uz uključenu podršku za prepoznavanje velikih i malih slova).
- Pokušaj #1:
Uviđamo odmah da program pronalazi (pod)niske dužine 4 u svim padežnim oblicima.
Znakovi 'a' i 'r' u velikoj zagradi (iako liče na nastavak, tj. 'završetak' nominativa), samo su nezavisni znakovi koji se kombinuju sa prethodećom niskom "Pet" (baš kao i u prvom primeru), što praktično znači da se odvijaju dve nezavisne pretrage: "Peta" i "Petr".
Međutim, regularni izraz za sada ne prepoznaje: svih sedam reči, u celosti.
Ukoliko dopunimo obrazac na sledeći način (pokušaj #2):
.... program će tražiti sva poklapanja dužine pet znakova: "Petar", "Petra", "Petru", "Petre" (što su potpuna poklapanja koja postoje u tekstu), "Petro" (što je delimično poklapanje, koje takođe postoji u tekstu), kao i niske "Petaa", "Petau", "Petae", "Petao", "Petrr" - koje u datoteci ne postoje.
Rekli bismo da se kod mnogih čitalaca u ovom trenutku (sasvim prirodno) može pojaviti pretpostavka da bi se sledećom dopunom (pokušaj #3) ....
.... problem mogao rešiti, međutim, navedeni izraz (iako je ispravan sam po sebi) - zapravo će pronaći samo instrumental imenice Petar ("Petrom").
Ukoliko u samom regularnom izrazu nije definisano da postoji mogućnost da se neki od znakova u velikim zagradama ne pojavi, smatra se da se svaki od četiri dela pretrage, mora pojaviti (što nije praktično jer, u primeru koji razmatramo, samo jedan od padeža ima šest slova).
Problem se rešava upotrebom tzv. kvantifikatora.
Kvantifikatori - ?, + i *
Regex kvantifikatori su specijalni znakovi preko kojih se navodi, koliko puta se ponavlja određeni znak, ili klasa znakova:
Shodno navedenim pravilima, u izraz koji smo prethodno definisali, možemo dodati kvantifikator ?
.
Kada dodamo kvantifikator ?
, dozvoljavamo da se četvrti član pretrage (znak 'm'), pojavi jednom ili nijednom, što omogućava svih sedam poklapanja (to je to, samo jedan dodatni znak, ali - 'pravi').
Ukoliko bismo koristili znak *
, poslednji član pretrage ("m"), mogao bi da se pojavi i više puta, ali - to nije dobar obrazac za pretragu u primeru koji razmatramo (proverite sami).
Pogledajmo još nekoliko jednostavnih regularnih izraza koji se daju formirati uz korišćenje do sada prikazanih opcija.
U gornjim primerima (očekivano) postoji i jedan manji problem (koji predstavlja uvod u sledeći odeljak): iako će niska #33aaff
biti prepoznata, a niska #33nnff
odbačena, niska #33aaffaaff
će takođe biti prepoznata - iako takva niska ne predstavlja heksadekadni zapis boje (koji inače podrazumeva šest cifara, ili osam, * u kom slučaju poslednje dve cifre označavaju Alfa kanal).
Naravno, i za ovakav problem postoji rešenje (s tim da ćemo do rešenja doći postepeno) ....
Operatori "{m, n}" i "|"
Ako se sa odrednicom za jednu cifru (?
), ne "zahvata" dovoljno veliki raspon, a uz odrednicu za proizvoljan broj cifara (*
), fali kontrole i preciznosti, rešenje može biti upotreba kvantifikatora raspona {}
, koji omogućava da se u vitičastim zagradama navede tačan ili približan broj ponavljanja.
Opšti princip objasnićemo preko primera (u kome važi: x = 3 i y = 5).
Regularni izraz za pronalaženje heksadekadnih brojeva, potencijalno bi se mogao izvesti na sledeći način ....
.... međutim, iako smo "na pravom tragu", ima još detalja oko kojih se moramo pobrinuti.
Izraz sa slike #16 traži (i pronalazi) sve niske koje počinju heštegom "#" posle koga sledi šest heksadekadnih cifara, i u pitanju je adekvatno rešenje za određene "neformalne" situacije, to jest, za tekstualne datoteke u kojima nema mnogo heksadekadnih brojeva, pri čemu znamo da je korišćen samo korektan šestocifreni hex zapis.
Ako je potrebno da budemo precizni, odnosno, ako je potrebno da se obuhvati i heksadekadni zapis boja sa osam cifara (poslednje dve cifre označavaju Alfa kanal), moramo "tražiti dalje" ....
Ako pokušamo da pretragom obuhvatimo i osmocifreni zapis boja, preko sledećeg izraza:
.... ponovo nećemo "dobro proći", jer, svaki put kada pronađe prvih šest cifara - program će praktično prestati da razmatra da li nakon šeste cifre ima i drugih cifara (kao i u prethodnom primeru), i biće označeno samo prvih šest cifara - nakon čega je svejedno da li uopšte ima cifara posle šeste cifre, da li slede dve cifre, ili sledi manje ili više od dve cifre)!
Da bismo bili skroz sigurni da će regularni izraz tražiti heksadekadni zapis boja sa (tačno) 6 ili 8 cifara, potrebno je ispratiti sledeća uputstva:
- umesto raspona, praktičnije je definisati dva zasebna regularna izraza
- dva nezavisna regex-a treba spojiti preko operatora "|" (logičko ILI)
- potrebno je koristiti i klasu znakova
\b
("boundary" - krajevi reči)
Regularni izraz sada predstavlja korektan obrazac pretrage, koji pronalazi: "niske koje počinju znakom '#' - posle čega sledi tačno šest ili osam znakova koji predstavljaju heksadekadne cifre - nakon čega se reč završava".
U svemu navedenom (kao što i sami možete lako zaključiti), od presudne važnosti je upotreba klase znakova \b
, jer - ukoliko se ne upotrebi navedena klasa znakova (kao "signal" da je potrebno uzeti u obzir granice reči) - programi koji izvršavaju regex pretragu, i dalje će uzimati u obzir početne delove niski koje se ne završavaju posle šeste cifre!
Grupisanje delova izraza preko zagrada
Male zagrade mogu se koristiti za grupisanje celina unutar izraza (pogledajmo primere):
Prvi izraz traži dve reči: "monokl" i "motocikl", dok drugi izraz traži heksadekadni zapis boje, koji se može (ali i ne mora) naći na određenom mestu.
Tipični primeri upotrebe regularnih izraza
Da bismo zaokružili uvodnu priču o regularnim izrazima, sagledaćemo detaljno nekoliko tipičnih primera (koji prikazuju kako različite tehnike koje smo do sada opisali 'sarađuju' u praksi).
Brojevi telefona
Ponešto pojednostavljen izraz koji vidimo ispod, može se iskoristiti za prepoznavanje brojeva telefona (sa pozivnim brojem za gradove ili mobilne mreže, ali, bez pozivnog broja za državu):
Posmatrajmo izraz po delovima:
Da li se izraz može pojednostaviti upotrebom prečica za klase znakova?
Svakako da može:
Niska \d
u regularnim izrazima predstavlja cifru, i stoga pojava navedene skraćenice ima isti efekat kao pojava klase znakova [0-9]
.
E-mail adrese
Regularni izraz za prepoznavanje e-mail adresa (sa kojim smo se "možda" već susreli :)), ima sledeći oblik:
Posmatrajmo izraz po delovima:
Na ovom mestu, možemo primetiti jednu krajnje 'zgodnu' stvar u regularnim izrazima: uz upotrebu zagrada, može se preciznije definisati dejstvo kvantifikatora, što ćemo razmotriti na primeru kvantifikatora {2, }
i +
koje smo koristili.
Kvantifikator {2,}
, koji je u zagradi, odnosi se na klasu znakova [a-z]
, odnosno, određuje da se bilo koji od znakova klase [a-z]
može pojaviti proizvoljan broj puta (pod uslovom da se pojavi grupa od bar dva znaka), posle čega mora uslediti tačka ([a-z]{2,}\.)
.
Kvantifikator +
određuje da se celokupan izraz koji smo prethodno naveli u zagradi (praktično, deo domena), * mora pojaviti jedanput, ali, može se pojaviti i više od jedanput.
Prepoznavanje HTML tagova
Prepoznavanje HTML tagova može se izvesti preko sledećeg obrasca pretrage:
Da pojasnimo:
- niska počinje znakom
<
- nakon prvog znaka, znakovi od 'a' do 'z', znak '/', znaci navoda, tačka, znak jednakosti i praznina, pojavljuju se proizvoljan broj puta
- niska se završava znakom
>
(Regularni izraz prepoznaje: i "otvarajuće" tagove, sa ili bez atributa, i "zatvarajuće" tagove.)
Preko prikazanog izraza, izabrani tagovi se lako mogu izdvojiti zarad kopiranja, brisanja, izdvajanja sadržaja između tagova (malo komplikovaniji zahvat i tema za neku kasniju diskusiju), i sl.
Sledeći koraci ....
Tehnike koje smo predstavili u članku, nisu (naravno) "sve što se može reći" o regularnim izrazima, ali, ima sasvim dovoljno 'građe' za početak i već sada (uz korišćenje stečenog znanja i uz ponešto truda), sami možete kreirati regularne izraze koji će sasvim dobro poslužiti u svakodnevnom radu.
Iako se opcije mogu isprobavati u editorima koje inače koristite, preporučujemo i jedan koristan sajt ....
Regex 101... koji u svemu može dodatno pomoći.
Uz dobro vladanje prečicama za rad sa tekstom (i ostalim naprednim opcijama u editorima), i uz dobro poznavanje regularnih izraza, bićete u prilici da poslove unosa i obrade teksta u web dizajnu i programiranju, obavljate znatno jednostavnije i znatno efikasnije.