Perl Compatible Regular Expressions (PCRE)

Indhold:

  1. Resumé
  2. Introduktion
  3. En lille advarsel
  4. Hvorfor bruge regulære udtryk
  5. preg_match og det første regulære udtryk
  6. Et test script
  7. Tips
  8. Simple mønstre
  9. ^ og $
  10. "|" eller
  11. "Character Classes"
  12. "Quantifiers"
  13. Grådige udtryk
  14. Meta karakterer og escaping
  15. "Modifiers"
  16. preg_match's tredie argument
  17. Search 'n (Destroy|Replace)
  18. Hvad er ikke dækket
  19. Eksempler
  20. Shortcuts

1. Resumé

I denne note gives en introduktion til brugen af regulære udtryk, også kaldet mønstre, i PHP. Noten beskriver hvordan regulære udtryk bruges til at klassificere strenge og derved kan bruges til at sikre at brugerinput følger en specificeret form og til at identificere, og derved gøre brug af, en specificeret del af en streng.

Konkret introduceres læseren til PCRE (Perl Compatible Regular Expressions) og PHP-funktionen preg_match.

Top

2. Introduktion

Regulære udtryk bruges til at klassificere tekststrenge. Uden brug af regulære udtryk kan vi skrive PHP-kode som undersøger om to strenge er identiske (består af de samme tegn). Følgende PHP-kode undersøger for eksempel om en variabel $email indeholder strengen holbech@itu.dk, strengen someone@itu.dk eller en helt tredie streng:


<?php
    
if ( $email == 'holbech@itu.dk' ) {
        
// gør noget
    
} elseif ( $email == 'someone@itu.dk' ) {
        
// gør noget andet
    
} else {
        
// gør noget tredie
    
}
?>

Denne løsning virker fint så længe vi arbejder med en begrænset, foruddefineret, mængde data. Hvis vi derimod arbejder med input fra brugere, må vi ofte understøtte en større mængde af inputmuligheder, såsom alle strenge som tager form af et cpr-nummer, alle strenge som tager form af en emailadresse, og så videre. Som vi skal se i det følgende giver regulære udtryk, også kaldet mønstre, mulighed for, meget præcist at specificere hvilken form for data der skal accepteres af programmet.

Top

3. En lille advarsel

Når man søger på regulære udtryk vil en stor del af resultaterne omhandle en anden slags regulære udtryk kaldet POSIX. Forskellen er ikke nødvendigvis så stor, men vær opmærksom på at ikke alt der gælder for POSIX gælder for PCRE og omvendt.

Top

4. Hvorfor bruge regulære udtryk

Regulære udtryk er nyttige til en lang række anvendelser i forbindelse med udvikling af webapplikationer:

  1. Validering af brugerinput
    Det er som hovedregel nødvendigt at validere/tjekke input fra brugere for at sikre systemet. Herudover giver validering af brugerinput mulighed for at hjælpe brugere med præcise fejlmeddelelser. Hvis for eksempel vores system beder en bruger om en emailadresse til at sende en adgangskode til, kan systemet, ved brug af regulære udtryk, undersøge om brugerens indtastning ligner en emailadresse. På samme måde kan systemet sikre at postnumre ligner postnumre, at cpr-numre ligner cpr-numre, og så videre.

    Bemærk dog at regulære udtryk ofte kun kan benyttes til at undersøge om data er velformet og ikke om data konkret giver mening. Eksempelvis giver regulære udtryk ikke mulighed for at undersøge om en email-adresse konkret eksisterer, at en person med et givet cpr-nummer eksisterer eller om et postnummer eksisterer. På samme vis er det vanskeligt med regulære udtryk præcist at undersøge om en angivet dato eksisterer (f.eks. er det vanskeligt at specificere at datoen 2007-02-29 ikke er en eksisterende dato, men at datoen 2004-02-29 er eksisterende). Det er dog muligt at bruge regulære udtryk til at undersøge om data følger ISO-formatet YYYY-MM-DD.

  2. Anvendelse af data fra fremmede websider
    Anvendelse af data fra fremmede websider (også kaldet webscraping) er ofte let at implementere ved brug af regulære udtryk til præcist at specificere hvilke dele af en webside systemet vil gøre brug af. Det er således muligt med regulære udtryk at finde, og gøre brug af, data der allerede er publiceret på Internettet. Eksempler på sådan data inkluderer valutakurser, vejrprognoser, nyheder og aktiekurser.

  3. Søgning efter mønstre i datamængder
    Regulære udtryk kan med fordel benyttes til at søge efter data i begrænsede datamængder, såsom strenge af begrænset størrelse. Regulære udtryk kan også bruges til at søge efter relevante rækker i en databasetabel; eksempelvis understøtter MySQL brug af regulære udtryk i WHERE-betingelser.

  4. Tekstsubstitution i strenge
    Regulære udtryk kan også bruges til udskiftning af delstrenge i en streng (søg-og-erstat). En sådan anvendelse af regulære udtryk kan for eksempel bruges til at generere specifikke email-beskeder ud fra en template, hvori modtagerens navn er refereret til som NAVN.

