A szabályos kifejezések

A szövegek vizsgálatának és elemzésének nagyszerû módja a szabályos kifejezések
(regular expressions) használata. Ezek lehetõvé teszik, hogy egy karakterláncon
belül mintákat keressünk, a találatokat pedig rugalmasan és pontosan kapjuk
vissza. Mivel hatékonyabbak az elõzõ órában tanult karakterlánc-kezelõ függvé-
nyeknél, lassabbak is azoknál. Így azt tanácsoljuk, hogy ha nincs különösebb
szükségünk a szabályos kifejezések által biztosított hatékonyságra, inkább haszn
áljuk a hagyományos karakterlánc-kezelõ függvényeket.
A PHP a szabályos kifejezések két típusát támogatja. Egyrészt támogatja a Perl által
használtakat, az azoknak megfelelõ függvényekkel, másrészt a korlátozottabb
tudású POSIX szabályos kifejezés formát. Mindkettõvel meg fogunk ismerkedni.
Az óra során a következõkrõl tanulunk:
• Hogyan illesszünk mintát egy karakterláncra a szabályos kifejezések
segítségével?
• Mik a szabályos kifejezések formai követelményei?
• Hogyan cseréljünk le karakterláncokat szabályos kifejezésekkel?
• Hogyan keressünk és cseréljünk le mintákat egy szövegben a hatékony Perl
típusú szabályos kifejezésekkel?
A POSIX szabályos kifejezések függvényei
A POSIX szabályos kifejezéseket kezelõ függvények lehetõvé teszik, hogy egy szö-
vegben bonyolult mintákat keressünk és cseréljünk le. Ezeket általában egyszerû-
en szabályoskifejezés-függvényeknek szokták nevezni, mi azonban POSIX szabá-
lyoskifejezés-függvényekként utalunk rájuk, egyrészt azért, hogy
megkülönböztethessük õket a hasonló, ám hatékonyabb Perl típusú szabályos
kifejezésektõl, másrészt azért, mert a POSIX bõvített szabályos kifejezés (extended
regular expression) szabványát követik.
A szabályos kifejezések olyan jelegyüttesek, amelyek egy szövegben egy mintára
illeszkednek. Használatuk elsajátítása így jóval többet jelent annál, hogy megtanuljuk
a PHP szabályoskifejezés-függvényeinek be- és kimeneti paramétereit.
Az ismerkedést a függvényekkel kezdjük és rajtuk keresztül vezetjük be az olvasót
a szabályos kifejezések formai követelményeibe.
Minta keresése karakterláncokban
az ereg() függvénnyel
Az ereg() bemenete egy mintát jelképezõ karakterlánc, egy karakterlánc, amiben
keresünk, és egy tömbváltozó, amelyben a keresés eredményét tároljuk. A függvény
egy egész számot ad vissza, amely a megtalált minta illeszkedõ karaktereinek számát
adja meg, illetve ha a függvény nem találja meg a mintát, a false (hamis) értéket
adja vissza. Keressük a "tt"-t az "ütõdött tánctanár" karakterláncban.
print ereg("tt", "ütõdött tánctanár", $tomb);
print "
$tomb[0]
";
// a kimenet:
// 2
// tt
A "tt" az "ütõdött" szóban megtalálható, ezért az ereg() 2-t ad vissza, ez
az illeszkedõ betûk száma. A $tomb változó elsõ elemében a megtalált karakterl
ánc lesz, amit aztán a böngészõben megjelenítünk. Felmerülhet a kérdés, milyen
esetben lehet ez hasznos, hiszen elõre tudjuk, hogy mi a keresett minta. Nem kell
azonban mindig elõre meghatározott karaktereket keresnünk. A pontot (.) haszn
álhatjuk tetszõleges karakter keresésekor:
340 18. óra
print ereg("d.", "ütõdött tánctanár", $tomb);
print "
$tomb[0]
";
// a kimenet:
// 2
// dö
A d. minta illeszkedik minden olyan szövegre, amely megfelel annak a meghatá-
rozásnak, hogy „egy d betû és egy azt követõ tetszõleges karakter”. Ebben az esetben
nem tudjuk elõre, mi lesz a második karakter, így a $tomb[0] értéke máris
értelmet nyer.
Egynél többször elõforduló karakter keresése
mennyiségjelzõvel
Amikor egy karakterláncban egy karaktert keresünk, egy mennyiségjelzõvel
(kvantorral) határozhatjuk meg, hányszor forduljon elõ a karakter a mintában.
Az a+ minta például azt jelenti, hogy az "a"-nak legalább egyszer elõ kell fordulnia,
amit 0 vagy több "a" betû követ. Próbáljuk ki:
if ( ereg("a+","aaaa", $tomb) )
print $tomb[0];
// azt írja ki, hogy "aaaa";
Vegyük észre, hogy ez a szabályos kifejezés annyi karakterre illeszkedik, amennyire
csak tud. A 18.1. táblázat az ismétlõdõ karakterek keresésére szolgáló mennyiségjelz
õket ismerteti.
18.1. táblázat Az ismétlõdõ karakterek mennyiségjelzõi
Jel Leírás Példa Erre Erre nem
illeszkedik illeszkedik
* Nulla vagy több elõfordulás a* xxxx (Mindenre
illeszkedik)
+ Egy vagy több elõfordulás a+ xaax xxxx
? Nulla vagy egy elõfordulás a? xaxx xaax
{n} n elõfordulás a{3} xaaa aaaa
{n,} Legalább n elõfordulás a{3,} aaaa aaxx
{,n} Legfeljebb n elõfordulás a{,2} xaax aaax
{n1,n2} Legalább n1 és legfeljebb a{1,2} xaax xaaa
n2 elõfordulás
A szabályos kifejezések használata 341
18
A kapcsos zárójelben levõ számok neve korlát. Segítségükkel határozhatjuk meg,
hányszor ismétlõdjön egy adott karakter, hogy a minta érvényes legyen rá.
A korlát határozza meg, hogy egy karakternek vagy karakterláncnak
hányszor kell ismétlõdnie egy szabályos kifejezésben. Az alsó és felsõ
határt a kapcsos zárójelen belül határozzuk meg. Például az
a{4,5}
kifejezés az a négynél nem kevesebb és ötnél nem több elõfordulására
illeszkedik.
Vegyünk egy példát. Egy klub tagsági azonosítóval jelöli tagjait. Egy érvényes azonos
ítóban egy és négy közötti alkalommal fordul elõ a "t", ezt tetszõleges számú
szám vagy betû karakter követi, majd a 99-es szám zárja. A klub arra kért fel
bennünket, hogy a tennivalókat tartalmazó naplóból emeljük ki a tagazonosítókat.
$proba = "A ttXGDH99 tagunk befizette már a tagsági
díjat?";
if ( ereg( "t{1,4}.*99 ", $proba, $tomb ) )
print "A megtalált azonosító: $tomb[0]";
// azt írja ki, hogy "A megtalált azonosító: ttXGDH99"
Az elõzõ kódrészletben a tagazonosító két t karakterrel kezdõdik, ezt négy
nagybetû követi, majd végül a 99 jön. A t{1,4} minta illeszkedik a két t-re,
a négy nagybetût pedig megtalálja a .*, amellyel tetszõleges számú, tetszõleges
típusú karakterláncot kereshetünk.
Úgy tûnik, ezzel megoldottuk a feladatot, pedig még attól meglehetõsen távol
állunk. A feltételek között a 99-cel való végzõdést azzal próbáltuk biztosítani,
hogy megadtuk, hogy egy szóköz legyen az utolsó karakter. Ezt aztán találat
esetén vissza is kaptuk. Még ennél is nagyobb baj, hogy ha a forráslánc
"Azonosítóm ttXGDH99 megkaptad már az 1999 -es tagsági díjat?"
akkor a fenti szabályos kifejezés a következõ karakterláncot találná meg:
"tóm ttXGDH99 megkaptad már az 1999"
Mit rontottunk el? A szabályos kifejezés megtalálta a t betût az azonosítóm
szóban, majd utána a tetszõleges számú karaktert egészen a szóközzel zárt 99-ig.
Jellemzõ a szabályos kifejezések „mohóságára”, hogy annyi karakterre illeszkednek,
amennyire csak tudnak. Emiatt történt, hogy a minta egészen az 1999-ig
illeszkedett, a 99-re végzõdõ azonosító helyett. A hibát kijavíthatjuk, ha biztosan
tudjuk, hogy a t és a 99 közötti karakterek kizárólag betûk vagy számok és
egyikük sem szóköz. Az ilyen feladatokat egyszerûsítik le a karakterosztályok.
ÚJDONSÁG
342 18. óra
Karakterlánc keresése karakterosztályokkal
Az eddigi esetekben megadott karakterekkel vagy a . segítségével kerestünk
karaktereket. A karakterosztályok lehetõvé teszik, hogy karakterek meghatározott
csoportját keressük. A karakterosztály meghatározásához a keresendõ karaktereket
szögletes zárójelek közé írjuk. Az [ab] minta egyetlen betûre illeszkedik, ami
vagy a vagy b. A karakterosztályokat meghatározásuk után karakterként kezelhetj
ük, így az [ab]+ az aaa, bbb és ababab karakterláncokra egyaránt illeszkedik.
Karaktertartományokat is használhatunk karakterosztályok megadására: az [a-z]
tetszõleges kisbetûre, az [A-Z] tetszõleges nagybetûre, a [0-9] pedig tetszõ-
leges számjegyre illeszkedik. Ezeket a sorozatokat és az egyedi karaktereket
egyetlen karakterosztályba is gyúrhatjuk, így az [a-z5] minta tetszõleges kisbet
ûre vagy az 5-ös számra illeszkedik.
A karakterosztályokat „tagadhatjuk” is, a csúcsos ékezet (^) kezdõ szögletes zárójel
után való írásával: az [^A-Z] mindenre illeszkedik, csak a nagybetûkre nem.
Térjünk vissza legutóbbi példánkra. Mintát kell illesztenünk egy 1 és 4 közötti
alkalommal elõforduló betûre, amelyet tetszõleges számú betû vagy számjegy
karakter követ, amit a 99 zár.
$proba = "Azonosítóm ttXGDH99 megkaptad már az 1999 -es
tagsági díjat?";
if ( ereg( "t{1,4}[a-zA-Z0-9]*99 ", $proba, $tomb ) )
print "A megtalált azonosító: $tomb[0]";
// azt írja ki, hogy "A megtalált azonosító: ttXGDH99 "
Szép lassan alakul. Az a karakterosztály, amit hozzáadtunk, már nem fog szóköz-
ökre illeszkedni, így végre az azonosítót kapjuk meg. Viszont ha az azonosító után
a próba-karakterlánc végére vesszõt írunk, a szabályos kifejezés megint nem illeszkedik:
$proba = "Azonosítóm ttXGDH99, megkaptad már az 1999 -es
tagsági díjat?";
if ( ereg( "t{1,4}[a-zA-Z0-9]*99 ", $proba, $tomb ) )
print "A megtalált azonosító: $tomb[0]";
// a szabályos kifejezés nem illeszkedik
Ez azért van, mert a minta végén egy szóközt várunk el, amely alapján meggyõ-
zõdhetünk, hogy elértük az azonosító végét. Így ha egy szövegben az azonosító
zárójelek közt van, kötõjel vagy vesszõ követi, a minta nem illeszkedik rá. Közelebb
visz a megoldáshoz, ha úgy javítunk a szabályos kifejezésen, hogy mindenre
illeszkedjék, csak szám és betû karakterekre nem:
A szabályos kifejezések használata 343
18
$proba = "Azonosítóm ttXGDH99, megkaptad már az 1999 -es
tagsági díjat?";
if ( ereg( "t{1,4}[a-zA-Z0-9]*99[^a-zA-Z0-9]", $proba,
$tomb ) )
print "A megtalált azonosító: $tomb[0]";
// azt írja ki, hogy "A megtalált azonosító: ttXGDH99, "
Már majdnem kész is vagyunk, de még mindig van két probléma. Egyrészt
a vesszõt is visszakapjuk, másrészt a szabályos kifejezés nem illeszkedik, ha
az azonosító a karakterlánc legvégén van, hiszen megköveteltük, hogy utána
még egy karakter legyen. Más szóval a szóhatárt kellene megbízható módon
megtalálnunk. Erre a problémára késõbb még visszatérünk.
Az atomok kezelése
Az atom egy zárójelek közé írt minta (gyakran hivatkoznak rá részmintak
ént is). Meghatározása után az atomot ugyanúgy kezelhetjük, mintha
maga is egy karakter vagy karakterosztály lenne. Más szóval ugyanazt a 18.1. táblá-
zatban bemutatott rendszer alapján felépített mintát annyiszor kereshetjük, ahányszor
csak akarjuk.
A következõ kódrészletben meghatározunk egy mintát, zárójelezzük, és
az így kapott atomnak kétszer kell illeszkednie a keresendõ szövegre:
$proba = "abbaxaabaxabbax";
if ( ereg( "([ab]+x){2}", $proba, $tomb ) )
print "$tomb[0]";
// azt írja ki, hogy "abbaxaabax"
Az [ab]+x illeszkedik az "abbax" és az "aabax" karakterláncokra egyaránt,
így az ([ab]+x){2} illeszkedni fog az "abbaxaabax" karakterláncra.
Az ereg() függvénynek átadott tömbváltozó elsõ elemében kapjuk vissza
a teljes, megtalált, illeszkedõ karakterláncot. Az ezt követõ elemek az egyes
megtalált atomokat tartalmazzák. Ez azt jelenti, hogy a teljes találat mellett
a megtalált minta egyes részeihez is hozzáférhetünk.
A következõ kódrészletben egy IP címre illesztünk mintát, és nem csupán a teljes
címet tároljuk, hanem egyes alkotóelemeit is:
ÚJDONSÁG
344 18. óra
$ipcim = "158.152.55.35";
if ( ereg( "([0-9]+)\.([0-9]+)\.([0-9]+)\.([0-9]+)", $ipcim,
$tomb ) )
{
foreach ( $tomb as $ertek )
print "$ertek
";
}
// Az eredmény:
// 158.152.55.35
// 158
// 152
// 55
// 35
Vegyük észre, hogy a pontokat a szabályos kifejezésben fordított perjel elõzte
meg. Ezzel fejeztük ki, hogy a . karaktert itt minden különleges tulajdonsága
nélkül, egyszerû karakterként szeretnénk kezelni. Minden olyan karakternél ez
a követendõ eljárás, amely egyedi szereppel bír a szabályos kifejezésekben.
Elágazások
A szûrõkarakter (|) segítségével mintákat összekötve a szabályos kifejezéseken
belül elágazásokat hozhatunk létre. Egy kétágú szabályos kifejezés vagy az elsõ
mintára, vagy a második mintára illeszkedõ karakterláncokat keresi meg. Ettõl lesz
a szabályos kifejezések nyelvtana még rugalmasabb. A következõ kódrészletben
a .com, a .de vagy a .hu karakterláncot keressük:
$domain = "www.egydomain.hu";
if ( ereg( "\.com|\.de|\.hu", $domain, $tomb ) )
print "ez egy $tomb[0] tartomány
";
// azt írja ki, hogy "ez egy .hu tartomány"
A szabályos kifejezés helye
Nemcsak a keresendõ mintát adhatjuk meg, hanem azt is, hol illeszkedjen egy karakterl
áncban. Ha a mintát a karakterlánc kezdetére szeretnénk illeszteni, a szabá-
lyos kifejezés elejére írjunk egy csúcsos ékezetet (^). A ^a illeszkedik az "alma",
de nem illeszkedik a "banán" szóra.
A karakterlánc végén úgy illeszthetjük a mintát, hogy dollárjelet ($) írunk a szabályos
kifejezés végére. Az a$ illeszkedik a "róka", de nem illeszkedik a "hal" szóra.
A szabályos kifejezések használata 345
18
A tagazonosítót keresõ példa újragondolása
Most már rendelkezésünkre állnak azok az eszközök, melyekkel megoldhatjuk
a tagazonosítás problémáját. Emlékeztetõül, a feladat az volt, hogy szövegeket
elmezve kigyûjtsük azokat a tagazonosítókat, amelyekben a "t" egy és négy
közötti alkalommal fordul elõ, ezt tetszõleges számú szám vagy betû karakter
követi, amelyet a 99 zár. Jelenlegi problémánk az, hogyan határozzuk meg, hogy
illeszkedõ mintánknak szóhatárra kell esnie. Nem használhatjuk a szóközt, mivel
a szavakat írásjelekkel is el lehet választani. Az sem lehet feltétel, hogy egy nem
alfanumerikus karakter alkossa a határt, hiszen a tagazonosító lehet, hogy éppen
a szöveg elején vagy végén található.
Most, hogy képesek vagyunk elágazásokat megadni és helyhez kötni a szabályos
kifejezéseket, megadhatjuk feltételként, hogy a tagazonosítót vagy egy nem szám
vagy betû karakter kövesse, vagy pedig a karakterlánc vége. Ugyanezzel a gondolatmenettel
adhatjuk meg a kód elején elõforduló szóhatárt is. A zárójelek haszná-
latával a tagazonosítót minden központozás és szóköz nélkül kaphatjuk vissza:
$proba = "Azonosítóm ttXGDH99, megkaptad már az 1999 -es
tagsági díjat?";
if ( ereg( "(^|[^a-zA-Z0-9])(t{1,4}[a-zA-Z0-9]*99)([^a-zAZ0-
9]|$)", $proba, $tomb ) )
print "A megtalált azonosító: $tomb[2]";
// azt írja ki, hogy "A megtalált azonosító: ttXGDH99"
Amint láthatjuk, a szabályos kifejezések elsõre egy kicsit riasztók, de miután kisebb
egységekre bontjuk azokat, általában egész könnyen értelmezhetõk. Elértük, hogy
mintánk egy szóhatárra illeszkedjen (pontosan úgy, ahogy azt a feladat megkövetelte).
Mivel nem vagyunk kíváncsiak semmilyen, a mintát megelõzõ vagy követõ
szövegre, a minta számunkra hasznos részét zárójelek közé írjuk. Az eredményt
a $tomb tömb harmadik (2-es indexû) elemében találhatjuk meg.
346 18. óra
Az ereg() megkülönbözteti a kis- és nagybetûket. Ha nem
szeretnénk megkülönböztetni õket, használjuk az eregi() függv
ényt, amely egyébként minden más tekintetben megegyezik
az ereg() függvénnyel.
Minták lecserélése karakterláncokban
az ereg_replace() függvénnyel
Eddig csupán mintákat kerestünk a karakterláncokban, magát a karakterláncot
nem módosítottuk. Az ereg_replace() függvény lehetõvé teszi, hogy megkeress
ük és lecseréljük az adott szöveg egy mintára illeszkedõ részláncát. Bemenete
három karakterlánc: egy szabályos kifejezés, a csereszöveg, amivel felül szeretn
énk írni a megtalált mintát és a forrásszöveg, amelyben keresünk. Találat esetén
a függvény a megváltoztatott karakterláncot adja vissza, ellenkezõ esetben az eredeti
forrásszöveget kapjuk. A következõ kódrészletben egy klubtisztviselõ nevét
keressük ki, majd felülírjuk utódjának nevével:
$proba = "Titkárunk, Vilmos Sarolta, szeretettel
üdvözli önt.";
print ereg_replace ("Vilmos Sarolta", "László István",
$proba);
// azt írja ki, hogy " Titkárunk, László István,
szeretettel üdvözli önt."
Fontos megjegyezni, hogy míg az ereg() függvény csak a legelsõ megtalált mint
ára illeszkedik, az ereg_replace() a minta összes elõfordulását megtalálja és
lecseréli.
Visszautalás használata az ereg_replace() függvénnyel
A visszautalások azt teszik lehetõvé, hogy az illeszkedõ kifejezés egy részét
felhasználhassuk a felülíró karakterláncban. Ehhez szabályos kifejezésünk összes
felhasználandó elemét zárójelbe kell írnunk. Ezekre a részminták segítségével
megtalált karakterláncokra két fordított perjellel és az atom számával hivatkozhatunk
(például \\1) a csereláncokban. Az atomok sorszámozottak, a sorszámozás
kívülrõl befelé, balról jobbra halad és \\1-tõl indul. A \\0 az egész illeszkedõ
karakterláncot jelenti.
A következõ kódrészlet a hh/nn/éééé formátumú dátumokat alakítja
éééé.hh.nn. formátumúvá:
$datumt = "12/25/2000";
print ereg_replace("([0-9]+)/([0-9]+)/([0-9]+)",
"\\3.\\1.\\2.", $datum);
// azt írja ki, hogy "2000.12.25"
Vegyük észre, hogy a fenti kód csereszövegében a pontok nem különleges értelm
ûek, így nem is kell \ jelet tenni eléjük. A . az elsõ paraméterben kapja a tetszõ-
leges karakterre illeszkedés különleges jelentését.
A szabályos kifejezések használata 347
18
Karakterláncok felbontása a split() függvénnyel
Az elõzõ órában már láttuk, hogy az explode() függvény segítségével
egy karakterláncot elemeire bontva tömbként kezelhetünk. Az említett függvény
hatékonyságát az csökkenti, hogy határolójelként csak egy karakterhalmazt
használhatunk. A PHP 4 split() függvénye a szabályos kifejezések hatékonys
ágát biztosítja, vele rugalmas határolójelet adhatunk meg. A függvény bemenete
a határolójelként használt minta és a forráslánc, amelyben a határolókat keressük,
kimenete pedig egy tömb. A függvény harmadik, nem kötelezõ paramétere
a visszaadandó elemek legnagyobb számát határozza meg.
A következõ példában egy elágazó szabályos kifejezéssel egy karakterláncot
bontunk fel, úgy, hogy a határolójel egy vesszõ és egy szóköz vagy egy szókö-
zökkel közrefogott és szó:
$szoveg = "almák, narancsok, körték és barackok";
$gyumolcsok = split(", | és ", $szoveg);
foreach ( $gyumolcsok as $gyumolcs )
print "$gyumolcs
";
// a kimenet:
// almák
// narancsok
// körték
// barackok
Perl típusú szabályos kifejezések
Ha Perlös háttérrel közelítjük meg a PHP-t, akkor a POSIX szabályos kifejezések
függvényeit valamelyest nehézkesnek találhatjuk. A jó hír viszont az, hogy
a PHP 4 támogatja a Perlnek megfelelõ szabályos kifejezéseket is. Ezek formája
még az eddig tanultaknál is hatékonyabb. Ebben a részben a két szabályoskifejez
és-forma közötti különbségekkel ismerkedünk meg.
348 18. óra
Az ereg_replace() függvény megkülönbözteti a kis- és nagybetû-
ket. Ha nem szeretnénk megkülönböztetni ezeket, használjuk
az eregi_replace() függvényt, amely minden más tekintetben
megegyezik az ereg_replace() függvénnyel.
Minták keresése a preg_match() függvénnyel
A preg_match() függvény három paramétert vár: egy szabályos kifejezést,
a forrásláncot és egy tömbváltozót, amelyben az illeszkedõ karakterláncokat adja
vissza. A true (igaz) értéket kapjuk vissza, ha illeszkedõ kifejezést talál és
false (hamis) értéket, ha nem. A preg_match() és az ereg_match() függv
ények közötti különbség a szabályos kifejezést tartalmazó paraméterben van.
A Perl típusú szabályos kifejezéseket határolójelek közé kell helyezni. Általában
perjeleket használunk határolójelként, bár más, nem szám vagy betû karaktert is
használhatunk (kivétel persze a fordított perjel). A következõ példában
a preg_match() függvénnyel egy h-valami-z mintájú karakterláncot keresünk:
$szoveg = "üvegház";
if ( preg_match( "/h.*z/", $szoveg, $tomb ) )
print $tomb[0];
// azt írja ki, hogy "ház"
A Perl típusú szabályos kifejezések és a mohóság
Alapértelmezés szerint a szabályos kifejezések megkísérelnek a lehetõ legtöbb
karakterre illeszkedni. Így a
"/h.*z/"
minta a h-val kezdõdõ és z karakterre végzõdõ lehetõ legtöbb karaktert közrefogó
karakterláncra fog illeszkedni, a következõ példában a teljes szövegre:
$szoveg = "ház húz hülyéz hazardíroz";
if ( preg_match( "/h.*z/", $szoveg, $tomb ) )
print $tomb[0];
// azt írja ki, hogy "ház húz hülyéz hazardíroz"
Viszont ha kérdõjelet (?) írunk a mennyiséget kifejezõ jel után, rávehetjük
a Perl típusú szabályos kifejezést, hogy egy kicsit mértékletesebb legyen. Így míg a
"h.*z"
minta azt jelenti, hogy „h és z között a lehetõ legtöbb karakter”, a
"h.*?z"
minta jelentése a „h és z közötti lehetõ legkevesebb karakter”.
A szabályos kifejezések használata 349
18
A következõ kódrészlet ezzel a módszerrel keresi meg a legrövidebb szót, amely
h-val kezdõdik és z-vel végzõdik:
$szoveg = "ház húz hülyéz hazardíroz";
if ( preg_match( "/h.*?z/", $szoveg, $tomb ) )
print $tomb[0];
// azt írja ki, hogy "ház"
A Perl típusú szabályos kifejezések és
a fordított perjeles karakterek
A Perl típusú szabályos kifejezésekben is vezérlõkarakterré változtathatunk bizonyos
karaktereket, ugyanúgy, mint ahogy azt karakterláncokkal tettük. A \t például
a tabulátor karaktert jelenti, az \n pedig a soremelést. Az ilyen típusú szabályos
kifejezések olyan karaktereket is leírhatnak, amelyek teljes karakterhalmazokra,
karaktertípusokra illeszkednek. A fordított perjeles karakterek listáját a 18.2. táblá-
zatban láthatjuk.
18.2. táblázat Karaktertípusokra illeszkedõ beépített karakterosztályok
Karakter Illeszkedik
\d Bármely számra
\D Mindenre, ami nem szám
\s Bármely elválasztó karakterre
\S Mindenre, ami nem elválasztó karakter
\w Bármely szóalkotó karakterre (aláhúzást is beleértve)
\W Mindenre, ami nem szóalkotó karakter
Ezek a karakterosztályok nagymértékben leegyszerûsíthetik a szabályos kifejezé-
seket. Nélkülük karaktersorozatok keresésekor saját karakterosztályokat kellene
használnunk. Vessük össze a szóalkotó karakterekre illeszkedõ mintát
az ereg() és a preg_match() függvényben:
ereg( "h[a-zA-Z0-9_]+z", $szoveg, $tomb );
preg_match( "/h\w+z", $szoveg, $tomb );
A Perl típusú szabályos kifejezések több váltókaraktert is támogatnak, melyek hivatkoz
ási pontként mûködnek. A hivatkozási pontok a karakterláncon belül helyekre
illeszkednek, nem pedig karakterekre. Ismertetésüket a 18.3. táblázatban találhatjuk.
350 18. óra
18.3. táblázat Hivatkozási pontként használt váltókarakterek
Karakter Illeszkedik
\A Karakterlánc kezdetére
\b Szóhatárra
\B Mindenre, ami nem szóhatár
\Z Karakterlánc végére (az utolsó soremelés vagy
a karakterlánc vége elõtt illeszkedik)
\z Karakterlánc végére (a karakterlánc legvégére
illeszkedik)
Emlékszünk még, milyen problémáink voltak a szóhatárra való illesztéssel a tagsági
azonosítást megvalósító példában? A Perl típusú szabályos kifejezések jelentõsen
leegyszerûsítik ezt a feladatot. Vessük össze, hogyan illeszt mintát szóalkotó karakterre
és szóhatárra az ereg() és a preg_match():
ereg ( "(^|[^a-zA-Z0-9_])(y{1,4}[a-zA-Z0-9_]*99)
å ([^a-zA-Z0-9_]|$)", $szoveg, $tomb );
preg_match( "\bt{1,4}\w*99\b", $szoveg, $tomb );
Az elõzõ példában a preg_match() függvény meghívásakor a szóhatáron levõ
legalább egy, de legfeljebb négy t karakterre illeszkedik, amelyet bármennyi
szövegalkotó karakter követhet és amit a szóhatáron levõ 99 karaktersor zár.
A szóhatárt jelölõ váltókarakter igazából nem is egy karakterre illeszkedik, csupán
megerõsíti, hogy az illeszkedéshez szóhatár kell. Az ereg_match() meghívásá-
hoz elõször létre kell hoznunk egy nem szóalkotó karakterekbõl álló mintát, majd
vagy arra, vagy pedig szóhatárra kell illeszteni.
A váltókaraktereket arra is használhatjuk, hogy megszabaduljunk a karakterek
jelentésétõl. Ahhoz például, hogy egy . karakterre illeszthessünk, egy fordított
perjelet kell elé írnunk.
Teljeskörû keresés a preg_match_all() függvénnyel
A POSIX szabályos kifejezésekkel az az egyik probléma, hogy a minta karakterláncon
belüli összes elõfordulását bonyolult megkeresni. Így ha az ereg() függv
énnyel keressük a b-vel kezdõdõ, t-vel végzõdõ szavakat, csak a legelsõ találatot
kapjuk vissza. Próbáljuk meg magunk is:
A szabályos kifejezések használata 351
18
$szoveg = "barackot, banánt, bort, búzát és
békákat árulok";
if ( ereg( "(^|[^a-zA-Z0-9_])(b[a-zA-Z0-9_]+t)
å ([^a-zA-Z0-9_]|$)", $szoveg, $tomb ) )
{
for ( $x=0; $x< count( $tomb ); $x++ )
print "\$tomb[$x]: $tomb[$x]
\n";
}
// kimenete:
// $tomb[0]: barackot,
// $tomb[1]:
// $tomb[2]: barackot
// $tomb[3]: ,
Ahogy azt vártuk, a $tomb harmadik elemében találjuk az elsõ találatot,
a "barackot". A tömb elsõ eleme tartalmazza a teljes találatot, a második
a szóközt, a negyedik a vesszõt. Hogy megkapjuk az összes illeszkedõ kifejezést
a szövegben, az ereg_replace() függvényt kellene használnunk, ciklikusan,
hogy eltávolítsuk a találatokat a szövegbõl, mielõtt újra illesztenénk a mintát.
Fel kell, hogy hívjuk a kedves olvasó figyelmét arra, hogy a magyar szövegekre
az ékezetes betûk miatt alapbeállításban nem alkalmazható a fentihez hasonló
egyszerû szabályos kifejezés. A nemzeti beállítások testreszabására használható
setlocale() függvényt kell alkalmaznunk, hogy a kívánt eredményt elérjük.
A preg_match_all() függvényt használhatjuk arra, hogy egyetlen hívásból
a minta összes elõfordulását megkapjuk. A preg_match_all() bemenete
egy szabályos kifejezés, a forráslánc és egy tömbváltozó. Találat esetén true (igaz)
értéket ad vissza. A tömbváltozó egy többdimenziós tömb lesz, melynek elsõ eleme
a szabályos kifejezésben megadott mintára illeszkedõ összes találatot tartalmazza.
A 18.1. programban a preg_match_all() függvénnyel illesztettük mintánkat
egy szövegre. Két for ciklust használunk, hogy megjelenítsük a többdimenziós
eredménytömböt.
18.1. program Minta teljeskörû illesztése a preg_match_all() függvénnyel
1:
2:
3: 18.1. program Minta teljeskörû illesztése<br />a preg_match_all() függvénnyel
4:
5:
352 18. óra
18.1. program (folytatás)
6: 7: $szoveg = "barackot, banánt, bort, búzát és
békákat árulok";
8: if ( preg_match_all( "/\bb\w+t\b/", $szoveg, $tomb ) )
9: {
10: for ( $x=0; $x< count( $tomb ); $x++ )
11: {
12: for ( $y=0; $y< count( $tomb[$x] ); $y++ )
13: print "\$tomb[$x][$y]:
".$tomb[$x][$y]."
\n";
14: }
15: }
16: // A kimenet:
17: // $tomb[0][0]: barackot
18: // $tomb[0][1]: banánt
19: // $tomb[0][2]: bort
20: // $tomb[0][3]: búzát
21: // $tomb[0][4]: békákat
22: ?>
23:
24:
A $tomb változónak a preg_match_all() függvénynek történõ átadáskor
csak az elsõ eleme létezik és ez karakterláncok egy tömbjébõl áll. Ez a tömb tartalmazza
a szöveg minden olyan szavát, amely b-vel kezdõdik és t betûre végzõdik.
A preg_match_all() ebbõl a tömbbõl azért készít egy többdimenziósat, hogy
az atomok találatait is tárolhassa. A preg_match_all() függvénynek átadott
tömb elsõ eleme tartalmazza a teljes szabályos kifejezés minden egyes találatát.
Minden további elem a szabályos kifejezés egyes atomjainak (zárójeles részmintá-
inak) értékeit tartalmazza az egyes találatokban. Így a preg_match_all()
alábbi meghívásával
$szoveg = "01-05-99, 01-10-99, 01-03-00";
preg_match_all( "/(\d+)-(\d+)-(\d+)/", $szoveg, $tomb );
a $tomb[0] az összes találat tömbje:
$tomb[0][0]: 01-05-99
$tomb[0][1]: 01-10-99
$tomb[0][2]: 01-03-00
A $tomb[1] az elsõ részminta értékeinek tömbje:
$tomb[1][0]: 01
A szabályos kifejezések használata 353
18
$tomb[1][1]: 01
$tomb[1][2]: 01
A $tomb[2] a második részminta értékeinek tömbjét tárolja:
$tomb[2][0]: 05
$tomb[2][1]: 10
$tomb[2][2]: 03
és így tovább.
Minták lecserélése a preg_replace() függvénnyel
A preg_replace() használata megegyezik az ereg_replace() használatával,
azzal a különbséggel, hogy hozzáférünk a Perl típusú szabályos kifejezések által
kínált kényelmi lehetõségekhez. A preg_replace() bemenete egy szabályos
kifejezés, egy csere-karakterlánc és egy forráslánc. Találat esetén a módosított karakterl
áncot, ellenkezõ esetben magát a forrásláncot kapjuk vissza változtatás nélkül.
A következõ kódrészlet a nn/hh/éé formátumú dátumokat alakítja át éé/hh/nn
formátumúvá:
$szoveg = "25/12/99, 14/5/00";
$szoveg = preg_replace( "|\b(\d+)/(\d+)/(\d+)\b|",
å "\\3/\\2/\\1", $szoveg );
print "$szoveg
";
// azt írja ki, hogy "99/12/25, 00/5/14"
Vegyük észre, hogy a szûrõkaraktert (|) használtuk határolójelként. Ezzel megtakar
ítottuk a perjelek kikerülésére használandó váltókaraktereket az illesztendõ
mintában. A preg_replace() ugyanúgy támogatja a visszautalásokat a cseresz
övegben, mint az ereg_replace().
A preg_replace() függvénynek a forráslánc helyett karakterláncok egy tömbjét
is átadhatjuk, az ebben az esetben minden tömbelemet egyesével átalakít. Ilyenkor
a visszaadott érték az átalakított karakterláncok tömbje lesz.
Emellett a preg_replace() függvénynek szabályos kifejezések és csere-karakterl
áncok tömbjét is átadhatjuk. A szabályos kifejezéseket egyenként illeszti
a forrásláncra és a megfelelõ csereszöveggel helyettesíti. A következõ példában
az elõzõ példa formátumait alakítjuk át, de emellett a forráslánc szerzõi jogi
(copyright) információját is módosítjuk:
$szoveg = "25/12/99, 14/5/00. Copyright 1999";
$miket = array( "|\b(\d+)/(\d+)/(\d+)\b|",
å "/([Cc]opyright) 1999/" );
$mikre = array( "\\3/\\2/\\1", "\\1 2000" );
354 18. óra
$szoveg = preg_replace( $miket, $mikre, $szoveg );
print "$szoveg
";
// azt írja ki, hogy "99/12/25, 00/5/14. Copyright 2000"
Két tömböt hozunk létre. A $miket tömb két szabályos kifejezést, a $mikre tömb
pedig csere-karakterláncokat tartalmaz. A $miket tömb elsõ eleme a $mikre
tömb elsõ elemével helyettesítõdik, a második a másodikkal, és így tovább.
Ha a csere-karakterláncok tömbje kevesebb elemet tartalmaz, mint a szabályos
kifejezéseké, a csere-karakterlánc nélkül maradó szabályos kifejezéseket a függv
ény üres karakterláncra cseréli.
Ha a preg_replace() függvénynek szabályos kifejezések egy tömbjét adjuk át,
de csak egy csereláncot, akkor az a szabályos kifejezések tömbjének összes mintá-
ját ugyanazzal a csereszöveggel helyettesíti.
Módosító paraméterek
A Perl típusú szabályos kifejezések lehetõvé teszik, hogy módosító paraméterek
segítségével pontosítsuk a minta illesztési módját.
A módosító paraméter olyan betû, amelyet a Perl típusú szabályos kifejez
ések utolsó határolójele után írunk, és amely tovább finomítja szabá-
lyos kifejezéseink jelentését.
A Perl típusú szabályos kifejezések módosító paramétereit a 18.4. táblázatban
találhatjuk.
18.4. táblázat A Perl típusú szabályos kifejezések módosító paraméterei
Minta Leírás
i Nem különbözteti meg a kis- és nagybetûket.
e A preg_replace() csereláncát PHP-kódként kezeli.
m A $ és a ^ az aktuális sor elejére és végére illeszkedik.
s A soremelésre is illeszkedik (a soremelések általában nem
illeszkednek a .-ra).
x A karakterosztályokon kívüli elválasztó karakterekre az olvashat
óság érdekében nem illeszkedik. Ha elválasztó karakterekre
szeretnénk mintát illeszteni, használjuk a \s, \t, vagy a \
(fordított perjel–szóköz) mintákat.
ÚJDONSÁG
A szabályos kifejezések használata 355
18
18.4. táblázat. (folytatás)
Minta Leírás
A Csak a karakterlánc elején illeszkedik (ilyen megszorítás
nincs a Perlben).
E Csak a karakterlánc végén illeszkedik (ilyen megszorítás
nincs a Perlben).
U Kikapcsolja a szabályos kifejezések „mohóságát”, azaz a lehetõ
legkevesebb karaktert tartalmazó találatokat adja vissza
(ez a módosító sem található meg Perlben).
Ahol ezek a megszorítások nem mondanak egymásnak ellent, egyszerre többet is
használhatunk belõlük. Az x megszorítást például arra használhatjuk, hogy
könnyebb legyen olvasni a szabályos kifejezéseket, az i megszorítást pedig arra,
hogy mintánk kis- és nagybetûktõl függetlenül illeszkedjék a szövegre.
A / k \S* r /ix kifejezés például illeszkedik a "kar" és "KAR" szavakra, de
a "K A R" szóra már nem. Az x-szel módosított szabályos kifejezések váltókarakter
nélküli szóközei nem fognak illeszkedni semmire sem a forrásláncban,
a különbség csupán esztétikai lesz.
Az m paraméter akkor jöhet jól, ha egy szöveg több sorában szeretnénk hivatkozási
pontos mintára illeszteni. A ^ és $ minták alapértelmezés szerint a teljes karakterl
ánc elejét és végét jelzik. A következõ kódrészletben az m paraméterrel módosítjuk
a $^ viselkedését:
$szoveg = "név: péter\nfoglalkozás: programozó\nszeme: kék\n";
preg_match_all( "/^\w+:\s+(.*)$/m", $szoveg, $tomb );
foreach ( $tomb[1] as $ertek )
print "$ertek
";
// kimenet:
// péter
// programozó
// kék
Egy olyan szabályos kifejezést hozunk létre, amely olyan mintára illeszkedik,
amelyben bármely szóalkotó karaktert vesszõ és tetszõleges számú szóköz követ.
Ezután tetszõleges számú karaktert követõ „karakterlánc vége” karakterre ($)
illesztünk. Az m paraméter miatt a $ az egyes sorok végére illeszkedik a teljes
karakterlánc vége helyett.
356 18. óra
Az s paraméter akkor jön jól, ha a . karakterrel szeretnénk többsoros karakterl
áncban karakterekre illeszteni. A következõ példában megpróbálunk a karakterl
ánc elsõ és utolsó szavára keresni:
$szoveg = "kezdetként indítsunk ezzel a sorral\nés így
å egy következtetéshez\n érkezhetünk el végül\n";
preg_match( "/^(\w+).*?(\w+)$/", $szoveg, $tomb );
print "$tomb[1] $tomb[2]
";
Ez a kód semmit nem ír ki. Bár a szabályos kifejezés megtalálja a szóalkotó karaktereket
a karakterlánc kezdeténél, a . nem illeszkedik a szövegben található
soremelésre. Az s paraméter ezen változtat:
$szoveg = "kezdetként indítsunk ezzel a sorral\nés így
å egy következtetéshez\n érkezhetünk el végül\n";
preg_match( "/^(\w+).*?(\w+)$/s", $szoveg, $tomb );
print "$tomb[1] $tomb[2]
";
// azt írja ki, hogy "kezdetként végül"
Az e paraméter különlegesen hasznos lehet számunkra, hiszen lehetõvé teszi,
hogy a preg_replace() függvény csereszövegét PHP kódként kezeljük.
A függvényeknek paraméterként visszautalásokat vagy számokból álló listákat
adhatunk át. A következõ példában az e paraméterrel adunk át illesztett számokat
egy függvénynek, amely ugyanazt a dátumot más formában adja vissza.
function datumAtalakito( $honap, $nap, $ev )
{
$ev = ($ev < 70 )?$ev+2000:$ev;
$ido = ( mktime( 0,0,0,$honap,$nap,$ev) );
return date("Y. m. j. ", $ido);
}
$datumok = "3/18/99
\n7/22/00";
$datumok = preg_replace( "/([0-9]+)\/([0-9]+)\/([0-9]+)/e",
"datumAtalakito(\\1,\\2,\\3)",
$datumok);
print $datumok;
// ezt írja ki:
// 1999. 03. 18.
// 2000. 07. 22.
?>
A szabályos kifejezések használata 357
18
Három, perjelekkel elválasztott számhalmazt keresünk és a zárójelek használatával
rögzítjük a megtalált számokat. Mivel az e módosító paramétert használjuk,
a cserelánc paraméterbõl meghívhatjuk az általunk meghatározott
datumAtalakito() függvényt, úgy, hogy a három visszautalást adjuk át neki.
A datumAtalakito() csupán fogja a számbemenetet és egy jobban festõ
dátummá alakítja, amellyel aztán kicseréli az eredetit.
Összefoglalás
A szabályos kifejezések hatalmas témakört alkotnak, mi csak felületesen ismerkedt
ünk meg velük ezen óra során. Arra viszont már biztosan képesek leszünk, hogy
bonyolult karakterláncokat találjunk meg és cseréljünk le egy szövegben.
Megismerkedtünk az ereg() szabályoskifejezés-függvénnyel, amellyel karakterl
áncokban mintákat kereshetünk, illetve az ereg_replace()-szel, amellyel
egy minta összes elõfordulását cserélhetjük le. A karakterosztályok használatával
karakterláncokat, a mennyiségjelzõk használatával több mintát, az elágazásokkal
mintákat vagylagosan kereshetünk. Részmintákat is kigyûjthetünk és rájuk visszautal
ásokkal hivatkozhatunk. A Perl típusú szabályos kifejezésekben váltókarakterekkel
hivatkozási pontos illesztést kérhetünk, de karakterosztályok illesztésére is
rendelkezésre állnak váltókarakterek. Ezenkívül a módosító paraméterekkel
képesek vagyunk finomítani a Perl típusú szabályos kifejezések viselkedésén.
Kérdések és válaszok
A Perl típusú szabályos kifejezések nagyon hatékonynak tûnnek.
Hol található róluk bõvebb információ?
A szabályos kifejezésekrõl a PHP weboldalán (http://www.php.net) találhatunk
némi felvilágosítást. Emellett még a http://www.perl.com oldalain találhatunk
információt, a Perl típusú szabályos kifejezésekhez pedig
a http://www.perl.com/pub/doc/manual/html/pod/perlre.htm
címen és Tom Christiansen cikkében,
a http://www.perl.com/pub/doc/manual/html/pod/perlfaq6.html
címen találhatunk bevezetést.
358 18. óra
Mûhely
A mûhelyben kvízkérdések találhatók, melyek segítenek megszilárdítani az órában
szerzett tudást. A válaszokat az A függelékben helyeztük el.
Kvíz
1. Melyik függvényt használnánk egy karakterláncban minta illesztésére,
ha POSIX szabályos kifejezéssel szeretnénk a mintát meghatározni?
2. Milyen szabályos kifejezést használnánk, ha a "b" betû legalább egyszeri,
de hatnál nem többszöri elõfordulására szeretnénk keresni?
3. Hogyan határoznánk meg a "d" és az "f" betûk közé esõ karakterek
karakterosztályát?
4. Hogyan tagadnánk a 3. kérdésben meghatározott karaktertartományt?
5. Milyen formában keresnénk tetszõleges számra vagy a "bokor" szóra?
6. Melyik POSIX szabályos kifejezés függvényt használnánk egy megtalált minta
lecserélésére?
7. A .bc* szabályos kifejezés mohón illeszkedik, azaz inkább
az "abc0000000bc" karakterláncra illeszkedik, mint az "abc"-re. A Perl
típusú szabályos kifejezésekkel hogyan alakítanánk át a fenti szabályos kifejez
ést úgy, hogy a megtalált minta legelsõ elõfordulását keresse meg?
8. A Perl típusú szabályos kifejezéseknél melyik fordított perjeles karakterrel
illesztünk elválasztó karakterre?
9. Melyik Perl típusú szabályos kifejezést használnánk, ha az a célunk, hogy
egy minta összes elõfordulását megtaláljuk?
10. Melyik módosító karaktert használnánk egy Perl típusú szabályoskifejezésf
üggvényben, ha kis- és nagybetûtõl függetlenül szeretnénk egy mintára
illeszteni?
Feladatok
1. Gyûjtsük ki az e-mail címeket egy fájlból. A címeket tároljuk egy tömbben és
jelenítsük meg az eredményt a böngészõben. Nagy mennyiségû adattal finom
ítsuk szabályos kifejezéseinket.