Kirjautuminen

Haku

Tehtävät

Oppaat: PHP-ohjelmointi: Osa 16 - Säännölliset lausekkeet

  1. Osa 1 - Johdanto
  2. Osa 2 - Muuttujat
  3. Osa 3 - if-rakenne
  4. Osa 4 - for-silmukka
  5. Osa 5 - Taulukot
  6. Osa 6 - Lomakkeet
  7. Osa 7 - Nettisivusto
  8. Osa 8 - Lisää silmukoista
  9. Osa 9 - Tiedostot
  10. Osa 10 - Omat funktiot
  11. Osa 11 - Istunnot
  12. Osa 12 - Tietokannat
  13. Osa 13 - Tietoturva
  14. Osa 14 - Olio-ohjelmointi
  15. Osa 15 - Kuvien luonti
  16. Osa 16 - Säännölliset lausekkeet
  17. Osa 17 - Merkistöt
  18. Osa 18 - PHP:n ongelmat

Kirjoittaja: Antti Laaksonen (2011).

Säännöllinen lauseke on kuvaus merkkijonon rakenteesta. Esimerkiksi säännöllinen lauseke a(b|c)d* vaatii, että merkkijono alkaa merkillä a, sitten tulee merkki b tai c ja lopuksi on mikä tahansa määrä merkkiä d. Esimerkiksi merkkijonot abdd ja acddddd vastaavat tätä lauseketta.

Ohjelmoinnissa säännöllisistä lausekkeista on hyötyä esimerkiksi silloin, kun tehtävänä on tarkistaa, onko annettu merkkijono oikeanmuotoinen. Usein yhdellä säännöllisellä lausekkeella voi toteuttaa tarkistuksen, johon muuten vaadittaisiin monimutkainen koodinpätkä.

Tässä oppaassa tutustumme säännöllisiin lausekkeisiin PHP:ssä. Oppaan tietoja voi soveltaa myös muissa ohjelmointikielissä, koska säännöllisten lausekkeiden perusasiat ovat samanlaisia eri kielissä.

Esimerkki: Sanan tunnistus

Oletetaan, että tehtävänä on tunnistaa sanat, jotka muodostuvat kirjaimista a–z ja joiden pituus on 4–10 kirjainta. Tunnistuksen voi toteuttaa seuraavasti tähän mennessä opituin keinoin:

<?php
$sana = "selleri";
$kirjaimet = "abcdefghijklmnopqrstuvwxyz";
$kelvollinen = true;
for ($i = 0; $i < strlen($sana); $i++) {
    if (substr_count($kirjaimet, $sana[$i]) == 0) {
        $kelvollinen = false;
        break;
    }
}
if (strlen($sana) < 4 || strlen($sana) > 10) {
    $kelvollinen = false;
}
if ($kelvollinen) {
    echo "Sana on kelvollinen.";
} else {
    echo "Sana ei ole kelvollinen.";
}
?>

Säännöllisen lausekkeen avulla koodista tulee näin lyhyt:

<?php
$sana = "selleri";
if (preg_match("/^[a-z]{4,10}$/", $sana)) {
    echo "Sana on kelvollinen.";
} else {
    echo "Sana ei ole kelvollinen.";
}
?>

Seuraavaksi katsomme tarkemmin, miten säännölliset lausekkeet muodostuvat.

Lausekkeen rakenne

Säännöllisen lausekkeen perusosat ovat vaihtoehto, ryhmittely ja toisto.

Vaihtoehto ilmaistaan |-merkin avulla. Esimerkiksi lauseke apina|banaani|cembalo vastaa merkkijonoja apina, banaani ja cembalo.

Ryhmittely merkitään sulkujen avulla. Esimerkiksi lauseke (tammi|helmi|maalis)kuu vastaa merkkijonoja tammikuu, helmikuu ja maaliskuu.

Toistoon ovat käytettävissä seuraavat merkinnät:

merkintäselitys
*toisto miten monta kertaa tahansa, myös 0 kertaa
+toisto miten monta kertaa tahansa, ainakin kerran
?toisto 0 tai 1 kertaa
{n}toisto tarkalleen n kertaa
{n,}toisto ainakin n kertaa
{n,m}toisto ainakin n, korkeintaan m kertaa

Esimerkiksi lauseke (ab)+ vastaa merkkijonoja ab, abab, ababab jne.