Top

5. preg_match og det første regulære udtryk

I det følgende introduceres begrebet "mønstre" (engelsk: patterns), der egentligt er et synonym for "regulære udtryk".

Et mønster er en stump tekst der definerer hvilken form, det vi leder efter har (f.eks. indeholder stregen kun bogstaver, kun tal etc.).

Sammen med mønsteret har vi en streng (ofte brugerinput) der angiver hvor vi leder henne.

Ud fra vores mønster kan vi så spørge "matcher mønsteret X strengen Y"

Eller: findes mønsteret X i strengen Y

PHP funktionen vi skal benytte hedder preg_match. Den tager to til tre argumenter. Først det mønster vi ledder efter, dernæst den streng vi leder i og til sidst kan vi angive navnet på et array vi vil fylde noget i. Det tredie argument kigger vi på senere.

Lad os kigge på et eksempel:


<?php
if(preg_match('/a/''kat')){
    echo 
"Der er et 'a' i 'kat'";
} else {
    echo 
"Der er ikke noget 'a' i 'kat'";
}
?>
  1. Vi definerer vores mønster til at være /a/ og vi leder i strengen "kat"
  2. Funktionen returnerer 1 eller 0
  3. Vi benytter det i vores if sætning til at se om det er sandt eller flask
  4. Selve vores regulære udtryk/mønster er a
  5. De to / fortæller hvornår vores regulære udtryk starter og hvornår det stopper
Top

6. Et test script

For at teste dine regulære udtryk kan du med fordel snuppe nedenstående script og gemme det på din server som pcre_test.php, eller du kan finde det her: pcre_test.php. Mens du læser, så prøv lidt udtryk af, det er meget lettere når man gør det


<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"
   "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml" lang="en" xml:lang="en">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1" />
    <link rel="stylesheet" href="style.css" type="text/css" media="screen" />

        <title>Perl Compatible Regular Expressions (PCRE)</title>
    </head>
    <body>
<h1>Perl Compatible Regular Expressions (PCRE)</h1>

