Tutorials

Regular expressions

De complete uitleg over regular expressions (PCRE Compatible)

Pagina 1

Voorwoord

Deze tutorial gaat in op de automagische wereld van regular expressions. Regex. In php zijn twee verschillende soorten regex opgenomen om het overzichtelijk te houden: de ereg en preg smaken.

De ereg regular expressions zijn gebaseerd op de posix variant. Deze tutorial zal in gaan op de perl variant die iets meer mogelijkheden heeft, sneller is en een kortere notatie.

In PHP zul je vaak bewerkingen op tekst of html doen: kijken of iets in een stuk tekt zit, of controleren of de input wel klopt. Simpele zoekoperaties zoals alle e-mail adressen uit een tekst halen tot ingewikkeldere zoekopdrachten als alle woorden die met twee keer opeenvolgende letters beginnen, alle palindromen, je kan het zo gek niet bedenken.

Reguliere Expressies zijn een krachtige manier om in tekst te zoeken, valideren en te vervangen, en zal je wanneer je dit beheerst veel tijd besparen.
Pagina 2

My First Regex

De simpelste regex is een woord, of eigenlijk een string van letters. Een reguliere expressie (regexp) die uit één woord bestaat zal met elke string matchen[1] die ook dat woord bevat.

<?
preg_match("/World/", "Hello World"); // Matched
?>

Maar waar gaat dit nou eigenlijk over? "Hello Word" is een simpele string tussen dubbele aanhalingstekens en de // die de string World omsluiten vertellen aan php dat dat het gedeelte is waar naar gezocht moet worden.

De functie preg_match zal true of false terug geven: met matched, of het matched niet. In condities (if, for, while) zijn deze erg handig:

<?
if (preg_match("/World/", "Hello World")) {
	print "Het komt overeen";
} else {
	print "Het komt niet overeen";
}
?>


Ook kunnen we de letterlijke string "Hello World" en reguliere expressie /World/ vervangen door een variabele:

<?
$regex = "/World/";
$greeting = "Hello World";
if (preg_match($regex, $greeting)) {
	print "Het komt overeen";
} else {
	print "Het komt niet overeen";
}
?>

Ook kan je andere tekens dan // gebruiken als 'matchtekens, bijvoorbeeld !. Dit is met name handig wanneer je een iets waar veel forward slashes (/) in zitten wil matches (bijvoorbeeld een URL).

<?
preg_match("!World!", "Hello World");
preg_match("{World}", "Hello World");
?>

Laten we nu eens kijken welke reguliere expressies zouden overeenkomen met "Hello World".

<?
preg_match("/world/", "Hello World"); // Matched niet
preg_match("/o W/", "Hello World");   // Matched
preg_match("/oW/", "Hello World");    // Matched niet
preg_match("/World /", "Hello World");// Matched niet
?>

De eerste regexp mached niet omdat regexp hoofdlettergevoelig zijn. De tweede matched omdat
'o W'
voorkomt in
'Hello World'
. De spatie ' ' wordt gewoon beschouwd als een spatie en heeft geen speciale betekenis. Zonder de spatie zou deze niet werken, dit is te zien in de 3e regel, 'oW' matched niet, en in het vierde voorbeeld is te zien dat wanneer er een spatie te veel staat aan het eind van de regex. De les is dat de regexp precies moet matchen om overeen te komen.

Wanneer een regex meer dan een keer in een string voorkomt zal php altijd de eerste matchen.

<?
preg_match("/o/", "Hello World");      // Matched de eerste 'o' in 'Hello'.
preg_match("/hat/","That hat is red"); // Matched 'hat' in 'That'
?>

Wat verder belangrijk is is dat een paar tekens (metacharacters) zijn gereserveerd voor regexp notatie.
De metacharacters zijn:


{}[]()^$|*+?\


De significatie van elke deze zzal in de rest van de tutorial worden uitgelegd. Wat nu belangrijk is is dat een metacharacter kan gebruikt worden als letterlijk teken door er een backslash voor te zetten:

<?
preg_match("/2+2/","2+2=4");  // Matched niet, + is een metacharacter.
preg_match("/2\+2/","2+2=4"); // Matched, + wordt als een gewoon teken behandeld.
preg_match("/[0,1)./", "De interval is [0,1)");    // Ongeldige regexp syntax!
preg_match("/\[0,1\)\./", "De interval is [0,1)"); // matched
preg_match("/http:\/\/www.phphulp.nl\/","http://www.phphulp.nl"); // matched
preg_match("!http://www.phphulp.nl!","http://www.phphulp.nl");    // matched
?>

