Kirjoittaja: Antti Laaksonen (2005).
Kokeneiden PHP-ohjelmoijien skripteissä esiintyy usein merkillisen näköisiä koodirivejä, jotka saavat pituuteensa nähden paljon aikaan. Yksi ainoa huolellisesti muodostettu koodirivi saattaa esimerkiksi muuttaa kaikki tekstissä olevat nettiosoitteet toimiviksi HTML-linkeiksi. Vastaava onnistuisi mainiosti myös perinteisillä merkkijonofunktioilla (substr
, strpos
), mutta kirjoitettavaa olisi silloin monin verroin enemmän. Siksi säännöllisiä lausekkeita (regular expressions, regexp), jotka etsivät tai korvaavat merkkijonossa tiettyä kaavaa noudattavat tekstinpätkät, kannattaa käyttää aina sopivan tilaisuuden tullen. PHP tukee kahdenlaisia säännöllisiä lausekkeita, joista tähän oppaaseen on valittu monipuolisemmat Perl-kielestä lainatut lausekkeet.
Säännöllisiin lausekkeisiin liittyvät funktiot ovat:
Funktio | Käyttötarkoitus |
---|---|
preg_grep | erottaa taulukosta säännönmukaiset merkkijonot |
preg_match | etsii merkkijonosta ensimmäisen sääntöön sopivan tekstinpätkän |
preg_match_all | etsii merkkijonosta kaikki sääntöön sopivat tekstinpätkät |
preg_quote | lisää säännöllisten lausekkeiden erikoismerkkien eteen kenoviivan |
preg_replace | korvaa merkkijonon sääntöön sopivat tekstinpätkät toisella |
preg_replace_callback | välittää tekstinpätkät erilliselle korvauksen muodostavalle funktiolle |
preg_split | muodostaa merkkijonosta taulukon säännölliset lausekkeet erottimina |
Seuraavassa esimerkissä preg_replace
-funktiota on käytetty aivan str_replace
-funktion tapaan. Ainoana erona on korvattavan merkkijonon alussa ja lopussa olevat /
-merkit, jotka pitää kirjoittaa säännöllisen lausekkeen ympärille.
<?php echo preg_replace("/2004/", "2005", "Nyt on vuosi 2004"); // Nyt on vuosi 2005 ?>
Useimmat merkit kirjoitetaan sellaisenaan säännölliseen lausekkeeseen. Kuitenkin muutamien merkkien eteen pitää panna kenoviiva (\
), koska niillä on muuten erikoistehtävä. Erikoismerkit ovat: /\.^$[]|()+*?!{}=:<>
. Jos säännöllinen lauseke on PHP-skriptissä lainausmerkkien ympärillä, kenoviivoja pitää tässä tapauksessa kirjoittaa kaksi.
<?php echo preg_replace("/\\?/", "!", "Toimii?"); // Toimii! ?>
Tavallisesti suurten ja pienten kirjainten välille tehdään ero, mutta säännöllisen lausekkeen perään laitettava i
-kirjain poistaa tämän erottelun.
<?php if (preg_match("/mysql/i", "MySQL")) echo "Sana löytyi."; // Sana löytyi. ?>
Eräänlaisena yleismerkkinä toimii piste, joka vastaa mitä tahansa merkkiä rivinvaihtoa lukuun ottamatta. Lisäksi \w
tarkoittaa aakkosmerkkiä ja \d
numeromerkkiä. Tässä esimerkissä nimen täytyy olla viisikirjaiminen, alkaa A:lla ja päättyä i:hin.
<?php if (preg_match("/A...i/", "Antti")) echo "Nimi kelpaa."; // Nimi kelpaa. ?>
Edellisen esimerkin tarkistus ei ole aivan aukoton. Se kyllä kelpuuttaa kaikki oikeanmuotoiset nimet, mutta myös koko joukon vääriä. Esimerkiksi nimet Juho-Anssi ja Amalia menevät täydestä. Syynä on se, että etsittävä tekstinpätkä saa olla missä tahansa kohden merkkijonoa. Korjaus on käyttää merkkijonon aloitus- (^
) ja lopetusmerkintöjä ($
) tähän tapaan:
<?php if (preg_match("/^A...i$/", "Antti")) echo "Nimi kelpaa."; // Nimi kelpaa. ?>
Jos säännöllisen lausekkeen jälkeen on m
-kirjain, myös rivin alku ja loppu kelpaavat. Nyt riittää, että etsittävä nimi on omalla rivillään.
<?php if (preg_match("/^A...i$/m", "Pekka\nAntti\nJohanna")) echo "Nimi kelpaa."; // Nimi kelpaa. ?>
Merkintä \b
tarkoittaa, että sen paikalla ei saa olla aakkosmerkkiä. Tämän merkinnän avulla pystyy tunnistamaan kokonaisia sanoja. Seuraavassa esimerkissä ainoastaan puu-sana lähtee pois, vaikka kirjaimet esiintyvät myös muissa sanoissa.
<?php echo preg_replace("/\\bpuu\\b/", "", "puute puu kaipuu"); // puute kaipuu ?>
Hakasulkujen sisään kirjoitetuista merkeistä kelpaa mikä tahansa. Seuraavassa esimerkissä kaikki vokaalit muuttuvat kysymysmerkeiksi.
<?php echo preg_replace("/[AEIOUYÅÄÖaeiouyåäö]/", "?", "Auto kääntyi pihaan."); // ??t? k??nt?? p?h??n. ?>
Peräkkäistä kirjain- tai numerosarjaa ei tarvitse kirjoittaa kokonaisuudessaan, vaan ensimmäinen ja viimeinen merkki ja väliviiva riittävät. Esimerkiksi merkintä [a-zA-Z0-9]
tarkoittaa isoja ja pieniä kirjaimia a – z ja numeroita. Seuraava tarkistus hyväksyy kaikki shakkipelin ruudut (ensin kirjain a – h, sitten numero 1 – 8).
<?php if (preg_match("/^[a-h][1-8]$/", "b5")) echo "Ruutu kelpaa."; // Ruutu kelpaa. ?>
Jos heti aloittavan hakasulun jälkeen tulee hattumerkki (^
), kaikki muut merkit kelpaavat paitsi mainitut. Tässä esimerkissä kyy ja pyy joutuvat pois.
<?php echo preg_replace("/[^s]yy/", "", "kyy pyy syy"); // syy ?>
Pystyviivoilla erotetuista tekstinpätkistä kelpaa mikä tahansa.
<?php echo preg_replace("/hintava|kallis|tyyris/", "halpa", "Auto on kallis."); // Auto on halpa. ?>
Jos vaihtoehdot meinaavat sekoittua muuhun tekstiin, ne pitää ympäröidä suluilla. Tämä tarkistus edellyttää, että tiedoston nimi päättyy pisteeseen ja kuvamuodon tunnukseen.
<?php if (preg_match("/\\.(png|jpg|gif)$/", "oma.jpg")) echo "Kuva kelpaa."; // Kuva kelpaa. ?>
Yksittäisen merkin, merkkiryhmän tai vaihtoehdon perässä seuraavat merkinnät tarkoittavat toistoa:
Merkintä | Toisto |
---|---|
* | vähintään nolla kertaa |
+ | vähintään yhden kerran |
? | ei kertaakaan tai kerran |
{m} | tasan m kertaa |
{v,} | vähintään v kertaa |
{v,k} | vähintään v kertaa, enintään k kertaa |
Tässä esimerkissä kaikki numerosarjat muuttuvat risuaidoiksi.
<?php // vaatimus: erillinen sana, jossa on yksi tai useampi numeromerkki echo preg_replace("/\\b[0-9]+\\b/", "#", "talo 77, kerros 3, huone 218"); // talo #, kerros #, huone # ?>
Seuraava tarkistus hyväksyy päivämäärää muistuttavat merkkijonot, joissa on pisteillä erotettuina kaksi yhden tai kahden numeron osuutta ja viimeisenä neljän numeron osuus. Tarkistusta voisi parantaa rajaamalla tarkemmin päivän ja kuukauden ensimmäisen numeron, mutta täysin luotettavaa tarkistusta säännöllisillä lausekkeilla ei pysty tekemään.
<?php if (preg_match("/^[0-9]{1,2}\\.[0-9]{1,2}\\.[0-9]{4}$/", "24.2.2005")) { echo "Päivämäärä kelpaa."; } // Päivämäärä kelpaa. ?>
Jos toistomerkinnät on kirjoitettu sellaisenaan, ne yrittävät löytää mahdollisimman pitkän sopivan tekstinpätkän. Jos taas merkintöjen perään on laitettu kysymysmerkki, ne tyytyvät mahdollisimman vähään. Seuraavassa esimerkissä tarkoituksena on poistaa suluissa olevat tekstit. Ilman kysymysmerkkiä poistettava osuus alkaa ensimmäisestä sulusta ja päättyy viimeiseen, minkä takia tekstiä katoaa liikaa.
<?php echo preg_replace("/\\(.*\\)/", "", "2 (kaksi) 3 (kolme)"); // 2 echo preg_replace("/\\(.*?\\)/", "", "2 (kaksi) 3 (kolme)"); // 2 3 ?>
Tekstin korvauksessa olisi monesti kätevää hyödyntää säännölliseen lausekkeeseen sopinutta tekstinpätkää myös korvausvaiheessa. Tämä onnistuu kirjoittamalla \0
-merkintä korvauksen ilmoittavaan merkkijonoon. Seuraavassa esimerkissä kaikki numerosarjat muuttuvat lihavoiduiksi.
<?php echo preg_replace("/\\b[0-9]+\\b/", "<b>\\0</b>", "talo 77, kerros 3, huone 218"); // talo <b>77</b>, kerros <b>3</b>, huone <b>218</b> ?>
Myös säännöllisen lausekkeen osiin pääsee käsiksi kirjoittamalla ne sulkujen sisään. Koko lauseketta kuvaavan \0
:n lisäksi käytössä on silloin merkinnät \1
, \2
, \3
jne., joissa on järjestyksessä suluissa olleet osuudet. Tässä esimerkissä kymmentä kirjainta pidemmät sanat lyhennetään, ja niiden perään tulee kolme pistettä.
<?php // vaatimus: kymmenen kirjainta, joiden jälkeen yksi tai useampi kirjain // \1 viittaa suluissa olevaan osuuteen eli kymmeneen kirjaimeen echo preg_replace("/(\w{10})\w+/", "\\1...", "kynä matkapuhelin tietosanakirja"); // kynä matkapuhel... tietosanak... ?>
Aikaisemmin löytyneitä lausekkeen osia on mahdollista hyödyntää myös varsinaisessa säännöllisessä lausekkeessa. Seuraava tarkistus kelpuuttaa kaikki numerosarjat, joiden jokainen numero on sama.
<?php // vaatimus: ensimmäinen numero väliltä 0 - 9, loput samaa numeroa if (preg_match("/^([0-9])\\1*$/", "333333")) echo "Numerosarja kelpaa."; // Numerosarja kelpaa. ?>
Funktiolla preg_replace_callback
saa syötettyä korvattavan osan merkkijonosta toiselle funktiolle samanlaisena taulukkona, kuin preg_match
antaa tuloksen. Tämä mahdollistaa monenlaiset tyylikkäät korvaukset. Seuraavassa tekstissä esiintyvät murtoluvut muutetaan desimaaliluvuiksi.
<?php function muuta_murtoluku($m) { return round($m[1] / $m[2], 3); } // vaatimus: kaksi numerosarjaa, joiden välissä on jakomerkki echo preg_replace_callback("/([0-9]+)\/([1-9][0-9]*)/", "muuta_murtoluku", "Tulokset: 6/17 ja 10/21."); // Tulokset: 0.353 ja 0.476. ?>
Tämä esimerkki sekoittaa sanojen keskellä olevat kirjaimet.
<?php function sekoita_sana($m) { return $m[1] . str_shuffle($m[2]) . $m[3]; } // vaatimus: kaksi kirjainta, joiden välissä voi olla lisää kirjaimia $teksti = "Saatko selvää tällaisesta tekstistä?"; echo preg_replace_callback("/(\w)(\w*)(\w)/", "sekoita_sana", $teksti); // Saktao slevää ttelaiäslsa titekstsä? ?>
Tässä on lopuksi muutamia hyviä säännöllisiin lausekkeisiin liittyviä Internet-sivustoja (englanniksi):
Voit lähettää oppaaseen liittyvää palautetta ja kysymyksiä sähköpostilla tai sivun alalaidassa olevan lomakkeen kautta.
PS. Suurin osa oppaan tiedoista pätee myös muissa säännöllisiä lausekkeita ymmärtävissä ohjelmointikielissä!
Kiva. :)
Kiitoksia. Olen kaivannutkin tällaista.
Loistava opas jälleen kerran!
Joskus etsin ymmärrettävää regexp-opasta niin pitkään että tyydyin tekemään purkkakoodia sen sijasta että oisin tuhlannut tunneittain aikaa ettimiseen. Olen aina halunnut tietää miten regexpit toimii. Uskomattoman hyödykäs ja helppotajuinen opas =)
Onpas hyvä ;) tälläistä olen kaivannut. Nyt ei enää ole temppukaan tehdä hyvää hakuskriptiä :D
Ihanaa, tälläista toisiaan on kaivattu.
Vinkki: Jos preg_matchin ja preg_replacen parametrimerkkijonot pistää yksittäisten heittomerkkien sisään niin kuin minkä tahansa merkkijonon voi, kenoviivoja ei tarvitse laittaa aina kahta, koska yksittäisheittomerkkimerkkijonoja PHP ei parseroi. Tällöin myös nopeus kasvaa.
Katos, juuri tätä tarvitsinkin :)
Yhdyn mielelläni edelliseen..............
....kirjoitukseen. Kiitos tömä tuli tarpeeseen, kiitos myös Tomppa32:lle ja Tietty tämän laatijalle Antille!!
Miten saisi tehtyä sellaisen että se tulostaa tiedoston päätten?
Esim.
Tiedosto ______Tulostus
etusivu.php ___.php
testi.html ____.html
kuva.png _____ .png
Kiitos jo etukäteen!
Aivan mahtava opas. Hyvin neuvottiin miten saa tunnistettua kokonaisia sanoja. Haluaisin nyt kuitenkin leikata tekstistä esim. viisi ensimmäistä sanaa erilleen samalla tavoin kuin Ohjelmointiputkan uusimman keskusteluviestin näyttävässä laatikossa. Miten toimitaan?
Koetin tällaista viritelmää, muttei se näytä toimivan:
$teksti = "Tässä tekstissä on yli viisi sanaa, joten viidennen sanan jälkeen tulee pisteitä ja lue lisää -linkki."; $teksti = preg_replace("/(\\b[a-zA-Z0-9åäöÅÄÖ]+\\b)\\1{5}/", "\\0... <a href='#' title='Lue lisää'>Lue lisää</a>", $teksti); // Pitäisi näyttää tältä: // Tässä tekstissä on yli viisi... Lue lisää
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.