<?php
if(isset($_REQUEST['expr'])){
    
$matches=array();
    
$expr=$_REQUEST['expr'];
    
$str=$_REQUEST['str'];
    echo 
"<div>";
    if(
preg_match($expr$str$matches)){
        echo 
"<p class='success'>Dit udtryk <code>".htmlspecialchars($expr)."</code> matcher strengen</p>";
        echo 
"<div>Arrayet der indeholder \"matches\" ser således ud:</div>";
        echo 
"<pre class='shell'>";
        
$s=print_r($matches1);
        echo 
htmlspecialchars($s);
        echo 
"</pre>";
    } else {
        echo 
"<p class='error'>Dit udtryk <code>".htmlspecialchars($expr)."</code> matcher IKKE strengen</p>";
    }
    echo 
"</div>";
}
?>
<form action='pcre_test.php' method='post'>
    <div>
        <label>Regulært udtryk (husk delimiters (/  /)<br />
        <input type='text' class='formfield largefriendlyletters' name='expr' size='50' value="<?php echo htmlentities($_REQUEST['expr']);?>" /></label>
    </div>
    <div>
        <label>Streng der skal tjekkes<br />
        <textarea name='str' class='formfield' rows='5' cols='30'><?php echo $_REQUEST['str'];?></textarea></label>
    </div>
    <div>
        <input type='submit' value='Tjek' />
    </div>

</form>
</body>
</html>
Top

7. Tips

Det kan være unødvendigt bøvlet at lave regulære udtryk, et par hints.

  1. Hvad kendetegner det vi leder efter, hvilke regler kan vi opstille, eksempel, "hvad er dit fødselsår?":
    1. Et årstal består af fire tal
    2. De første to tal skal være 19 eller 20
    3. De sidste to skal bare være tal
  2. Tag lidt af gangen, start med det helt simple og få det til at virke
  3. Udvid det, så det finder lidt mere, er lidt bedre etc.
  4. Der er altid flere muligheder, nogle gange er det lettest at skrive hvad man IKKE vil have med frem for at sige hvad man vil have med
Top

8. Simple mønstre

Tidligere så vi det regulære udtryk /a/, oversat betyder det at:

  1. / : her starter vores regulære udtryk
  2. a : her er selve mønsteret vi leder efter
  3. / : her slutter vores regulære udtryk

De to / tegn kaldes "delimiters". Vi kan bruge forskellige tegn som delimiters (oftest /, ! eller | ). Det tegn vi bruger som delimiters, kan vi ikke bruge som en del af det mønster vi ønsker at finde (UDEN at escape tegnet - mere om det senere). Delimiter-tegnet må ikke være bogstaver eller tal

Det tegn vi starter med skal også være det vi slutter med.

De simpleste mønstre vi kan lave er mønstre der matcher bogstav for bogstav

	Mønsteret /as/ matcher eksempelvis strengene: Jonas og bas
	
	Mønsteret /dag/ matcher eksempelvis strengene: Mandag og tirsdag

Møsntre er som udgangspunkt "case sensitive", dvs der er forskel på store og små bogstaver

	Mønsteret /as/ matcher IKKE strengen: Asger
	
	Mønsteret /As/ matcher blandt andet strengen: Asger
Top

9. ^ og $

De to tegn ^ og $ har en speciel betydning i regulære udtryk

Indtil videre har vores regulære udtryk ledt efter matches inde i strengen, når vi har ledt efter et bogstav har vi været tilfredse hvis bogstavet bare var et eller andet sted.

Når vi tjekker brugerinput er det ikke godt nok.

^ angiver at vores regulære udtryk skal matche fra starten af strengen

$ angiver at vores regulære udtryk skal matche til slutningen af strengen

	Udtrykket /Hans/ matcher strengen: "Anders Hansen" fordi Hans findes inde i strengen
	
	Udtrykket /^Hans/ matcher ikke strengen fordi strengen ikke starter med Hans
	Udtrykket matcher tilgengæld strengen: "Hansen, Anders"
	
	Udtrykket /Hans$/ matcher ikke strengen fordi den ikke slutter på: "Hans"
	Men udtrykket matcher stadig mere end Hans, eksempelvis: "Der er Hans"
	
	Rigtigt ofte benytter vi ^ og $ sammen til at tjekke på hele strengen:
	
	/^Hans$/ matcher KUN strengen "Hans", intet andet
	
Top

10. "|" eller

"Stregen" | betyder i regulære udtryk: eller, som i enten eller.

I det efterfølgende bruger vi det engelske ord "pipe".

Vi kan benytte pipe'n sammen med paranteser til at lave nogle ret avancerede ting

	Mønsteret /ab|cd/ mather de to strenge: ab og cd

Dvs. | siger enten skal det være alt det på min venstre side, ellers skal det være det fra min højre

Så kan vi bruge paranteser til at indsnævre hvor langt til siden den må kigge

	Mønsteret /(Man|Tirs|Ons|Tor|Fre)dag/ mather strengene: Mandag, Tirsdag, Onsdag, Torsdag og Fredag
Prøv selv at udvide det regulære udtryk så det også matcher Lørdag og Søndag
Top

11. "Character Classes"

Foreløbigt er det her ikke særligt smart,vi skal stadig skrive ret præcists hvad det er vi vil have.

Character classes to the rescue

Vi kan angive tegn inde i hårde klammer [ og ] og så skal strengen bare matche et af tegnene

Samtidigt kan vi angive intervaller, a la a-z, A-Z, 0-9 og A-z

Og så kan vi kombinere de to ting: Alle danske bogstaver: [a-zæøå]

	Et mønster der matcher datoer (danske):
	/[0-3][0-9]-[0-1][0-9]-[0-9][0-9][0-9][0-9]/
	Bindestregerne imellem klasserne er bare bindestreger, så vores datoer ser således ud: 31-12-2010
	
	Et mønster der matcher danske navne der starter med stort og har fire bogstaver:
	/[A-ZÆØÅ][a-zæøå][a-zæøå][a-zæøå]/
	
	Alle almindelige tegn, inklusiv mellemrum
	/[A-zÆØÅæøå0-9 -]/
Top

12. "Quantifiers"

Stadig ikke smart nok? No worries

Med "quantifiers" kan man sige, forgående del-mønster vil vi gerne have X gange

Vi skal kende følgende quantifiers

Quantifier Betydning Eksempel på brug Note
? 0 eller 1 gang /(+45)?/ Nul eller en gang dansk landekode (hele blokken +45 nul eller en gang)
* 0 eller flere gange /[0-9]*/ Nul ellere flere tal
+ 1 eller flere gange /[A-Z][a-z]+/ Et stort bogstav, efterfulgt af en eller flere små bogstaver
{antal} Præcis antal matches /[0-9]{4}/ Præcis fire tal (et postnummer?)
{start, slut} Mellem start og slut matches, begge inklusive /[0-9]{2,4}/ Fra to til fire tal (evt årstal med uden 20..)
{start, } start eller flere matches /[0-9]{1,}/ Et eller flere tal (svarer til +)

Quantifiers knytter sig til det der står lige til venstre for dem, og ikke ligesom | der er "grådig"

For at få quantifiers til at knytte sig til mere, benytter vi paranteser eller klasser

	Det regulære udtryk /ab*/ matcher
	a, ab, abb, abbb, abbbbbbb ...
	Fordi vi ikke bruger parantes står der: "et a, efterfulgt af nul eller flere b'er
	
	Det regulære udtryk /(ab)*/ matcher (ud over en tome streng!)
	ab, abab, ababab ...
	Nul eller flere forekomster af hele parantesen
Top

13. Grådige udtryk

Regulære udtryk er som udgangspunkt grådige

De forsøger at matche så meget de overhovedet kan

	Hvis vi har strengen: ababababab
	Så matcher det regulære udtryk: /[ab]*/ hele strengen
Top

14. Meta karakterer og escaping

Der er, som vi faktisk allerede har set, en række tegn der har speciel betydning i regulære udtryk

Tegn Betydning
\ Escape tegn
^ Match fra starten af strengen
$ Match til slutningen af strengen
. Wildcard, alle tegn undtagen linieskift (\n)
| Eller (som i enten eller)
[ ] Start og slut klasser
( ) Sub patterns (grupperinger)
{ } Start/slut quantifiers

Hvis vi vil matche disse tegn skal vi escape dem. Escaping foregår ved at vi sætter escpaetegnet (\) foran.

Det ene tegn der endnu ikke er gennmemgået er punktummet.

Som der står er det et wildcard, dvs det gælder for alt (bortset fra linieskift: \n)

	Det regulære udtryk /^.{2}$/ mather en hver streng der består af to tegn (igen, uden linieskift)
	Eks: .a, ba, '", 9t
	
	Det regulære udtryk /^\.\.$/ mather strengen:
	..
	
	Et simpelt mønster til at matche email adresser:
	/^.+@.+\.[a-z]+$/

Det sidste udtryk forklaret:

  1. Vi benytter ^ og $ til at angive det er hele strengen der skal matche, email adressen skal ikke bare være et sted i strengen
  2. .+ : først en række vilkårlige tegn
  3. @ : så skal der komme et snabel-a
  4. .+ : efterfulgt af en række vilkårlige tegn
  5. \. : et punktum (bemærk vi escaper punktummet så det ikke længere er et wildcard)
  6. [a-z]+ : et eller flere bogstaver (domæneendelse, a la, "dk")

Når vi benytter de her meta karakterer inde i klasser ([ ]), mister de fleste deres specielle betydning og behøver ikke escapes

Der er dog fire af tegnene vi skal være opmærksomme på når de benyttes i klasser:

  1. ^ : Når ^ er første tegn i klassen betyder den "må ikke forekomme"
    	Så udtrykket /[^a-z]/ matcher alle strenge der ikke indeholder et af bogstaverne fra a-z
    	
  2. \ : Escape tegnet er stadig escape tegnet
  3. - : Bindestregen bruges jo til at angive intervaller [a-z], hvis vi vil have at klassen skal matche bindestreger skal det være det sidste tegn
    	Udtrykket /[a-z-]+/ matcher eksempelvis:
    	sammen-slutning
    	
  4. ] : er det tegn der lukker klassen, og derfor kan vi ikke bruge det (uden at escape)