In de laatste twee voorbeelden is te zien hoe het af en toe handig is om bij forward slashes de delimiter (/) te vervangen door een uitroepteken of een ander teken om de leesbaarheid te vergroten.

Ook de blackslash \ is een metacharacter en moet ook geescaped worden:

<?
preg_match('/C:\\WIN/', 'C:\WIN');
?>


In alle bovenstaande regexps geld: als de regexp ook maar ergens in de string voorkomt was de expressie geldig. Soms wil je aangeven waar de string de regexp overeen zou moeten komen. Om dit te doen zijn de (anchor) metacharacters ^ en en ^ in het leven geroepen. De anchor ^ betekend dat hij aan het begin van de string zou moeten matchen en de anchor $ betekend dat hij aan het eind van de string zou moeten matchen of voor een newline aan het eind van de string.

<?
preg_match("/keeper/", "housekeeper");    // matches
preg_match("/^keeper/", "housekeeper");   // matched niet
preg_match("/keeper$/", "housekeeper");   // matches
preg_match("/keeper$/", "housekeeper\n"); // matches
?>


Het tweede voorbeeld matched niet omdat het ^ teken forceerd dat keeper alleen aan het begin matched, terwijl in "housekeeper" keeper in het midden begint. De derde regexp matched omdat keeper aan het einde van "housekeeper" staat.

Wanneer zowel ^ als $ in een regexp gebruikt worden moet de regexp zowel aan het begin als aan het eind matchen. Met andere woorden: de gehele string moet matchen.

<?
preg_match("/^keep$/", "keeper");   // Matched niet
preg_match("/^keeper$/", "keeper"); // Matched
preg_match("/^$/", "");             // Matched een lege string
?>


De eerste regexp matched niet om de string uit meer bestaat dan 'keep'. De tweede regexp is precies de string en matched. if ($string == "keeper") zou dan ook nuttiger zijn in dit geval. De ^...$ syntax wordt nuttiger op het moment dat we wat krachtigere regexp tools gaan gebruiken later in de tutorial.
Pagina 3

Het gebruik van character classes

Hoewel je al een hoop kan doen met letterlijke regexps zijn zowel alle voorbeelden hierboven ook de doen met substr(). Dit is dan ook maar het topje van de ijsberg van wat mogelijk is met reguliere expressies. In deze en de hieropvolgende secties zullen we regexp concepten (en bijbehorende metacharacter notaties) behandelen die een regexp in staat stellen om niet alleen een rij teken of letter te matchen maar een hele class (een set tekens).

Een zo'n voorbeeld is een character class. Een character class zorgt er voor dat je een set van mogelijke tekens, in plaats van een enkel teken kan matchen op een bepaald pun in een regexp. Character classes worden gescheiden door brackets [...], met een set van tekens die mogelijk matchen daarin.

Voorbeeld:
<?
preg_match("/cat/", $string); // matched 'cat'
preg_match("/[bcr]at/", $string); // matched 'bat, 'cat', or 'rat'
preg_match("/item[0123456789]//", $string); // matched 'item0' of ... of 'item9'
preg_match("/[cab]/","abc"); // matched 'a'
?>

In het laatste voorbeeld matched 'a', ook al staat 'c' als eerste in de regex. 'a' matched omdat het het eerste teken is in de string.

<?
preg_match("/[yY][eE][sS]/",$string);
// Matched 'yes' in een hoofdletterongevoelige
// manier: yes, Yes, yEs, YES, yeS, etc.
?>

Deze regexp geeft een veel voorkomend probleem weer: en case insensitive match. Dit is mogelijk door een /i toe te voegen aan het eind van de string. i, staat voor case insensitive, en is een voorbeeld van een modifier. Op deze manier kan de vorige regex worden herschreven als:

<?
preg_match("/[y][e][s]/i", $string);
?>

We zagen eerder dat er gewone tekens zijn en speciale tekens die ene backslash nodig hebben om letterlijk te gebruiken. Het zelfde geld in een character classe, maar binnen een character class moeten iets andere tekens geescaped worden. Namelijk:


-]\^$


Zie hier een voorbeeld hoe je dit moet oplossen:

<?
preg_match("/[\]c]def/",$string); // Matched ']def' of 'cdef'

$x = 'bcr';
preg_match("/[$x]at/", $string); // matched 'bat', 'cat' en 'rat'
preg_match("/[\$x]at/", $string); // matched '$at' of 'xat'
preg_match("/[\\$x]at/", $string);// matched '\at\', 'bat', 'cat' of 'rat'

