Regular expressions
Dit document beschrijft de werking van de regular expression optie van substitution rules. Hoewel dit document niet bedoeld is als bron voor het leren van regular expressions, wordt er wel een korte introductie gegeven voor de nieuwe gebruiker.
Regular expressions in Clonable
Als je al bekend bent met regular expressions (regex) in andere programma's, kan het zijn dat ze binnen Clonable niet altijd werken zoals je verwacht. Binnen Clonable wordt namelijk gebruikgemaakt van een non-backtracking regex engine om problemen te voorkomen. Hierdoor kan het voorkomen dat ingewikkelde regex niet of anders werken dan in een backtracking engine.
De meeste regex engines zijn gebaseerd op Perl Compatible Regular Expressions (PCRE). Deze engines maken vaak gebruik van backtracking, maar dit is vanwege veiligheidsredenen niet geschikt binnen Clonable. Daarom maakt Clonable gebruik van een non-backtracking engine die een subset van PCRE ondersteunt. Dit betekent dat bijvoorbeeld look-aheads niet werken. Heb je hierover specifieke vragen, neem dan contact met ons op.
Introductie regular expressions
Heb je al ervaring met regular expressions (regex), dan hoef je dit hoofdstuk waarschijnlijk niet te lezen. Ben je nog niet bekend met regex, dan is dit een kleine introductie om te starten. Aan het einde van dit hoofdstuk vind je verdere bronnen waarmee je meer kunt oefenen met regex.
Waarvoor wordt een regex gebruikt?
Een regex gebruik je wanneer je meerdere dingen wilt afvangen die veel op elkaar lijken, maar op sommige plekken afwijken. Denk hierbij bijvoorbeeld aan zinnen die de voorraad vermelden met daarin een getal, of zinnen waarin verschillende data staan. Je kunt dan heel veel verschillende substitution rules maken voor alle mogelijke combinaties, maar dit is vaak niet te doen of kost veel tijd.
Karaktergroepen
Karaktergroepen worden in regex gebruikt om aan te geven wat voor soort teken er op een bepaalde plek mag staan. Een karaktergroep wordt altijd tussen twee vierkante haken ([]) vermeld. Hierbinnen staan dan de toegestane karakters. Dit kan ook een bereik van karakters zijn: zo betekent [0-9] alle cijfers van 0 tot 9 en [A-Z] alle hoofdletters. Karaktergroepen kunnen ook gecombineerd worden; [A-Za-z] matcht bijvoorbeeld alle grote en kleine letters.
// Matcht zowel hallo als Hallo
[Hh]allo
// Matcht elk getal van 100 tot 499:
[1-4][0-9][0-9]
Quantifiers
Zoals in het voorbeeld hierboven te zien is, moest de [0-9] herhaald worden. Naast het feit dat dit de regex onoverzichtelijk maakt, is het ook niet flexibel (het is bijvoorbeeld niet mogelijk een regex te maken die de getallen 1 tot 99 matcht). Om dit probleem op te lossen kun je quantifiers gebruiken. Deze geven aan hoe vaak het karakter (of de karaktergroep) ervoor moet voorkomen. Deze quantifiers worden altijd vermeld tussen accolades ({}). Hierbinnen staat, gescheiden door een komma, wat het minimum en maximum is. Zijn het minimum en maximum hetzelfde, dan hoef je maar één getal te vermelden. Zo betekent {1,3} minstens 1 en maximaal 3, en betekenen {4,4} en {4} beide exact 4.
Daarnaast zijn er ook nog een aantal speciale quantifiers. ? betekent 0 of 1, + betekent 1 of meer en * betekent 0 of meer.
// Matcht hoi en hooi
ho{1,2}i
// Matcht elk getal beginnend met een 1
1[0-9]*
// Matcht elk getal beginnend met een 1 en dat groter of gelijk is aan 10
1[0-9]+
// Matcht elk getal van 1 tot 19
1[0-9]?
// Matcht elk woord (hoofd- en kleine letters) van 6 letters lang
[A-Za-z]{6}
// Probeer het zelf: maak een regex die alle woorden matcht die beginnen met een hoofdletter. (Antwoord: zie voetnoot 1)
+ en *Moet je in je regex gebruikmaken van de + of * quantifiers, dan is het meestal een goed idee om +? of *? te gebruiken. Door het vraagteken toe te voegen matcht de regex zo min mogelijk, waardoor je voorkomt dat de regel niet werkt. Voorbeeld:
// Tussen [] staat aangegeven wat gematcht wordt
// [<img src="/images/photo.jpg"><a href="/home"]>home</a>
<img src=".*"
// [<img src="/images/photo.jpg"]><a href="/home">home</a>
<img src=".*?"
Je kunt je voorstellen dat het bovenste gedrag in een volledige webpagina problemen oplevert, omdat er dan een veel te lange match ontstaat.
(Match)groepen
Het kan voorkomen dat je specifieker wilt zijn dan een karaktergroep met een bepaalde lengte. Ook daarvoor heeft regex een oplossing, in de vorm van (match)groepen. Een matchgroep wordt geplaatst tussen twee haakjes (()). Hierbinnen kan dan een reeks tekens en/of karaktergroepen gezet worden die samen één geheel vormen. Achter deze groepen kan dan weer een quantifier gezet worden.
// Matcht nom, nomnom, nomnomnom en nomnomnomnom
(nom){1,4}
// Matcht elk woord met daarin de letters oe
[A-Za-z]*(oe)[A-Za-z]*
// Probeer het zelf: maak een regex die alle woorden matcht met daarin 2 of 4 klinkers achter elkaar. (Antwoord: zie voetnoot 2)
Daarnaast zijn deze matchgroepen ook nog heel handig bij het overnemen van data in de replacement van een substitution (bijvoorbeeld getallen). Je kunt namelijk in de replacement de inhoud van zo'n groep overnemen door gebruik te maken van $[groepnummer]. Zie onderstaand simpel voorbeeld voor het verplaatsen van het euroteken in een geldbedrag van voor naar achter het getal:
Original Replacement | Options | |
|---|---|---|
Gereserveerde tekens gebruiken
Zoals je misschien al hebt opgemerkt, gebruikt regex een aantal tekens voor de syntax die ook in zinnen voor kunnen komen (bijvoorbeeld ? of .). Je vraagt je dan misschien af hoe je die tekens zelf kunt gebruiken. Dat is gelukkig zeer eenvoudig: wil je een teken gebruiken zonder dat het zijn functie in de regex uitvoert, dan plaats je er een backslash (\) voor.
// Matcht Hallo. maar ook Hallok of Hallo@, etc.
Hallo.
// Matcht alleen Hallo.
Hallo\.
// Matcht internationaal
(inter)nationaal
// Matcht (in)ternationaal
\(inter\)national
Overige tips
- Een
.is een wildcard voor elk teken. Met.*matcht je dus alles (elke hoeveelheid van elk teken). \dis een alias voor[0-9][A-z]is niet hetzelfde als[A-Za-z], maar bevat ook een aantal leestekens