Top

15. "Modifiers"

Der findes en række modifiers til regulære udtryk. Modifiers er (surprise!) ting der modificerer et udtryk.

Vi angiver vores modifiers efter den sidste delimiter

Vi bekymrer os kun om to her

  1. i : angiver at det regulære udtryk skal opfattes "case-insensitive"
  2. U : angiver at udtrykket skal være "non-greedy"
	Udtrykket /^anders$/ mathcer IKKE strengen:
	Anders
	Mens udtrykket /^anders$/i gør
	
	
	U:
	Hvis vi har strengen: <a href='somewhere.html'>Link</a>
		
	Så matcher det regulære udtryk: /<.+>/ hele strengen, mens /<.+>/U matcher til første '>' (<a href='somewhere.html'>)
	
Top

16. preg_match's tredie argument

Indtil videre har vi kun testet på OM noget har matchet, men ikke HVAD der har matchet

Funktionen preg_match kan tage et tredie argument som vil blive fyldt med alle matches (i et array)

Logikken er at alt der matches ender som index 0, første parantes som index 1, anden parantes som index 2 ...


<?php
$string
="<h1>All your base are belong to us</h1>"
preg_match('!<h1>([A-Za-z ]+)</h1>!'$string$matches);

echo 
$matches[0]; // udskriver <h1>All your base are belong to us</h1>
echo "<br />";
echo 
$matches[1]; // udskriver indholdet af første parantes: All your base are belong to us
?>