?>

De laatste twee zijn een beetje tricky. in [\$x], zorgt de backslash ervoor dat $ letterlijk geintrepeteerd moet worden, zodat het twee onderdelen heeft: $ en x. in [\\$x] wordt $x als een variabele beschouwd en bestaat de set uit \ en de variabelen in $x.

Update: de verwarringsloze versie is uiteraard waarbij je netjes variabelen buiten quotes zet:
<?
preg_match("/[".$x."]at/", $string); // matched 'bat', 'cat' en 'rat'
?>

Het - teken zorgt ervoor dat je een hele reeks aan tekens kan aangeven in een character class. Zo dat een reeks opeenvolgende tekens kan worden herschreven als een reeks. Met reeksen voorkom je enorme regexps als [0123456789] en [abcdefghijklmnopqrstuvwxyz], dit wordt [0-9] en [a-z].

<?
preg_match("/item[0-9]/",$string); // matched item0 tot item9.
preg_match("/[0-9bx-z]aa/", $string); // matched '0aa' tot '9aa' en 'baa' en 'xaa', 'yaa' of 'zaa'
preg_match("/0-9a-fA-F/", $string); // matched hexadecimale getallen
preg_match("/0-9a-zA-Z_/", $string); // matched een "woord" teken zoals in een variabelenaam/
?>

Wanneer - het eerst of laatste teken is in een character class wordt het als een gewoon teken beschouwd. [-ab] [ab-] en [a\-b] hebben het zelfde resultaat.

Het speciale teken ^ in de eerste positie in een character class betekend dat alle tekens behalve de tekens binnen de brackets [] zullen matchen.

Dus:

<?
preg_match("/[^a]at/", $string) // matched niet met aat of at, maar wel met bat, cat, 0at, %at etc.
preg_match("/[^0-9]/", $string) // matched alles behalve cijfers
preg_match("/[a^]at/", $string) // matched 'aat' of '^at' ^ wordt hier als een gewoon teken beschouwd.
?>

Afkortingen character classes
Zelfs [0-9] kan vervelend zijn om meerdere keren te schrijven dus om het aantal aanslagen te beperken zijn er een paar afkoringen voor veel gebruikte charachter classes:

\d  is een nummer, en staat voor [0-9]
\s  is een whitespace (spatie, enter, carriage return, tab) teken en staat voor [\ \t\r\n\f]
\w  is een woord teken en staat voor [0-9a-zA-Z_]
\D  is het omgekeerde van een nummer en dus [^0-9]
\S  is het omgekeerde van whitespace [^\ \t\r\n\f]
\W  is het omgekeerde van een woord teken [^0-9a-zA-Z_]
het '.' teken zal elk teken matchen behalve '\n'.


De \d\s\w\D\S\W afkortingen kunnen zowel binnen als buiten character classes gebruik worden.

<?
preg_match("/\d\d:\d\d:\d\d/", $string); // matched een uu:mm:ss tijd formaat
preg_match("/[\d\s]/", $string); // matched een nummer of een whitespace teken
preg_match("/\w\W\w/", $string); // matched een woord teken gevolgd door een
// niet-woord teken gevolgd door een woordt teken.
preg_match("/..rt/", $string); // matched twee tekens gevolgd door 'rt'
preg_match("/end\./, $string); // matched 'end.'
?>

Omdat . een metacharacter is moet het worden escaped om als een gewone punt gematched te worden.

Een anchor die handig is in simpele regexps is de word anchor \b. Deze matched de grens tussen een woord teken een niet-woord teken. Dus: waar een woordt begint of eindigd. Voor de techneuten onder ons: tussen \w\W of \W\w.

<?
$x = "Housecat catenates house and cat";
preg_match("/cat/", $string); // matches cat in 'housecat'
preg_match("/\bcat/", $string); // matches cat in 'catenates'
preg_match("/cat\b/", $string); // matches cat in 'housecat'
preg_match("/\bcat\b/", $string);// matches 'cat' at end of string
?>

In het laaste voorbeeld wordt het einde van de string gezien als een woordgrens.

Mocht je je afvragen waarom '.' elk teken behalve "\n" (\n is een enter teken) matched? De reden is dat we vaak alleen de regel willen matchen en de enter willen negeren. Bijvoorbeeld, wanneer \n een regel betekend willen we er graag over denken als leeg. In dat geval:

<?
preg_match("/^$/", ""); // matched
preg_match("/^$/", "\n"); // matched, "\n" word genegeerd
preg_match("/./", ""); // matched niet, het heeft een teken nodig
preg_match("/^.$/", ""); // matched niet, het heeft een teken nodig
preg_match("/^.$/", "\n");// matched niet, het heeft een teken nodig anders dan een enter
preg_match("/^.$", "a"); // matched
preg_match("/^.$", "a\n");// matched, de \n wordt genegeerd
?>

Dit gegrag is handig omdat we normaal gesproken de enters willen negeren en we de gehele string in een keer willen matchen. Soms wil je dat \n tekens wel meetellen, en dat ^ en $ niet het begin en eind van de string betekenen maar het begin en eind van een regel. In dit geval worden de //s en //m modifiers aangeboden om wel of niet op enters te letten in een string. Kortom: is het een set regels (bijvoorbeeld in een cvs bestand, waarin elke regel een aparte regel is? Of een doorlopende string waarin enters zitten, bijvoorbeeld in een comment op een website of een forum?

Deze twee modifiers beinvloeden hoe de regexp wordt geintrepeteerd.

1) hoe is de '.' character class gedefinieerd?
2) waar matchen de anchors ^ en $?

