Uvod
Većina zadataka vezanih za pretragu i izmenu teksta u editorima može se obaviti na jednostavan način, pomoću osnovnih alata koji uneti tekst iz polja za pretragu traže 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 izrazima prepoznaju tekstualne obrasce.
Regularni izrazi, popularni i pod pozajmljenom skraćenicom regex (engl. "regular expression") su niske znakova koje definišu obrasce za pretragu ili validaciju teksta.
Pojam pretrage je svakako jasan čitaocima, a validacija podrazumeva proveru određene niske, pri čemu se obraća pažnju na to da li se niska poklapa sa traženim obrascem (tipičan primer: proveriti da li je email adresa, uneta preko formulara, formatirana na propisan način).
Svi editori o kojima smo govorili u članku o editorima i uređivanju teksta podržavaju regularne izraze (i naravno, regularni izrazi su, preko biblioteka, podržani i u svim popularnim programskim jezicima).
Kako se formiraju regularni izrazi
Za početak, verujemo da prvi susret sa ovakvom niskom ....
[\w\.]*@(\w*\.){1, 10}\w*
.... 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 sa interneta 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žen 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 (zaista nije previše teško).
Prvo ćemo se, kako dolikuje, upoznati sa jednostavnim primerima koji se jedva razlikuju od obične pretrage (ali ćete videti da i sa najjednostavnijim regularnim izrazima možemo praviti 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").
Otvorite editor teksta koji koristite i isprobavajte primere uporedo sa čitanjem (otvorite i prozor za pretragu i aktivirajte korišćenje regularnih izraza).

Pre svega, regularni izrazi dozvoljavaju da pretragu obavljamo na način koji je skoro isti kao i pretraga bez regularnih izraza (pretraga "znak-po-znak") - pod uslovom da ne koristimo specijalne znakove kao što su (pre svih) tačka, apostrof i kosa crta (ima i drugih, o čemu ćemo više govoriti u nastavku) - koji u regularnim izrazima imaju specijalnu namenu!
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 nekim drugim potrebno pokrenuti pretragu "ručno").