Læg mærke til at vi benytter ! som delimiters, da strengen vi kigger i indeholder /

Top

17. Search 'n (Destroy|Replace)

Med funktionen preg_replace($pattern, $replacement, $subject) kan vi så lave søg og erstat

Funktionen tager tre argumenter, $pattern der angiver hvad vi søger efter, $replacement der angiver hvad vi vil udskifte med og $subject der angiver hvilken streng vi leder i.

Funktionen returnerer så en streng hvor alle forekomster af $pattern i strengen $subject er udskiftet med $replacement


<?php
    $string
="Kære NAME, velkommen ombord";
    
$newname="Anders";
    
    
$newstring=preg_replace('/NAME/'$newname$string);
    
    echo 
$newstring// Udskriver: Kære Anders, velkommen ombord
    
?>
Top

18. Hvad er ikke dækket

Dette afsnit er ikke en del af pensum!

PCRE er ganske omfattende og kan rigtigt mange ting der ikke er blevet gennemgået her. Dette er kun en introduktion til det mest basale. Nedenfor er et par links hvis dui vil vide mere

Top

19. Eksempler

Husk, der er altid mange måder at lave sine regulære udtryk på

	Ligner det en gyldig email:
	/^[A-Za-z_.-]+@[A-Za-z_.-]+\.[a-z]+$/
	
	Eller ved at angive hvad der ikke må være:
	/^[^@]+@[^@]+\.[^@]+$/
	
	Et gyldigt dansk postnummer (før Danmarks Radio fik et postnummer der startede med nul):
	/^[1-9][0-9]{3}$/
	eller
	/^[1-9][0-9][0-9][0-9]$/
	eller (med nul)
	/^[0-9]{4}$/
	
	Navne: Fornavn, Evt mellemnavne og Efternavn
	/^[A-ZÆØÅ][a-zæøå-]+( [A-ZÆØÅ][a-zæøå-]+)+$/
	Læg mærke til at den sidste del kan gentages en eller flere gange: ( [A-ZÆØÅ][a-zæøå-]+)+
Top

20. Shortcuts

Dette afsnit er ikke en del af pensum!

Fordi vi så ofte benytter de samme klasser, findes der et par shortcuts i sproget

Klasse Shortcut
[0-9] \d
[\f\r\t\n\v] \s (betyder hvilken som helst slags "white-space")
[A-Za-z0-9_] \w
[^0-9] \D
[^\f\r\t\n\v] \S
[^A-Za-z0-9_] \W
Top

Jonas Holbech, 2010, IT Universitetet i København, 23. februar 2010, Rued Langgaards Vej 7, 2300 København S, Danmark.