Merkkiryhmässä hakasulkujen sisällä on joukko merkkejä. Esimerkiksi lauseke [a-zA-Z] vastaa englannin kielen aakkosia. Jos ensimmäinen merkki on ^, merkitys muuttuu käänteiseksi: lauseke [^a-zA-Z] hyväksyy kaikki merkit paitsi englannin kielen aakkoset. Lisäksi merkki . tarkoittaa mitä tahansa merkkiä.

Huomaa, että kaikki esitellyt merkinnät voisi korvata käyttämällä vain merkkejä | ja * sekä sulkuja. Esimerkiksi lauseketta (ab)+ vastaa lauseke ab(ab)* ja lauseketta [a-f] vastaa lauseke a|b|c|d|e|f.

PHP:n funktiot

PHP:n tärkeimmät säännöllisiin lausekkeisiin liittyvät funktiot ovat:

PHP:ssä säännöllinen lauseke kirjoitetaan merkkien / sisään. Lisäksi mahdollisten säännöllisten lausekkeiden erikoismerkkien eteen täytyy laittaa merkki \. Esimerkiksi lauseke "/\([a-z]*\)/" vastaa merkkijonoja, joissa sulkujen sisällä on mikä tahansa määrä merkkejä a–z.

Tutustumme seuraavaksi PHP:n funktioiden käyttöön esimerkkien kautta.

preg_match

Seuraava koodi tarkistaa, onko merkkijono viikonpäivän lyhenne:

<?php
$paiva = "to";
if (preg_match("/^(ma|ti|ke|to|pe|la|su)$/", $paiva)) {
    echo "Viikonpäivä on oikeanmuotoinen.";
} else {
    echo "Viikonpäivä ei ole oikeanmuotoinen.";
}
?>

Merkki ^ tarkoittaa merkkijonon alkua ja merkki $ tarkoittaa merkkijonon loppua. Ilman näitä merkkejä säännöllinen lauseke tulkitsisi viikonpäiväksi esimerkiksi merkkijonon "aatos", koska sen osana on merkkijono "to".

Seuraava koodi tarkistaa, onko kellonaika oikeanmuotoinen:

<?php
$kello = "17:05:22";
$lauseke = "/^([01][0-9]|2[0-3]):[0-5][0-9]:[0-5][0-9]$/";
if (preg_match($lauseke, $kello)) {
    echo "Kellonaika on oikeanmuotoinen.";
} else {
    echo "Kellonaika ei ole oikeanmuotoinen.";
}
?>

Säännöllinen lauseke muodostuu kolmesta osasta, joiden välissä on kaksoispiste. Lausekkeen ensimmäinen osa vastaa kaksinumeroista lukua, joka alkaa numerolla 0 tai 1 ja päättyy numeroon 0–9 tai alkaa numerolla 2 ja päättyy numeroon 0–3. Lausekkeen kaksi muuta osaa vastaavat kaksinumeroista lukua, joka alkaa numerolla 0–5 ja päättyy numeroon 0–9.

preg_match_all

Seuraava koodi erottaa tekstissä olevat luvut taulukkoon:

<?php
$teksti = "Vuodessa on 12 kuukautta ja 52 viikkoa.";
preg_match_all("/[0-9]+/", $teksti, $luvut);
foreach ($luvut[0] as $luku) {
    echo $luku . "<br>";
}
?>

Koodin tulostus on seuraava:

12
52

Tässä funktio preg_match_all kerää taulukkoon $luvut kaikki merkkijonon $lause osat, jotka vastaavat säännöllistä lauseketta [0-9]+ eli ovat lukuja.

Seuraava koodi erottaa tekstissä olevat linkit taulukkoon:

<?php
$teksti = "Tässä <a href=\"lause.html\">lauseessa</a> on " .
          "<a href=\"viisi.html\">viisi</a> sanaa.";
$lauseke = "/<a href=\"(.*?)\">(.*?)<\/a>/";
preg_match_all($lauseke, $teksti, $linkit);
foreach ($linkit[1] as $linkki) {
    echo $linkki . "<br>";
}
?>

Koodin tulostus on seuraava:

lause.html
viisi.html

Lausekkeessa linkin osoitteen ja otsikon kohdalla on merkintä (.*?). Tässä sekä sulkujen että kysymysmerkin merkitys poikkeaa aiemmin esitetystä.

Sulkujen avulla linkin osoite ja otsikko tulevat omiin kohtiinsa taulukossa $linkit. Taulukon kohdassa 0 ovat lausekkeen kokonaiset esiintymät tekstissä, kohdassa 1 ovat ensimmäisen sulkuosuuden esiintymät ja kohdassa 2 ovat toisen sulkuosuuden esiintymät. Käytännössä taulukon sisältö on seuraava:

taulukon kohtasisältö
$linkit[0][0]<a href="lause.html">lauseessa</a>
$linkit[0][1]<a href="viisi.html">viisi</a>
$linkit[1][0]lause.html
$linkit[1][1]viisi.html
$linkit[2][0]lauseessa
$linkit[2][1]viisi

Kysymysmerkki määrittää, että lausekkeen osa tunnistaa mahdollisimman lyhyen pätkän tekstiä. Ilman kysymysmerkkiä tunnistettava osa olisi mahdollisimman pitkä, jolloin lauseke erottaisi tekstistä vain yhden "linkin":

lause.html">lauseessa</a> on <a href="viisi.html

preg_replace

Seuraava koodi poistaa tekstistä kaikki muut merkit paitsi numeromerkit:

<?php
$teksti = "Vuodessa on 12 kuukautta ja 52 viikkoa.";
$teksti = preg_replace("/[^0-9]/", "", $teksti);
echo $teksti;
?>

Koodin tulostus on seuraava:

1252

Seuraava koodi korvaa tekstissä olevat tagit [b]...[/b] tageilla <b>...</b>:

<?php
$teksti = "Tässä [b]lauseessa[/b] on [b]viisi[/b] sanaa.";
$teksti = preg_replace("/\[b\](.*?)\[\/b\]/", "<b>$1</b>", $teksti);
echo $teksti;
?>

Koodin tulostus on seuraava:

Tässä lauseessa on viisi sanaa.

Korvaavassa tekstissä $0 viittaa koko säännölliseen lausekkeeseen, $1 viittaa ensimmäiseen sulkuosuuteen, $2 viittaa toiseen sulkuosuuteen jne.

preg_replace_callback

Mutkikkaampia korvauksia voi tehdä niin, että uusi teksti tuotetaankin omalla funktiolla. Tämä mahdollistaa esimerkiksi seuraavan tyylikkään korvauksen:

<?php
function jakolasku($m) {
	// $m = array("6/17", "6", "17")
	return round($m[1] / $m[2], 3);
}
$teksti = "Tulokset ovat 6/17 ja 10/21.";
$teksti = preg_replace_callback("/([0-9]+)\/([0-9]+)/", "jakolasku", $teksti);
echo $teksti;
?>

Koodin tulostus on seuraava:

Tulokset ovat 0.353 ja 0.476.

Lausekkeen ideana on muuttaa tekstissä esiintyvät murtoluvut desimaaliluvuiksi. Funktio jakolasku saa parametrikseen taulukon, jossa on ensin koko osuma ja sitten erikseen sulkuihin osuneet kohdat eli ensin koko murtoluku ja sitten murtoluvun osoittaja ja nimittäjä. Näistä lasketaan osamäärä, ja funktio round pyöristää tuloksen kolmen desimaalin tarkkuudelle.

SL-haaste

Ohjelmointiputkan SL-haaste sisältää tehtäviä, jotka opettavat säännöllisten lausekkeiden suunnittelua. Jos onnistut ratkaisemaan SL-haasteen vaikeimmat tehtävät, hallitset säännölliset lausekkeet riittävän hyvin kaikkiin käytännön ohjelmointitilanteisiin.


Kommentit

jajarvin [13.09.2011 16:25:12]

#

Viikonpäivän tarkistavassa koodiesimerkissä on varmaan muuttujan $kello tilalla oltava muuttuja $paiva.

Antti Laaksonen [13.09.2011 22:48:11]

#

Kiitos huomiosta, korjasin virheen.

ApE!0 [21.09.2011 11:38:19]

#

"Lisäksi mahdollisten säännöllisten lausekkeiden erikoismerkkien eteen täytyy laittaa merkki \."

Vanhassa oppaassa oli joitakin yleisimpiä lueteltuina, mutta mielestäni myös tässä olisi hyvä mainita muutama

edit: niin tai siis kaikki . \ + * ? [ ^ ] $ ( ) { } = ! < > | : -

Kirjoita kommentti

Huomio! Kommentoi tässä ainoastaan tämän oppaan hyviä ja huonoja puolia. Älä kirjoita muita kysymyksiä tähän. Jos koodisi ei toimi tai tarvitset muuten vain apua ohjelmoinnissa, lähetä viesti keskusteluun.

Muista lukea kirjoitusohjeet.
Tietoa sivustosta