Hier zijn de vier mogelijke combinaties:

- geen modifiers: (//). Standaard gedrag. De '.' matched elk teken behandeld "\n". ^ matched alleen aan het begin van een string, en $ matched alleen aan het eind of voor een \n aan het eind.

- s modifier (//s). Behandel een string als één lange regel. \n matched elk teken, ook \n. ^ matched alleen aan het begin of aan het eind van een string.

- m modifier (//m). Behandel een string als een set van meedere regels. '.' matched elk teken, behalve \n. ^ en $ matchen het begin van een regel binnen de string.

- s en m modifier (//sm). Behandel de string als een set van meerdere regels. '.' maar detecteer meerdere regels. '.' zal elk teken matchen, ook \n, maar ^ en $ zullen aan het begin en eind van elke regel binnen de string matchen.

Zie hier de voorbeelden van //s en //m:

<?
$x = "There once was a girl\nWho programmed in Perl\n";
preg_match("/^Who/", $string); // doesn't match, "Who" not at start of string
preg_match("/^Who/s", $string), $string); # doesn't match, "Who" not at start of string
preg_match("/^Who/m", $string); // matches, "Who" at start of second line
preg_match("/^Who/sm", $string); // matches, "Who" at start of second line
preg_match("/girl.Who/", $string); // doesn't match, "." doesn't match "\n"
preg_match("/girl.Who/s", $string); // matches, "." matches "\n"
preg_match("/girl.Who/m", $string); // doesn't match, "." doesn't match "\n"
preg_match("/girl.Who/sm", $string); // matches, "." matches "\n"
?>

Meestal is het het standaard gedrag wat je wil, maar //s en //m zijn soms erg handig. Wanner //m wordt gebruik kan je nog steeds het begin en eind van de de string matchen met de anchors: \A en \Z (deze matchen ook de enters aan ervoor of erna)

<?
preg_match("/^Who/m", $string); // matches, "Who" at start of second line
preg_match("/\AWho/m", $string); // doesn't match, "Who" is not at start of string
preg_match("/girl$/m", $string); // matches, "girl" at end of first line
preg_match("/girl\Z/m", $string); // doesn't match, "girl" is not at end of string
preg_match("/Perl\Z/m", $string); // matches, "Perl" is at newline before end
preg_match("/Perl\z/m", $string); // doesn't match, "Perl" is not at end of string
?>

Dit was het voor de charachter classes. In het volgende deel wordt uigelegd hoe je verschillende sets kan matchen.
Pagina 4

Het een of het ander matchen

Soms willen we dat onze regexp in staat is om verschillende mogelijke woorden of tekens te matchen. Dit is mogelijk door het alternation metacharacter |. Om dog of cat te matchen gebruiken we de regexp dog|cat. Zoals eerder gezegd zal het geen dat het eerst voorkomt matchen.

<?
preg_match("/cat|dog|bird/", "cats and dogs"); // matched cat
preg_match("/dog|cat|bird/", "cats and dogs"); // matched cat
?>

Zelfs al is dog het eerste teken in de regexp, is cat het eerste wat voorkomt in de string en wat matched.

<?
preg_match("/c|ca|cat|cats/","cats"); // matched 'c'
preg_match("/cats|cat|ca|c/","cats"); // matched 'cats'
?>

In dit geval maakt de volgorde wel uit. De eerste geldige uit de set alternatieven zal matchen.
Pagina 5

Groeperen en hierarchisch matchen

Alterneren staat toe dat je uit verschillende alternatieven kunnen kiezen, maar soms wil je tussen verschillende regexps kunnen kiezen, en soms voor verschillende onderdelen van regeps. Bijvoorbeeld housecats of housekeepers. De regexp housecat|housekeepers zal dat doen, maar is niet erg efficient omdat we house twee keer hebben moeten typen. Het zou mooi zijn om onderdelen van de regexp constant te houden en uit anderen te kunnen kiezen bijvoorbeeld cat|keep.
De grouping metacharacters: () zorgen dat sommige delen als een worden beschouwt. Dus housecat|housekeep kan worden house(cat|keeper).

Voorbeelden:

/(a|b)b/	 matched ab of bb
/ac|b)b/     matched acb of bb
/(^a|b)c/    matched ac aan het begin of bc overal in de string
/(a|[bc])d/  matched ad, bd of cd
/house(cat|) matched housecat of house
/house(cat(s|)|)  matched of housecats, housecat of house. (Let op: het innestelen van groepen is mogelijk)
/(19|20|)\d\d/ matched de jaren 19xx, 20xx, of het Y2K probleem: xx


Alternatie gedraagd zich binnen groepen op dezelfde manier als erbuiten. De eerste die voorkomt zal matchen, en de eerste uit de alternatie die mogelijk kan matchen zal matchen.

Hoe dit precies werkt is uitgelegd in een stap bij stap analyse van het volgende commando:

<?
preg_match("/(abd|abc)(df|d|de)/", "abcde");
?>

1: Begin met de eerste letter in de string 'a'
2: probeer het eerste alternatief 'abd'
3: match 'a', gevolgd door 'b', to nu toe gaat het goed
4: 'd' in de regexp matched 'c' niet in de string, en deze optie gaat niet werken. Dus moeten we twee tekens terug en proberen we het tweede alternatief uit de groep abc
5: Match 'a' gevolgd door 'b' gevolgd door 'c', dit gaat goed en de eerste groep gaat werken.
6: Ga verder naar de tweede groep en kies het eerste alternatief 'df'
7: Match de 'd'
8: 'f' in de regexp matched de 'e' in de string niet en deze optie gaat niet werken, twee tekens terug, en we gaan verder met het tweede alternatief groep 'd'
9: 'd' matched, de tweede groep matched 'd'
10: we zijn aan het eind van de regexp, en we zijn kaar. We hebben 'abcd' uit de string 'abcde'

Wat opvalt is dat het derde alternatief uit de tweede groep ook 'de' had, maar 'd' was voldoende om de regexp te matchen en toen is de regexp gestopt. Ook was het al bij het eerste teken mogelijk om 'a' te matchen. Was dit niet mogelijk geweest waren eerst alle mogelijk matches verkend voordat we met het hele verhaal bij 'b' opnieuw waren begonnen. Als alle mogelijke opties niet hadden gewerkt, zou preg_match als waarde false terug geven.

Reguliere expressies zijn ondanks al dit werk nog opmerkelijk snel, ondanks dit is preg_match sneller dan de strtr en str_replace functies in php. Zie: http://www.simplemachines.org/community/index.php?topic=175031.0;imode
Pagina 6

Het extraheren van matches

De grouping metacharects hebben nog een tweede doel: het uit extraheren van een specifiek gematched gedeelte. Dit is erg handig om uit te zoeken wat gematched heeft, en om specifieke delen uit een string te halen. Voor elke groep is het deel dat gematched wordt een aparte slice in een array die je als derde argument in het preg_match commando kan opgeven.

Bijvoorbeeld:

<?
preg_match("/(\d\d):(\d\d):(\d\d)/", $datum, $datum_array);

print_r($datum_array);
// [0] => uren
// [1] => minuten
// [2] => seconden
?>

Wanneer de groeperingen in een regexp zijn genesteld is de volgorde in de array de volgorde van de groep die het eerst geopend wordt.
Voor complexe voorbeelden zal hij als volgt matchen:

/(ab(cd|ef)((gi)|j))/
 1  2      34

Dus als er een geldige waarde voor de regexp wordt gegevel zou in dit voorbeeld:

<?
preg_match("/(ab(cd|ef)((gi)|j))/", $string, $array);
?>

$array[1] gelijk zijn aan 'cd' of 'ef'.

Ook kan je matches gebruiken in de regex zelf!

Door backreferences te gebruiken: \1 \2 \3 etc. Kan je een de waarde van een gematchte groep gebruiken in de regex:

Zo zal:

 /(\w\w\w)\s\1/

Alle drieletterwoorden die achter elkaar stana matchen, bijvoorbeeld:
bow bow
yoo yoo
who who

etc.

Een voorbeeld van dit wanneer je dit tegen een woordenboek bestand zou matchen:

/^(\w\w\w\w|\w\w\w|\w\w|\w)\1$/
levert dit alle repeterende woorden op in het woordenboek.

beriberi
booboo
coco
mama
murmur
papa
Pagina 7

Repeterende matches

In het vorige voorbeelden kwamen we wat vervelende zaken tegen. We waren alleen 3 of 4 letter woorden aan het matchen. Wanneer we woorden zouden willen matchen van verschillende grote in zenuwslopende alternatieven als \w\w\w\w|\w\w\w|\w\w\w

Dit is precies het probleem dat de quantifiers metcharacters ? * + en {} oplossen. Hierdoor kan je aangeven hoeveel keer een deel van een regexp moet matchen. Quantifiers kunnen achter een enkel teken, een character class of een group worden geplaatst.

Ze hebben de volgende betekenis:
a? = match 'a' 1 of 0 keer
a* = match 'a' 0 of meer keer
a+ = match 'a' 1 of meer keer
a{n,m} = match a op zijn minst n keer, maar niet meer dan m keer.
a{n,} = match a op zijn minst n keer
a{n} = match a precies n keer

Hier wat voorbeelden:


/[a-z]+\s+\d*/

match een lowercase woord met zijn minst een spatie en meerdere nummers


/(\w+)\s+\1/

Match twee gelijke woorden die na elkaar staan.


/y(es)?/i;

Matched Y, y, yes en Yes.

<?
preg_match("/\d{2,4}/", $year);
//Het jaar moet uit 2 tot 4 cijfers bestaan
?>



<?
preg_match("/\d{2}|\d{4}/", $year);
// Beter: 3 cijfer jaargetallen zijn niet toegestaan
?>


<?
preg_match("/(\d{2}|\d{2})/", $year, $array);
// het zelfde in een andere notatie
// maar bij gebruik van $array is het met deze mogelijk om het 
// gehele getal af te vangen in $array[0]
?>

Ook bij quantifiers geld dat php de eerste quantifier zoveel mogelijk keer zal laten proberen te matchen. Pas wanneer dit niet lukt zal naar alternatieve matches worden gezocht. Dus bij /a? ../ zal eerst gezocht worden naar een oplossing waar a in zit, daarna pas naar een oplossing zonder a.

Voor het * quantifier krijgen we het volgende resultaat:

<?
$x = "the cat in the hat";
preg_match("/^(.*)(cat)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'the'
// [1] => 'cat'
// [2] => ' in the hat'
?>

Dit is omdat alleen cat in de string gevonden wordt, en maar een oplossing mogelijk is. Wanneer er meerdere oplossingen mogelijk zijn bijvoorbeeld bij 'at' krijg je andere resultaten:

<?
$x = "the cat in the hat";
preg_match("/^(.*)(at)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'the cat in the h'
// [1] => 'at'
// [2] => ''
?>

Dit is omdat de eerste quantifier zoveel mogelijk keer gematched wordt, en zo doende wordt pas de laatste 'at gematched, en is de derde groep leeg. Als regel geld dat de meeste rechtse quantifier greedy is en zoveel mogelijk matched.

Wanneer een regexp op verschillende manieren kan matchen gebruiken we het bovenstaande principe om te voorspellen hoe de regexp zal matchen:

Prinicpe 0: Over het geheel gezien zal de regexp op de eerst mogelijke positie in de string matchen
Prinicpe 1: In een alternatie a|b|c zal de het eerste alternatief dat een resultaat opleverd worden gebruik
Principe 2: De maximal matching quantifiers ?, *, + en {n,m} zullen over het algemeen zoveel mogelijk keer matchen als mogelijk zolang het een resultaat opleverd voor de regexp
Principe 3: Als er twee of meer elementen in een regexp zijn, zal de meest linkse zoveel mogelijk keer matchen, zolang de regexp een resultaat heeft. Daarna zal de volgende linkse zo vaak mogelijk worden voldaan, enzovoorts.

Dit is de volgorde waarop een regexp zal matchen.

Hier is een voorbeeld van deze pincipes in actie:

<?
$x = "The programming republic of Perl";
preg_match("/^(.+)(e|r)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'The programming republic of Pe'
// [1] => 'r'
// [2] => 'l'
?>

In dit geval begint de match gelijk al bij T. Bij de keuze uit e of r, matched r, omdat als eerste de linker greedy .* wordt voldaan.


<?
$x = "The programming republic of Perl";
preg_match("/^(m{1,2})(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'mm'
// [1] => 'ing republic of Perl'
?>

Hier is de eerst mogelijke match bij de eerste 'm' van programming. m{1,2} is de eerste quantifier en worden het maximaal aantal matches (2) gekozen.

<?
$x = "The programming republic of Perl";
preg_match("/^.*(m{1,2})(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'm'
// [1] => 'ing republic of Perl'
?>


Hier matched de .* eerst zoveel mogelijk en is m{1,2} ook tevreden met 1, en matched de eerste groep slechts een m.

<?
$x = "The programming republic of Perl";
preg_match("/^(.?)(m{1,2})(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'a'
// [1] => 'mm'
// [2] => 'ing republic of Perl'
?>

Hier matched .? op de eerste positie mogelijk (de eerste positie die gevolgd wordt door een 'm') een letter, op de 'a'. Hierdoor worden de twee opvolgende 'mm' ook gematched.

Valkuilen:
Reguliere expressies kunnen je misleiden en onverwachte resultaten geven, daarom is het goed om inzicht te hebben hoe ze werken.

<?
preg_match("/(X*)/","aXXXb", $array);
// $array[0] = ''
?>

Hierbij levert X* bij de eerste mogelijkheid (de a) een geldige maximale hoeveelheid van 0 op. Omdat dat een goede match is (* is 0 of meer keer) wordt er niet verder gezocht naar andere matches.

In dit geval had X+ een betere optie geweest.

Soms is een afwijking van het greedy gedrag gewenst. Dat sommige quantifiers juist een minimale hoeveelheid van een string in plaats van zoveel mogelijk matchen. Voor dit doel zijn voor perl minimal match of anders gezegd non-greedy quantifiers gemaakt: ??, *?, +?, {}?. Dit zijn de gebruikelijke quantifiers met een ? eraan geplakt. Ze hebben de volgende betekenis:

- a?? = match 'a' 0 of 1 keer, probeer eerst 0 dan 1.
- a*? = match 'a' 0 of meer keer, in andere woorden: elk aantal, maar zo min mogelijk
- a+? = match 'a' 1 of meer keer, maar zo min mogelijk
- a{n,m}? = match 'a' op zijn minst n keer en niet meer dan m, maar zo min mogelijk
- a{n,}? = match 'a' op zijn minst n keer, en zo min mogelijk
- a{n}? = match 'a' precies n keer. Hier maakt de non-greedy quantifier geen verschil.


Laten we het vorige voorbeeld maar dan met minimal quantifiers bekijken:

<?
$x = "The programming republic of Perl";
preg_match("/^(.+?)(e|r)(.*)$/", $x, $matches);
print_r($matches);
// [0] => 'Th'
// [1] => 'e'
// [2] => ' programming republic of Perl'
?>

Doordat de eerste groep Zo min mogelijk keer matched, zal de eerst mogelijke oplossing voor de tweede groep de 'e' zijn. De rest is vrij om in de derde groep te worden opgevangen.

<?
$x = "The programming republic of Perl";
preg_match("/m{1,2}?(.*?)$/", $x, $matches);
print_r($matches);
// [0] => 'm'
// [1] => 'ming republic of Perl'
?>

Hier krijg je non-greedy gedrag op zijn top. Beide quantifiers willen zo min mogelijk, maar de eerste quantifier krijgt het minst. Doordat de tweede groep gelimiteerd is tot het eind van de string matched het niet 0 tekens, maar tot het eind van de string.

<?
$x = "The programming republic of Perl";
preg_match("/(.*?)(m{1,2})?(.*?)$/", $x, $matches);
print_r($matches);
// [0] => 'The progra'
// [1] => 'm'
// [2] => 'ming republic of Perl'
?>

In deze regexp zou jemisschien verwachten dat de eerste minimal quantifier .*? een lege string zou matchen omdat het niet beperkt is door een anchor ^ om aan het begin te matchen. Het eerder genoemde principe 0 is hier van werking waar we het eerder over hadden: omdat het mogelijk is de string vanaf het begin te matchen, gebeurt dat ook. Dus is de eerste oplossing voor deze regexp van de eerste goep alle tekens tot de eerste m van programming.

<?
$x = "The programming republic of Perl";
preg_match("/(.??)(m{1,2})(.*?)$/", $x, $matches);
print_r($matches);
// [0] => 'a'
// [1] => 'mm'
// [2] => 'ing republic of Perl'
?>

Net zoals in de vorige regexp is het voor de eerste quantifier .?? mogelijk om te matchen op de 'a' en gebeurt dat ook. De tweede quantifier is greedy en matched beide mm's. De derde matched de rest.

Het derde principe kunnen we herschrijven voor non-greedy quantifiers:
Principe 3: als er twe of meer elementen in een regexp zijn, zal de meest linker greedy (non-greedy) quantifier, als aanwezig, zoveel mogelijk (zo min mogelijk)

[edit]
Principe 3: Als er twee of meer elementen in een regexp zijn, zal de meest linkse greedy [non-greedy] zoveel mogelijk [zo min mogelijk] keer matchen, zolang de regexp een resultaat heeft. Daarna zal de volgende linkse zo vaak [min] mogelijk worden voldaan, enzovoorts.
[/edit]

Net als bij alternatie kunnen we het process stap voor stap doorlopen.


<?
$x = "the cat in the hat";
preg_match("/^(.*)(at)(.*)$/", $x, $matches);
// $matches[0] = 'the cat in the h'
// $matches[1] = 'at'
// $matches[2] = ''
?>

1. Start met de eerste letter in de string t
2. De eerste quantifier .* begint met het matchen van de hele string.
3. 'a' in het regexp element at matched niet aan het eind van de string, een teken terugspringen.
4. 'a' in de regexp matched nog steeds niet aan het einde van de string 't', dus doen we nog een stap terug
5. Nu kunnen we de 'a' en de 't' matchen.
6. We gaan door naar het derde element .* Omdat deze genoegen neemt met 0 keer matchen zijn we klaar en kunnen we naar huis.

Meestal zijn deze regexps behoorlijk snel. Sommige regexps zijn exponentieel langzamer, hoe groter de string wordt. Een typische structuur van een regexp die erg lang kan duren is
/(a|b+)*/

In dit geval zijn er vele verschillende mogelijkheden voor de regexp en worden ze in de eerder beschreven volgorde allemaal afgelopen. Het kan bij een lange string erg lang duren voordat er wel of geen match is gevonden.

Voor meer informatie is het boek Mastering regular expressions door Jeffrey Friedl aan te raden http://www.amazon.com/Mastering-Regular-Expressions-Jeffrey-Friedl/dp/0596528124
Pagina 8

Disclaimer, bronvermelding, handige links, TODO

7. Disclaimer, bronvermelding en handige links
Bronvermelding
Deze tutorial is primair gebaseerd op de perlretut uit de perl documentatie, geschreven door Mark Kvale

Link: perlretut

Links
Regex Cheat sheets
Jan Koehoorns Handige regex tester (helaas alleen nog met ereg cheat sheet)
Snelheids tests met str_replace, substr en preg_match
Mastering Regular Expressions, Door Jeffrey Frield op Amazon
Test hier je eigen reguliere expressies (zeer inzichtelijk)

Disclaimer
Deze tutorial is een rip off, een complete fake en ik heb hem zelf niet geschreven. Nouja, niet helemaal. Deze tutorial is gebaseerd op de perlretut uit de perl documentatie, geschreven door Mark Kvale. Dat klopt, de perl documentatie, vrijgegeven onder de Artistic Licence. Omdat in perl regex is opgenomen standaard in de syntax van de taal vereist het enige vertaling om dezelfde technieken in de preg_match functie te gebruiken. Verder heb ik de documentatie naar het nederlands vertaald.

In deze tutorial heb ik engelse termen gebruikt omdat ik simpelweg niet altijd snel genoeg passende nederlandse vertalingen kan vinden.

Hoewel ik alles zeer zorgvuldig getest en gecontroleerd heb, kan ik niet garanderen dat erop dit moment geen spelfouten, typefouten, vertaalfouten of andere fouten in deze tutorial zitten. Ik sta open voor alle suggesties en commentaar, en beschouw deze tutorial vooralsnog als een Work in Progress, laat me weten wat je er van vind.

TODO
- Een kortere samenvatting van de tutorial.
- Een lijst met voorbeelden van handige regexes (email, url matching, datum, postcode, telefoonnummer, etc).

Reacties

0
Nog geen reacties.