· Št. online gostov: 2
· Št. online članov: 0
· Vseh članov: 712
· Najnovejši član: lega
|
|
Prva stvar, ki bi jo rad omenil pri naprednih regexih je, da popoln regex ne obstaja, vedno se najde kak, ki je se natancnejsi, se hitrejsi, se preprostejsi ... zato se gre tu vecinoma za najboljsi priblizek kar ga lahko najdemo v danem casu.
Pomemben del phpjeve regex podpore je podpora unicode lastnosti znakov, ki jo omogocimo z modifierjem u. Te lastnosti nam pomagajo pri matchanju vseh crk, vseh stevil ipd., ker pac ne moremo predvideti kateri vsi znaki so velika tiskana crka v vseh jezikih. V regexu te lastnosti oznacimo z p{xx}, kjer xx predstavlja lastnost npr. L za crko (letter). Spisek vseh lastnosti si lahko ogledate na tu ali pa v php manualu pri opisu regexov. Te lastnosti negiramo z uporabo velikega p: P{xx}, vendar mi je osebno ljubsi podoben nacin kot negiranje character classa p{^xx}, ki v php manualu ni omenjen kot podprt vendar je (vecina regex interpreterjev tega ne podpira, poleg phpja vem samo se za perl). V opozorilo vsem, ki se bavijo z razlicnimi jeziki: Python unicode lastnosti sploh ne podpira.
Drug zelo pomemben del naprednejsega pisanja regexov pa je natancno oznacevanje stevila ponovitev v zavitih oklepajih (namesto + in * kvantifikatorja) po kljucu {min, max}. Primer:
[koda]
// matcha vse skupke pet crk
preg_match_all( '#p{L}{5}#u', $besedilo, $besede );
// matcha vse skupke vsaj pet crk
preg_match_all( '#p{L}{5,}#u', $besedilo, $besede );
// matcha vse skupke od ene do petih crk
preg_match_all( '#p{L}{1,5}#u', $besedilo, $besede );
// proti pricakovanjem NE matcha do pet crk ampak razume {,5} kot dobeseden string
preg_match_all( '#p{L}{,5}#u', $besedilo, $besede );
?>[/koda]
Velikokrat se v regexih uporabljajo tudi t.i. backreference, ki nam omogocajo, da v regexu uporabimo prej matchan subpattern ali del matcha uporabimo v zamenjavi. Tako bi lahko html stripali nepotrebnih tagov nekako takole:
[koda]
$input = preg_replace( '#<([a-z]+).+?>(.*?)1>#i', '$2', $input );
?>[/koda]
Kot lahko vidite smo tu najprej uporabili backreference v regexu in tako poskrbeli, da se zapirajoci html tag ujema z odpirajocim, v zamenjalnem stringu pa smo backreference uporabili, da smo namesto celotnega matcha vstavili samo osrednji del.
Ampak kaj ce hocemo zamenjati vse tage razen nekaterih? No lahko bi spisali regex, kjer bi character class zamenjali s spiskom prepovedanih tagov, kar bi odprlo vrata vsem nepredvidenim. Lahko bi najprej matchali vse tage potem pa z zanko preverjali, kateri so dovoljeni in kateri ne ter delali zamenjave, kar pa je casovno potratno. Zato raje uporabimo asercije (assertion).
Asercije so v osnovi subpatterni, ki nekaj preverijo a tega ne matchajo, kar je sprva tezko razumljivo in po pravici povedano je to vedno nekaksen vudu, ki dela ali ne in se redkokdaj obnasa po izhodnih pricakovanjih. V osnovi poznamo dva tipa asercij; naprej in nazaj. Pisemo jih kot obicajne subpatterne, le da subpattern zacnemo z ?= za pozitivno asercijo ali ?! za negativno (podobno je ?<= in ? za nazaj asercije. Se najlazje si jih predstavljamo kot nekaksen if stavke ...
Tako bi lahko prejsnji problem resili nekako takole:
[koda]
$input = preg_replace( '#<([a-z]+)(?(.*?)1>', '$2', $input );
?>[/koda]
kar bi izbrisalo vse tage razen em, strong, b in i.
Podobno bi z naprej asercijo lahko v besedilu iskali vse janeze, ki niso novaki:
[koda]
preg_match_all( '#janez(?! novak)(p{L}+)#ui', $besedilo, $matches );
?>[/koda]
V $matches bi dobili vse janeze z njihovimi priimki in nihce od njih se ne bi pisal novak. Problem bi seveda nastal, ker se v slovenscini mosko ime sklanja skupaj s priimkom in bi zato morali upostevati samostalnik janez z vsemi koncnicami, v grobem nekaj takega:
[koda]
preg_match_all( '#janez(?:a|u|ov|e){0,1}(?! novak)(p{L}+)#iu', $besedilo, $matches );
?>[/koda]
Verjetno sem spustil marsikatero koncnico, ampak zelel sem prikazati se eno super lastnost subpatternov; namrec, ce ga zacnemo z ?: subpattern deluje isto kot ponavadi le, da je noncapturing in nam torej ne mece svojih matchev v array z rezultati.
Mimogrede, asercije lahko tudi druzimo skupaj, tako da lahko v besedilu iscemo npr. vsa stevila vecja od 100 a manjsa od 900 nekako takole:
[koda]
preg_match_all( '#(?
?>[/koda]
Ideja tu je, da najprej matchamo vse serije treh stevilk, ki sledijo nestevilom, potem se z asercijo najprej prepricamo, da ta stevila niso tipa 0xx, z naslednjo asercijo se prepricamo, da to niso stevila tipa 9xx (se pravi so vecja od 100 in manjsa od 900), s se naslednjo asercijo pa se prepricamo, da jim sledijo nestevilke. Pri vsem skupaj se je potrebno zavedati, da, ker so asercije v resnici dolge nic znakov, se vse zadnje tri asercije izvrsijo na istem mestu v stringu.
Za skoraj konec si poglejmo se pogojne subpatterne, ki nam omogocajo matchanje stringa pod nekim pogojem. Tako lahko preverjamo, ce je string zacetek "abecede" nekako takole:
[koda]
if ( preg_match( '#(?(^p{N})123|abc)#u', $string ) )
?>[/koda]
kar bi, ce je prvi znak stevilka, preverilo ce string matcha 123, drugace pa bi poskusilo z abc ... kar je seveda bolj neumen primer, ampak primere si je pac tezko izmisljevati.
Za konec pa se en bonboncek, sad dolgih dni truda in izboljsav (na zalost ne morem pokazati zadnje verzije, ta je predpredzadnja):
#((p{L}+(p{^L}+|^)){0,5}((?<=[^p{L}])Farting(?=[^p{L}])|(?<=[^p{L}])farting(?=[^p{L}])|(?<=[^p{L}])farts(?=[^p{L}])|(?<=[^p{L}])fart(?=[^p{L}]))((p{^L}+|$)p{L}+){0,5})|(^(([^p{L}]+|$)p{L}+){0,6})#u
To matcha okolico petih besed naprej in nazaj okoli razlicnih oblik besede 'fart' in sest besed na zacetku stringa ter, v prihodnjih verzijah, sest besed na koncu stringa.
Upam, da sem vam v tem clanku predstavil nekaj lastnosti in trikov v regexih, ki jih se niste vedeli, se vec o regexih pa si lahko preberete na php manualu, ce pa vam bo kdaj dolgcas se oglasite na mojem blogu. |
#1 |
na 07.08.2010 ob 07:48
|
|
|
Za komentiranje se morate prijaviti.
|
|
|
|
Za pošiljanje sporočil morate biti prijavljeni.
|
|