Regularni izrazi su (po definiciji) "osetljivi" na velika i mala slova, ali, podešavanja u programima (editorima), često poništavaju ovu karakteristiku. Da bismo to rešili (da bismo se sa regularnim izrazima upoznali u njihovom "prirodnom stanju"), svakako možemo podesiti opcije u editoru:

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).
Lako uočavamo da su prva tri slova ista, a da se poslednje slovo menja, pa je regularni izraz koji pronalazi sve oblike koji tražimo:
bel[iao]
Znakovi uneti u veliku zagradu tretiraju se kao zasebne pojave svakog znaka - u kombinaciji sa niskom koja prethodi.
Kao što je u matematici a * (b + c + d) = (a * b) + (a * c) + (a * d), tako i regularni izraz koji smo naveli, istovremeno traži (praktično) tri reči: "beli", "bela", "belo".
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 neki od znakova iz velike zagrde (i, a ili o).
Niske kao što su (npr) "belilo" i "belina" - neće se poklopiti sa obrascem pretrage (iako postoji delimično poklapanje, budući da se obrazac pretrage poklapa sa početnim delovima navedenih niski).
Niska koja se na prethodno prikazan način navodi u velikim zagradama - 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đene su skraćeni načini zapisa:
. - bilo koji znak
\d - bilo koji znak koji predstavlja cifru - [0-9]
\D - bilo koji znak koji NE predstavlja cifru - [^0-9]
\s - bilo koji whitespace znak (razmak, tab, prelazak u novi red)
\S - bilo koji znak koji NIJE whitespace
\w - bilo koji znak iz skupa znakova: [a-zA-Z0-9_]
\W - bilo koji znak koji nije iz skupa znakova: [a-zA-Z0-9_]
\b - početak ili kraj reči (engl. "boundary")
Ugrađene klase znakova ("prečice") svakako dobro dođu, ali, ako situacija zahteva dodatnu preciznost - moramo biti precizniji.
Na primer, ako nam je potreban regex koji prepoznaje polja iz šahovske notacije, gornji regex ("znak-cifra") neće biti od pomoći (šahovska polja "M8", "Z6" i sl. - ne postoje), pa moramo biti skroz precizni:
[A-H][1-8]
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 sami definišemo (u smislu toga šta se može, a šta ne može, postići preko jednostavnih regularnih izraza), razmotrićemo još jedan jednostavan primer.
Unećemo u otvorenu datoteku imenicu "Petar" u svih sedam padežnih oblika ....
Petar
Petra
Petru
Petra
Petre
Petrom
Petru
.... i probati, uz uključenu podršku za prepoznavanje velikih i malih slova, nekoliko regularnih izraza (tražimo regularni izraz koji će obuhvatiti svih sedam reči).
Pokušaj #1:
Pet[ar]
Uviđamo odmah da program pronalazi (pod)niske dužine 4 u svim padežnim oblicima.
Znakovi 'a' i 'r' u velikoj zagradi ("[ar]") su, iako liče na nastavak nominativa, samo nezavisni znakovi koji se kombinuju sa prethodećom niskom "Pet" (baš kao i u prvom primeru), tako da se praktično odvijaju dve nezavisne pretrage: "Peta" i "Petr".
Međutim, regularni izraz za sada (naravno) ne prepoznaje svih sedam reči u celosti.
Ukoliko dopunimo obrazac na sledeći način (pokušaj #2):
Pet[ar][raueo]
.... 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 može (prirodno) pojaviti pretpostavka da bi se sledećom dopunom (pokušaj #3) ....
Pet[ar][raueo][m]
.... mogao rešiti problem, međutim, ovakav izraz (iako je ispravan sam po sebi) - zapravo će pronaći samo instrumental imenice Petar ("Petrom").
Ukoliko ne definišemo da je moguće da se neki od znakova u velikim zagradama ne pojavi, smatra se da svaki od četiri dela pretrage mora pojaviti (što nije praktično, jer u našem slučaju samo jedan od padeža ima šest slova).
Problem se rešava upotrebom tzv. kvantifikatora.
Kvantifikatori - ?, + i *
Regex kvantifikatori su specijalni znakovi kojima se navodi koliko puta se ponavlja određeni znak, ili klasa znakova:
? - Klasa znakova pojavljuje se jednom ili nijednom
* - Klasa znakova pojavljuje se: nijednom, jednom, ili više puta
+ - Klasa znakova pojavljuje se jednom ili više puta
Shodno navedenim pravilima, u izraz koji smo prethodno definisali možemo dodati kvantifikator "?".
Pet[ar][raueo][m]?
Kada dodamo kvantifikator "?" (to je to, samo jedan dodatni znak, ali - pravi), dozvoljavamo da se četvrti član pretrage (znak "m") pojavi jednom ili nijednom, što omogućava svih sedam poklapanja.
Ukoliko bismo koristili znak "*", to bi značilo da poslednji član pretrage (malo "m") može da se pojavi i više puta, ali - to nije dobar obrazac za pretragu u našem slučaju (proverite sami).
Pogledajmo još nekoliko jednostavnih regularnih izraza koji se daju formirati uz korišćenje do sada pomenutih opcija.
[0-9a-fA-F] - Heksadekadna cifra
[0-9a-fA-F]+ - Heksadekadni broj (više ponavljanja bilo koje od
heksadekadnih cifara)
((0[xX])?[0-9a-fA-F])+ - Heksadekadni broj u programskom jeziku C
(i srodnim jezicima)
#[0-9a-fA-F]* - Heksadekadni broj u CSS-u
[A-Za-z_][A-Za-z_0-9]* - identifikator promenljive (ali - ovakav regex je
dobar samo za otkrivanje loše formatiranih
identifikatora - u većem tekstu,
izabraće većinu reči)
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" biće takođe 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 ....
Operatori "{m, n}" i "|"
Ako sa odrednicom za jednu cifru ("?") ne "zahvatamo" dovoljno veliki raspon, a sa odrednicom za proizvoljan broj cifara ("*") nemamo kontrolu i preciznost, možemo koristiti kvantifikator raspona "{}" i u vitičastim zagradama navesti broj ponavljanja.
U opštem smislu (za x = 3 i y = 5), važi sledeće:
{x} - Broj ponavljanja je x (3)
{y,} - Broj ponavljanja je x (3) ili više
{x,y} - Broj ponavljanja je u rasponu od x do y (3, 4, ili 5)
.... pa bi se regex za pronalaženje heksadekadnih brojeva dao izvesti na sledeći način:
#[0-9a-fA-F]{6}
Izraz sada traži sve niske koje počinju heštegom "#" posle koga sledi šest heksadekadnih cifara i u pitanju je adekvatno rešenje za ("neformalne") situacije u kojima (u tekstu) nema mnogo heksadekadnih brojeva, pri čemu znamo da je korišćen samo šestocifreni hex zapis.
Ako hoćemo da budemo precizni, ili ako hoćemo da obuhvatimo i heksadekadni zapis boja sa osam cifara (poslednje dve su Alfa kanal), "moramo tražiti dalje" ....
Ako pokušamo da pretragom obuhvatimo i osmocifreni zapis boja, preko sledećeg izraza:
#[0-9a-fA-F]{6,8}
.... ponovo nećemo "dobro proći", jer će program svaki put kada pronađe prvih šest cifara - praktično zanemariti to da li nakon šeste cifre ima i drugih cifara, (kao i u prethodnom primeru, biće označeno samo prvih šest cifara i nakon toga je svejedno da li ima cifara posle šeste, 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 6 ili 8 cifara, uradićemo sledeće: ukoliko umesto raspona definišemo dva zasebna regularna izraza i spojimo ih preko operatora "|" (logičko ILI) - pri čemu koristimo i klasu znakova "\b" ("boundary" - kraj reči) ....
#[0-9a-fA-F]{6}\b|#[0-9a-fA-F]{8}\b
.... dobićemo upravo ono što smo tražili: "izraze koji traže niske koje počinju znakom '#', posle čega sledi šest ili osam znakova koji predstavljaju heksadekadne cifre" - nakon čega se reč završava.
U svemu navedenom, upotreba klase znakova \b
je od (veoma) velike važnosti, jer, ukoliko ne upotrebimo navedenu klasu znakova (kao "signal" da želimo da uzmemo u obzir granice reči), programi će i daje uzimati u obzir niske koje se ne završavaju posle šeste cifre!
Grupisanje operatora "()"
Male zagrade možemo upotrebiti za grupisanje celina unutar izraza (pogledajmo primere):
1. (mono|motoci)kl
2. (#[0-9a-fA-F]{6}\b)?
Prvi izraz tražiće dve reči: "monokl" i "motocikl", dok će drugi izraz tražiti heksadekadni zapis boje, koji se može (ali i ne mora) naći na datom mestu.
Tipični primeri upotrebe regularnih izraza
Da bismo zaokružili uvodnu priču o regularnim izrazima, sagledaćemo detaljno nekoliko tipičnih primera.
Brojevi telefona
Ponešto pojednostavljen izraz koji vidimo ispod, prepoznavaće brojeve telefona sa pozivnim brojem za gradove ili mobilne mreže, ali bez pozivnog broja za državu:
0[0-9]{2}[/-][0-9]{3,4}-?[0-9]{3,4}
Posmatrajmo izraz po delovima:
0 - Niska mora počinjati cifrom "0",
[0-9]{2}[/-] - sledeća dva znaka moraju biti cifre od 0 do 9,
a posle njih mora se naći znak "/" ili znak "-",
[0-9]{3,4} - Posle navedenih mora postojati grupa od tri ili četiri cifre,
-? - Zatim znak "-", koji se može, ali i ne mora pojaviti,
[0-9]{3,4} - ... i na kraju ponovo grupa od tri ili četiri cifre
Da li se ovakav izraz može pojednostaviti upotrebom prečica za klase znakova?
Svakako da može:
0\d{2}[/-]\d{3,4}-?\d{3,4}
Niska "\d" u regularnim izrazima predstavlja cifre i njena pojava odgovara pojavi klase znakova "[0-9]".
E-mail adrese
Regularni izraz za prepoznavanje e-mail adresa ima sledeći oblik (pretpostavljamo da ga se sećate od ranije):
[\w\.]*@(\w*\.){1, 10}\w*
Posmatrajmo izraz po delovima:
[\w\.]* - Tražimo prvo nisku znakova koja predstavlja korisničko ime
(koje se može sastojati samo od navedenih znakova - \w je
isto što i [a-zA-Z0-9_], a tačku, koja je specijalni znak
u regularnim izrazima, ali može biti deo korisničkog imena
u email adresi, navešćemo preko escape sekvence "\.")
@ - Posle korisničkog imena mora uslediti znak "@"
(\w*\.){1, 10} - U jednostavnim e-mail adresama, ovo je prvi deo domena
("google." u "google.com"), a u većim adresama, ovo je
celokupan domen izuzev poslednjeg nastavka
\w* - Završetak domena, u jednostavnim adresama drugi deo
("com" u "gogle.com"), a u komplikovanijim poslednji deo
Ovde možemo primetiti jednu krajnje zgodnu stvar u regularnim izrazima: ukoliko upotrebimo zagrade, možemo precizno definisati dejstvo kvantifikatora "*" i "{1, 10}".
Kvantifikator "*", koji je u zagradi, odnosi se na klasu \w, odnosno navodi da se bilo koji od znakova te klase može pojaviti proizvoljan broj puta (posle čega mora uslediti tačka). Kvantifikator "{1, 10}" definiše da se celokupan izraz koji smo maločas definisali u zagradi može pojaviti od jedan do deset puta.
Prepoznavanje HTML tagova
Prepoznavanje HTML tagova možemo izvesti na jednostavan način, upotrebom sledećeg obrasca:
<[a-z/\'\.= ]*>
Da pojasnimo: niska počinje znakom "<", posle čega se znakovi od "a" do "z", znak "/", znaci navoda, tačka, znak jednako i praznina pojavljuju proizvoljan broj puta, a niska se završava znakom ">" (ovakav regularni izraz prepoznaje i "otvarajuće" tagove, sa ili bez atributa i "zatvarajuće" tagove).
Preko prethodnog izraza, lako možemo izdvojiti izabrane tagove zarad kopiranja, obrisati ih i sl.
Sledeći koraci ....
Tehnike koje smo predstavili u članku (naravno) nisu "sve što se može reći" o regularnim izrazima, ali, za početak ima sasvim dovoljno "materijala" i već sada (uz korišćenje stečenog znanja i ponešto truda) sami možete kreirati regularne izraze koji sasvim dobro dođu u svakodnevnom radu.
Iako se opcije mogu isprobavati u editoru koji inače koristite, preporučujemo i jedan koristan sajt ....
Regex 101... koji u svemu može dodatno pomoći.
Uz dobro vladanje prečicama, 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 na jednostavan i efikasan način.