link
A PHP esetén több függvénykönyvtárból is választhatunk, ha reguláris kifejezésekkel szeretnénk foglalkozni: az egyik a POSIX kompatibilis ereg_* függvény család, valamint a Perl kompatibilis PCRE csomag által nyújtott preg_* funkciók. Mi az ereg_* függvényekkel nem foglalkozunk, mert több szempontból is szerényebb képeségekkel bír, mint a PCRE csomag: egyrészt lassabb is (egyes becslések szerint körülbelül 30%-kal), másrészt kevesebb lehetőséget is nyújt.
Reguláris kifejezésekkel kapcsolatban érdemes megemlíteni, hogy némileg közhiedelem, hogy lassúak, általában már nagyon egyszerű feladatok esetén is alig lassabb, mint a sima szöveg kezelő függvények, de kicsit is összetettebb feladatok esetén szinte biztosan állítható, hogy jobban járunk egy jól irányzott reguláris kifejezéssel, mintha szöveg kezelő funkciók sorozatával oldanánk meg az adott problémát. Persze ha valamit megoldhatunk egyetlen strpos, str_replace parancs segítségével, akkor felesleges regexpeket használni.
Mire is jók a reguláris kifejezések? Reguláris kifejezésekkel szövegek egy halmazát tudjuk általánosan leírni, mégpedig úgy, hogy egy ezeket egy speciális jelentéssel bíró metakaraktereket tartalmazó mintával határozzuk meg. Először is nézzük át, hogy milyen elemek találhatóak egy reguláris kifejezésben:
o karakterek: minden nem metakarakter karakter saját magát jelenti
o \: escape karakter, ezzel érhetjük el, hogy amúgy speciális jelentéssel bíró karakterekre is hivatkozni tudjunk
o karakter megadási módok:
– rövidítések: \n, \t, \r stb.
– oktális forma: \ddd (ddd az oktális szám)
– hexa forma: \xhh
– unicode hexa forma: \x{hh..}, \uhh.. (u módosító kell hozzá)
– kontroll karakterek: \cx (x tetszőleges karakter)
o karakter osztályok:
– .: a pont általában tetszőleges karaktert határoz meg kivéve az újsor
– \C: egy byte-ot jelent
– \X: egy unicode szekvenciát jelent (u módosító kell hozzá)
– \w, \W: egy tetszőleges szóban előfordulható karakter, illetve ennek fordítotja (locale függő)
– \d, \D: egy tetszőleges szám karaktert, illetve ennek fordítottja
– \s, \S: tetszőleges whitespace karakter, illetve ennek fordítottja
– az előbi 3 esetén érdekes lehet, hogy csak ASCII karaktereket találnak meg, ezért Unicode karakterek esetén használjuk a megfelelő \p{X}, \pX karakter osztályokat
– \p{X}, \pX: Unicode karaktereket szűrhetünk a tulajdonságaik alapján (L-Letter, N-Number, Z-Separator stb.)
– \P{X}, \PX: az előbbi ellentéte (\PL -> nem betű)
– []: saját karakter osztályok
o metakarakterek karakter osztályon belül: \ (escape), ^ (negálás), – (tartomány megadása), ] (karakter osztály lezárása)
o [a-zA-Z0-9], [^a-z], [\n\t\r\C\X\w\W\d\D\s\S\b…..]
o horgonyok:
– ezek feltételeket határoznak meg, nulla hosszúságúak (nem fogyasztanak el karaktert a szövegből)
– ^, $: szöveg eleje és vége (m módosító esetén újsor karakterre is illeszkednek)
– \A, \z, \Z: szöveg eleje, vége, vége vagy vége előtti újsor karakter (függetlenek az m módosítótól)
– \G: illesztés kezdeténél illeszkedik (preg_match 5. paramétere határozza meg)
– \b, \B: szó határon illeszkedik, illetve annak ellentétje (szintén locale függő, valamint karakter osztályon belül a \b backspace karaktert jelenti) (szintén csak ASCII karaktereket találnak meg)
– előre tekintés (lookahead): (?=), (?!) (negatív)
– hátra tekintés (lookbehin): (?<=), (?<!) (negatív)
o vezérlő karakterek:
– (): gyűjtő zárójel, a tartalma által megtalált szövegre \1, \2 stb. módon tudunk hivatkozni (ezeknél érdekes lehet, hogy így néz ki az oktális karakter megadási mód)
– |: csoportosítás (foo|bar)
– ?, *, +, {n,m}: számosság meghatározók (nulla vagy egy, nulla vagy több, egy vagy több, megadott számú találat, az intervalum bármelyik oldala lehet nyitott), ezek mohók (greedy), a lehető legtöbb karaktert próbálják meg elfogyasztani.
– ??, *?, +?, {n,m}?: lusta (lazy) számosság meghatározók (jelentés ugyanaz), viszont ezek a lehető legkevesebb karaktert próbálják meg elfogyasztani.
– ?+, *+, ++, {n,m}+: nem eresztő (possessive) számosság meghatározók: ennek a backtracking mechanizmus miatt lesz jelentősége, erről majd lesz később szó.
– (?:): nem gyűjtő zárójel (hasznos lehet, hogy ha csak csoportosításra használnánk amúgy a zárójelet, akkor a “gyűjtés” miatti memória foglálás és adminisztráció felesleges)
– (?<Name>): nevesített capturing (a találatot ilyen néven keresztül fogjuk elérni, hasznos lehet, mert ez esetben nem függünk attól, hogy a regexbe bekerül esetleg még egy gyűjtő zárójel)
– (?>): atomi grouping: szintén a backtracking mechanizmus miatt van jelentősége
– (?(condition)yes-pattern), (?(condition)yes-pattern|no-pattern): feltételes egyezés vizsgálata, majd látunk rá példát
o módosítók:
– /…/xzy: a minta vége után felsorolhatjuk őket
– (?modifier): innentől kezdve él a módosító ((?i) vagy (?-i) vagy kombinalva (?m-i))
– (?modifier:….): a zárójelen belül érvényes az adott módosító
– (?#…): komment
– #: komment (x módosító)
– \Q…\E: közöttük tetszőleges szöveg lehet, literálként lesz értelmezve
– módosítók:
o i: kis betű, nagy betű érzéketlen lesz a minta
o s: ilyenkor a . illeszkedik az újsor karakterre is
o m: ilyenkor a ^ és a $ csak a teljes szöveg elején illetve végén illeszkedik
o x: a whitespace karakterek nem lesznek részei a regexpnek (lesz rá példa)
o e: preg_replace esetén lesz érdekes, látunk rá példát
o u: UTF-8-ként lesz értelmezve a minta
o A, U, X, S, D
Általános jótanácsok:
o nem kötelező a / jelet használni határoló jelnek, lehet bármi más, a lényeg, hogy a regexpen belül escapleni kell.
$pattern = “!…\!…!”;
o ha nem ismert tartalmú szöveget kell beilleszteni egy mintába, akkor használjuk a preg_quote parancsot (ha nem / jel a határoló, akkor meg kell adni a függvénynek).
$pattern = “!…”.preg_quote($_GET[‘search’], “!”).”…!”;
o használjunk nevesített capturinget, kevésbé hibaérzékeny.
o ha egy zárójel csak csoportosításra szolgál, akkor jelezzük, hogy nincs szükség a capturingre.
o a kicsit is bonyolultabb reguláris kifejezéseket kommentezzük, mintha sima programkód lenne, különben a későbbiek során szintén elég nehezen módosíthatókká válnak.
/**
FAQ hozzaadasa egy kategoriahoz
@param int $category_id + Ebben a kategoriaban kell letrehozni a FAQ-ot
@param string $question + FAQ-hoz tartozo kerdes
@param string $answer + FAQ-hoz tartozo valasz
*/
public function actionAdd($category_id, $question, $answer) {
Controller_Faq::addItem($category_id, $question, $answer);
redirect(‘/faq/editor/ViewFaqs.cmd?category_id=’.$category_id);
}
function getActionParamsDefinition($pageClass, $action)
{
$reflection = new ReflectionClass($pageClass);
try {
$reflection = $reflection->getMethod(‘action’.$action);
} catch (ReflectionException $e) {
PEAR::raiseError(‘The requested action (‘.$action.’) not supported on this page!’);
}
if (null === ($comment = $reflection->getDocComment())) {
PEAR::raiseError(‘The requested action (‘.$pageClass.’->action’.$action.'()) has no DocComment!’);
};
// Nevesitett gyujto almintakat hasznlunk a konnyebb erthetoseg kedveert
$pattern = ‘
/# parameter informaciok kinyerese a kommentbol
@param # kulcsszo megkereses
\s+ # elvalasztas
(?P<type>int|string|array|boolean|float|number) # lehetseges tipusok
\s+ # elvalasztas
(?P<name>\$[a-z_\x7f-\xff][a-z0-9_\x7f-\xff]*) # parameter neve
\s+ # elvalasztas
(?: # leiras, ne gyujtse be a tartalmat
# kotelezo, opcionalis
(?:(?P<ismandatory>[+]))? # DEPRECATED egy darab plusz jel – mar nem hasznaljuk
# validatorok, opcionalis
(?:\( # nyito zarojel
(?P<validators>[a-z0-9_,]+) # validatorok: szovegek vesszovel elvalasztva
\))? # zaro zarojel
.*?$ # leiras maradek resze
)
/imsx
‘;
// A $mataches tomb nulladik elemének mérete megadja a parameterek szamat.
preg_match_all($pattern, $comment, $commentParams);
Számosság:
o alap esetben ezek a módosítók mohók, a lehető legtöbb karaktert próbálják meg elfogyasztani:
$string = “…<a>1…</a>…<a>2…</a>….”;
$pattern = “/<a>.*<\/a>/”; // “<a>1…</a>…<a>2…</a>”
o ha ez számunkra nem jó, akkor hasznéljunk lusta módosítókat:
$string = “…<a>1…</a>…<a>2…</a>….”;
$pattern = “/<a>.*?<\/a>/”; // “<a>1…</a>”
o {n,m} forma esetén tetszőleges variácó létezik: {,m}, {n,}, {n,m}, {n}
Karakter osztályok:
o használhatunk POSIX kompatibilis megadási formát is: :alpha:, :alnum:, :ascii:, :cntrl:, :digit:, :graph:, :lower:, :print:, :space:, :upper:, :xdigit:. Ezeket lehet negálni is: :^alnum:. Például: [[:ascii:]], [[:^digit:]]
o [^]: a negált karakter osztályok minden karakterre illeszkednek, amelyik nincs benne az adott osztályban, még az újsorra is
$string = “<img …\n…>”;
$pattern = “/<img.+?>/”;
$pattern = “/<img[^>]+>/”;
Módosítók:
o i: egyértelmű, szükség esetén használjuk.
o s: ha szeretnénk, hogy a . illeszkedjen minden karakterre, akkor kell használnunk.
$string = “<img …\n…>”;
$pattern = “/<img.+?>/s”;
o m: ha szeretnénk, hogy a ^ és a $ ne csak a teljes szöveg elejére és végére illeszkedjen, akkor kell használnunk.
o S: ha gyakran futtatunk egy regexpet, akkor érdemes lehet kipróbálni ezt az opciót, ugyanis ezzel arra utasítjuk a PCRE motort, hogy elemezze jobban a regexpet, és optimálisabban hajtsa végre.
o u: példa email cím ellenőrző.
o e: ezzel azt érjük el preg_replace esetén, hogy a helyettesítendő szöveg PHP kódként értékelődik ki (majd látunk rá példát).
Egyszerűbb példák:
o IP cím ellenőrzése:
$matching255 = “(?#100 alatti szamok)[1-9]?[0-9]|(?#1xx)1[0-9]{2}|(?#200-249)2[0-4][0-9]|(?#25x)25[0-5]”;
preg_match(“/$matching255/”, “255”);
//25 (a csoportosítás első eleménél ha találat van, akkor vége az illesztésnek)
preg_match(“/^$matching255$/”, “255”);
//25 (precedencia)
preg_match(“/^($matching255)$/”, “255”);
//255
$ipAddressPattern = “/^(($patternFor255).){3}($patternFor255)$/”;
o változónevek ellenőrzése:
$pattern = “/^\$[a-z]+([A-Z][a-z]+)*$/”;
o szóismétlések megkeresése:
$pattern = “/\b(\w+)\s+\\1\b/i”;
o email cím ellenőrzése:
$pattern = “/^[\w\d!#$%&’*+-\/=?^`{|}~]+(\.[\w\d!#$%&’*+-\/=?^`{|}~]+)*@([a-z\d][-a-z\d]*[a-z\d]\.)+[a-z][-a-z\d]*[a-z]/i”;
Horgonyok:
o ahogy említettük, ezek nem fogyasztanak el karaktereket a szövegből, nulla hosszúságúak, segítségükkel különböző feltételek teljesülését tudjuk előírni
o alap horgonyok: ^, $, \A, \Z, \z, \b
$_GET[“phone”] = “xxx30-123-1234xxx”;
preg_match(“/\d{2}-\d{3}-\d{4}/”, $_GET[“phone”]); // TRUE
preg_match(“/^\\d{2}-\\d{3}-\\d{4}$/”, $_GET[“phone”]); // FALSE
preg_match vs. preg_match_all
o a előbbi az első egyezést találja meg, a második az összeset.
o preg_match_all esetén hasznos lehet a PREG_SET_ORDER opció megadása: alap esetben a találatok tömbje elsődlegesen a gyűjtő zárójel sorszáma szerint van index-szelve, de ha megadjuk ezt az opciót, akkor a találatok sorendje lesz az elsődleges index (a példa alapján ez érthető is lesz).
<?php
$subject = “….(1|2)….(2|3)…”;
$pattern = “/\((\d+)\|(\d+)\)/”;
preg_match($pattern, $subject, $match);
echo ‘<pre>’.htmlentities(print_r($match, true)).'</pre>’;
// Array
// (
// [0] => (1|2)
// [1] => 1
// [2] => 2
// )
preg_match_all($pattern, $subject, $match);
echo ‘<pre>’.htmlentities(print_r($match, true)).'</pre>’;
// Array
// (
// [0] => Array
// (
// [0] => (1|2)
// [1] => (2|3)
// )
//
// [1] => Array
// (
// [0] => 1
// [1] => 2
// )
//
// [2] => Array
// (
// [0] => 2
// [1] => 3
// )
//
// )
preg_match_all($pattern, $subject, $match, PREG_SET_ORDER);
echo ‘<pre>’.htmlentities(print_r($match, true)).'</pre>’;
// Array
// (
// [0] => Array
// (
// [0] => (1|2)
// [1] => 1
// [2] => 2
// )
//
// [1] => Array
// (
// [0] => (2|3)
// [1] => 2
// [2] => 3
// )
//
// )
foreach($match as $item) {}
?>
preg_replace:
o ftp, http, https URL-eket szeretnénk linkekre cserélni.
preg_replace(“!((ht|f)tps?://\S+)!”, ‘<a href=”\1″>\1</a>’, $text);
o ugyanaz mint feljebb, de szeretnénk, hogy csak a ténylegesen létező URL-ekből legyen link, itt jön be a képbe az e módosító:
preg_replace(“!((ht|f)tps?://\S+)!e”, ‘isExistentUrl(\1) ? “<a href=\”\1\”>\1</a>” : “\1 (dead link)”‘, $text);
o nagyon egyszerű template motor:
$string = “…{foo}…”;
$templateVars[“foo”] = “bar”;
preg_repalce(“/{(\w+)}/e”, “$templateVars[\1]”, $string);
o PHPMailer kiegészítése:
Backtracking:
o alap esetben az ilesztés folyamata:
$string = “foo12”;
$pattern = “/\w+13/”;
// foo12 – illeszti az f-et
// [f]oo12 – illeszti az o-t
// [fo]o12 – illeszti a következő o-t
// [foo]12 – illeszti az 1-est
// [foo1]2 – itt nem sikerül a 3-at illeszteni, ezért visszalép és felad egy karaktert
// [fo]o12 – itt nem sikerül az 1-et illeszteni, ezért visszalép és felad egy karaktert
// [f]oo12 – itt nem sikerül az 1-et illeszteni, ezért visszalép és felad egy karaktert
// foo12 – nem sikerült az ilesztés
o adott esetben mi tudhatjuk, hogy nincs értelme visszalépnie, és ezt jelezhetjük is, erre szolgálnak a nem eresztő (possessive) számosság meghatározók.
$string = “foo12”;
$pattern = “/\w++13/”;
// foo12 – illeszti az f-et
// [f]oo12 – illeszti az o-t
// [fo]o12 – illeszti a következő o-t
// [foo]12 – illeszti az 1-est
// [foo1]2 – nem sikerült az illesztés
o ennek egyik alternatív megadási formája az atomic grouping:
$pattern = “/(?>\w+)13/”;
o egy érdekes példa, csak hogy érzékeltessük ennek jelentőségét: legyen a feladatunk, hogy a wikire jelemző MindenSzoNagybetuvelKezdodik jellegű szavakat kell kigyűjteni. Az alap ötlet kapásból megvan:
$pattern = “/(([A-Z][a-z]+)+)/”;
Ha azonban van egy ilyen szövegünk: “JoHosszuFileNevAmiHasonlitArraAmitKeresunk.php”, akkor a backtracking miatt lényegesen meg tud nőni a kiértékelés ideje, amit lecsökkenthetünk (egy teszt szerint például 1,5 másodpercről 0,05-re), ha így adjuk meg a regexpet:
$pattern = “/(?>([A-Z][a-z]+)+)/”;
jozsi123
[j]ozsi123
[jo]zsi123
[joz]si123
[jozs]i123
[jozsi]123
[jozsi1]23
[jozsi12]3
jozsi123
[j]
| marad | talalt |
| jozsi123 | |
| | |
| | |
| | |
| | |