Tegnap valaki megkérdezte tőlem, hogy miként lehet RSS hírcsatornát megjeleníteni egy blogon, úgy, ahogyan nálam az SF blogja, illetve a SoDI egyéb blogjai doboz is működik.
Nos, mi sem egyszerűbb ennél. Használhatunk ehhez ingyenesen elérhető PHP-s RSS értelmezőket, (ilyen például a MagpieRSS, vagy a Last RSS), de dönthetünk úgy is, hogy saját magunk írjuk meg a megjelenítő kódot.
Szerencsére, ha ezt az utat választjuk, akkor sem kell bonyolult reguláris kifejezésekkel (egyfajta szövegszűrő maszkokkal, vagyis olyan kódokkal, melyekkel megadható, hogy egy bizonyos szövegből milyen speciális részeket akarunk kinyerni) szarakodnunk, ugyanis már a PHP 4 is tartalmazza azokat az XML értelmező függvényeket, amiket így szimplán meg kell hívnunk a munkához. Ezeket a beépített függvényeket onnan ismerjük meg, hogy mindegyik úgy kezdődik, hogy xml_ (ikszemelaláhúzás :))
És hogy miért pont XML elemző kell nekünk? Hát azért, mert az RSS fájlok egyben XML fájlok is (jéééééé :DDD)
- Egy XML fájl nagyon leegyszerüsítve olyan, mint egy HTML fájl, csak magunk határozhatjuk meg a cimkéket. Például:
<?xml version="1.0">
<MENHELY>
<KUTYA>
<SZIN="barna" />
<NEV>Morzsi</NEV>
</KUTYA>
<KUTYA>
<SZIN="fekete" />
<NEV>Buksi</NEV>
</KUTYA>
</MENHELY>
Így saját magunk is létrehozhatunk egyedi formátumokat. Az RSS is egy ilyen XML formátum, csak éppen annyival több egy házi készítésű formátumnál, hogy nemzetközileg elfogadott W3C szabvány.
Na akkor kezdjük is. Egyben megírjuk SF blogjának a hírdobozát. :)
Annyit érdemes tudni, hogy PHP-ban a változókat $ jellel kezdjük.
$xml_elemzo = xml_parser_create();
Akármilyen meglepő, a fenti sorban annyi történik, hogy létrehoztunk egy XML elemzőt, melynek fantáziadúsan az xml_elemzo nevet adtuk.
Ez egy beépített PHP objektum. Úgy kell elképzelni, mint egy intelligens „virtuális lényt”, aki nem tud semmit, csak XML-t olvasni, de azt nagyon. Be van „idomítva” néhány ehhez kapcsolódó képességre (ezeket a dolgokat hívjuk metódusnak), és nekünk csak az ezekhez tartozó „parancsszavakat” kell ismernünk ahhoz, hogy megmondhassuk, „pontosan hova üljön a kutya”. :)
Na de menjünk is tovább...
xml_set_element_handler($xml_elemzo, "elemEleje", "elemVege");
Most megmondtuk a PHP-nek, hogy milyen függvényekkel akarjuk kezelni az XML elemzőnk által feldolgozni kívánt XML fájl kezdő- illetve záró címkéit (tag-jeit). Azt mondtuk neki, hogy az adott XML tag-ek kezdő címkéit az elemEleje, a végüket pedig a elemVege nevű függvényünk kezelje.
- A saját magunk által megírandó PHP függvényekről most annyit érdemes tudnunk, hogy ezek (nagyon leegyszerűsítve) saját névvel azonosított kódrészek, melyek paraméterként fogadhatnak néhány, adatokat tartalmazó változót, ezen paraméterek segítségével csinálhatnak valamit, majd dolguk végeztével esetleg az eredményt is visszaadják.
Az imént említett függvényeket majd később megírjuk.
xml_set_character_data_handler($xml_elemzo, "karakterAdat");
Most megadtuk, hogy az XML elemzőnk a feldolgozni kívánt XML fájlunk szöveges adatainak elemzését melyik függvénnyel végezze el. A karakterAdat nevű függvényt választottuk ki ehhez. Ezt majd szintén később írjuk meg.
$fajl = fopen("http://reakcio.blogspot.com/feeds/posts/default?alt=rss","r")
...
Ehun ni most megnyitottuk SF blogjának az RSS feed-jét, olvasásra (a végén ezt jelöli az r, mint read). A megnyitott fájlt fajl néven nyitjuk meg. :)
...
or die("Hiba az RSS olvasása közben.");
Ha viszont valamiért nem tudtuk megnyitni, akkor sírjunk be. ;)
while ($adat = fread($fajl, 4096))
...
Amíg a fájl végére nem érünk (vagyis amíg kerül onnan adat az adat nevű változóba), olvassuk be a megnyitott RSS fájl újabb és újabb 4 Kb-os darabjait.
Az itteni ciklusmagon belül történik maga az XML elemzés.
xml_parse($xml_elemzo, $adat, feof($fajl))
...
Itt azt mondjuk, hogy elemezze ki a korábban létrehozott elemzőnk (xml_elemzo) segítségével az imént beolvasott adatot, ha pedig a fájl végére értünk (feof - eof - End Of File), akkor belőle a végéig még hátralévő darabot.
or die(sprintf("XML hiba: %s ebben a sorban: %d",
...
Ha viszont hibát találtunk, akkor haljunk meg :-)), vagyis írjuk ki, hogy milyen hiba ez, és hogy hányadik sorban akadtunk rá. Ebben segít nekünk a speciális kiírásokat végző sprintf nevű kiíró függvény. Ez első paraméterként egy idézőjelek között lévő szöveget vár, melyet tetszőleges helyen %s illetve %d szövegekkel tarkíthatunk. A %s szöveges információt, míg a %d tetszőleges számot jelöl. Ezeknek a jelöléseknek a helyére a sprintf függvénynek a legelső után következő paramétereit helyettesíti be a PHP.
- Például a $mondat = sprintf ("A nevem %s.","Sodika");
PHP utasítás végrehajtása után a mondat változóba ez a szöveg kerül:
A nevem Sodika.
Nézzük csak, mi kerül az RSS-olvasó programunkban a % jelek helyére...
...
xml_error_string(xml_get_error_code($xml_elemzo)),
Lekérjük a keletkezett hiba kódját (error code), majd pedig az annak megfelelő szöveges hibaüzenetet (error string). Mivel ez a sprintf függvény második (az első utániak között a legelső) paramétere, így a visszakapott hibaüzenet az eredeti szövegben az elsőként szereplő %s jelölés helyére kerül.
xml_get_current_line_number($xml_elemzo)));
Mivel a mostani kódrész végrehajtására akkor kerül sor, ha hibát találtunk, ez a hiba nyilván azon a helyen van, ahol aktuálisan állunk. Ezt a helyet lekérjük egy számként. Mivel az sprintf függvény utolsó paramétere, így az eredmény az utolsó helyettesítő jelölés, vagyis a %d helyére kerül be.
fclose($fajl);
Miután végeztünk az RSS csatorna feldolgozásával, zárjuk be a fájlt.
xml_parser_free($xml_elemzo);
Legvégül megszüntetjük az eddig használt XML elemzőnket, ugyanis a továbbiakban már nincs szükségünk rá.
Ez mind nagyon szép, mind nagyon jó, mindennel nagyon meg vagyunk elégedve, akárcsak Ferencjóska, úgyhogy már csak azokat a függvényeket kell megírnunk, amiket az imént hozzárendeltünk az XML elemzőnkhöz. Ugye három ilyen függvény van elemEleje, elemVege, és karakterAdat névvel.
Haladjunk sorban.
function elemEleje($elemzo, $nev, $attributumok) {
...
}
Az elemEleje függvény három paramétert vár. (Ezeket az xml_set_element_handler kezelő küldi neki, mivel ott határoztuk meg ezt a függvényt).
A három paraméter:
-
Hivatkozás egy XML elemzőre (itt elemzo néven)
-
Az aktuális XML elem (tag) nevét (itt nev néven)
-
És egy asszociatív tömböt, ahol az attribútumok vannak. (Itt attributumok néven. Ebben a kulcsok az egyes XML attribútumok nevei lesznek, míg az értékek az attribútomok értékeivel egyeznek meg.)
-
A tömb olyan adatszerkezet, melyben adott típusú adatok sorozatát tárolhatjuk. Ezekre az adatokra a tömbön belüli indexükkel (sorszámukkal) hivatkozhatunk. Az asszociatív tömb pedig olyan tömb, ahol az egyes adatokat azonosító egyedi indexek nem számok, hanem szövegek.
Vegyük például az alábbi XML tag-et:
<kutya szin="piros" />
Az asszociatív tömb kulcsmezője itt a szin lesz, az aktuális elem értéke pedig az, hogy "piros".
Ezt az attributumok paramétert különben élesben nem fogjuk használni, ugyanis kizárólag a xml_set_element_handler megkötései miatt voltunk kénytelenek berakni a függvényünk paraméterlistájába.
Menjünk tovább... :)
global $elemenBelul, $tag, $cim, $leiras, $hivatkozas;
Meghatározunk néhány globális változót. Ezek attól globálisak, hogy a PHP fájlunk minden részében tetszés szerint használhatjuk őket; függvényeken belül és kívül egyaránt.
Hogy pontosan megérthessük, melyik változóra miért van szükség, elemezzük egy kicsit, hogyan néz ki az RSS kódben egy átlagos elem (hír) leírása.
<item>
<title>A hír címe</title>
<link>A hír URL-je (Internetes webcíme)</link>
<punDate>A hír URL készítésének időpontja</PubDate>
<description>A leírása (szövege)</description>
</item>
Egy konkrét Index hírnél például ez így néz ki:
<item>
<title><![CDATA[Lőfegyvereket találtak Tolnában]]></title>
<link>http://index.hu/politika/bulvar/bulvarhirek/343294</link>
<pubDate>Tue, 19 Feb 2008 21:54:00 +0100</pubDate>
<description><![CDATA[Autóban és lakásban is találtak lőszert és fegyvert a rendőrök.]]></description>
</item>
Itt a <![CDATA[szöveg]]> jelölés egy szöveges tartalmat azonosít. Enélkül nem lesz szabványos (másnéven valid) a kód, vagyis a legtöbb RSS olvasó nem tudná helyesen értelmezni.
Nos, visszatérve a globális változóinkra...
-
az elemenBelul logikai (true/false, igaz/hamis) változó azt jelzi, hogy egy item elemen belül járunk-e. A logikai változót úgy tudjuk elképzelni, mint egy érmét, amit leteszünk az asztalra. Lehet felül a fej (igaz érték), vagy az írás (hamis érték).
-
a tag elem azt jelzi, hogy pontosan melyik XML (RSS) tag-nél járunk
-
A cím az adott elem címét jelöli (TITLE tag)
-
A leiras az adott elem tartalmát jelöli (DESCRIPTION tag)
-
A hivatkozas pedig az elem Internetcímét jelöli (LINK tag)
Na folytassuk az elemEleje függvényünk megírását...
if ($elemenBelul) {
$tag = $nev;
} elseif ($nev == "ITEM") {
$elemenBelul = true;
}
Ha egy elemen belül (vagyis ITEM tag-en belül) járunk, akkor a tag változó legyen egyenlő az aktuális XML tag nevével (vagyis azzal, ahol éppen állunk, más szóval, amit éppen olvasunk). Ha viszont nem ilyen ITEM tag-en belül járunk, de az aktuális nyitó tag éppen egy ITEM, akkor az elemenBelul globális változónk igazra állításával jegyezzük fel ezt a tényt.
Egyszerűbben: ha feldolgozás közben éppen most lépünk az RSS feed egy hírének leírásába (ITEM tag-jába), akkor ezt jelezzük az elemenBelul változó igazra állításával, egyébként pedig, ha már egy ilyen híren belül járunk, akkor a tag nevű változó jelölje azt, hogy a hír leírásának melyik részét olvassuk éppen (a linkjét, az időpontját, vagy esetleg valami mást?).
Na, ezzel az elemEleje függvényt ki is veséztük.
Következzen a elemVege függvény, ami a záró XML elemek (tag-ek) feldolgozását végzi.
function elemVege($elemzo, $nev) {
...
}
Mint láthatjuk, ez a függvény két paramétert vár. Egyrészt kéri az XML elemzőnket, másrészt pedig annak a XML elemnek (tag-nek) a nevét, melyből éppen kilépünk.
if ($nev == "ITEM") {
...
}
Az előző sor jelentése: Ha éppen egy ITEM elemből, vagy egy önálló hír leírásából léptünk ki... ...
...
printf("<b><a href='%s' target='_blank'>%s</a></b>",
trim($hivatkozas),htmlspecialchars(trim($cim)));
A printf függvény ugyaúgy működik, mint a korábban már ismertetett sprintf függvény, annyi különbséggel, hogy míg az előbbi a képernyőre, addig az utóbbi egy szöveges változóba tölti az eredményét. Vagyis, mint ahogy már láttuk a sprintf függvény ismertetésénél, az idézőjelek között lévő két %s karakter behelyettesítésre kerül az első paraméter után következő két paraméterrel (itt: szöveges információval).
Az első %s helyére az éppen olvasott (feldolgozott) hír URL-je (linkje, hivatkozása) kerül, míg a második %s helyére annak címe.
A trim PHP függvényről azt érdemes tudni, hogy kiszedi a szöveg elejéről és végéről a felesleges szóközöket. Mint láthatjuk, a href HTML tag-ba beírandó hivatkozással (linkkel), és az aktuálisan feldolgozandó cikk (éppen olvasott RSS hír) címével is elvégezzük ezt a műveletet.
A htmlspecialchars függvény pedig egyes speciális karaktereket alakít át olyan módon, hogy ne zavarják meg a HTML kódot. Így a & jelet arra alakítja át, hogy, &, a " jelet pedig arra, hogy ", satöbbi. Ezek csak a lap forráskódjában (HTML kódjában) változnak meg, a felhasználó számára már eredeti alakjukban látszanak.
Ha ez a két karakterkezelő függvény egymásba van ágyazva (htmlspecialchars(trim(szöveg))), akkor egyszerre mindkét művelet, vagyis az üres helyek, és a speciális HTML karakterek kiszűrése is megtörténik.
...
$cim = "";
$leiras = "";
$hivatkozas = "";
$elemenBelul = false;
}
Mivel az elemVege függvényünk, amiben vagyunk (pontosabban az ahhoz tartozó, if-fel kezdődő, feltételfüggő kódrészünk) azt az esetet kezeli, ha éppen kiléptünk egy ITEM tag (RSS hír) vizsgálatából, annak feldolgozása (vagyis kiírása) után érdemes lenullázni a rá vonatkozó adatokat, és így azt is jelezni, hogy már nem elemen belül (adott hír RSS-leírásán belül) járunk.
Egyszerűbben: Ha már elolvastunk egy hírt, és ki is írtuk, akkor tök mindegy, milyen adatok vonatkoztak rá, ezeket nyugodtan törölhetjük.
No már csak a karakterAdat függvényünk van hátra.
function karakterAdat($elemzo, $adat) {
global $elemenBelul, $tag, $cim, $leiras, $hivatkozas;
...
}
Ez a függvény, mint már írtam, az XML feldolgozás közben a karakteres adatok kezelését végzi. Ehhez előszöris meg kell kapnia paraméterként az XML elemzőnket, valamint azt a szöveges adatot, amit az elemzőnk éppen olvas.
Egyben jeleznünk kell, hogy használni kívánjuk azokat a globális változókat, melyekben az XML (RSS) feldogozása közben a köztes adatokat jegyeztük fel (global ... rész).
Most pedig nézzük a függvény belső részét...
if ($elemenBelul) {
switch ($tag) {
case "TITLE":
$cim .= $adat;
break;
case "DESCRIPTION":
$leiras .= $adat;
break;
case "LINK":
$hivatkozas .= $adat;
break;
}
}
Mint látjuk, az egész kódrész csak akkor hajtódik végre, ha éppen egy elemen belül (egy adott RSS hírleíráson belül) vagyunk. ( if (feltétel) { utasítások} )
A switch ($tag) résszel fejezzük ki azt, hogy attól függően, éppen melyik XML tag-et olvassuk, más és más kódot szeretnénk végrehajtani (esetszétválasztás).
A .= operátor pedig egy szöveges változó végéhez fűz hozzá egy másik szöveget.
Például
$kutya .= "Buksi";
$kutya .= "ka";
echo $kutya;
Utasítássor azt írja ki, hogy Buksika.
De nézzük csak meg, konkrétan milyen eseteket vizsgálunk itt:
Azt, amit itt feljegyeztünk címként, leírásként, és hivatkozásként, értelemszerűen majd az elemVege függvényben fogjuk felhasználni, amint az RSS feldolgozás közben egy adott ITEM tag végére, vagyis adott RSS hírleírás végére értünk.
Ezzel készen is volnánk.
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>PHP alapú RSS értelmező</title>
</head>
<body>
<?php
$elemenBelul = false;
$tag = "";
$cim = "";
$leiras = "";
$hivatkozas = "";
function elemEleje($elemzo, $nev, $attributumok) {
global $elemenBelul, $tag, $cim, $leiras, $hivatkozas;
if ($elemenBelul) {
$tag = $nev;
} elseif ($nev == "ITEM") {
$elemenBelul = true;
}
}
function elemVege($elemzo, $nev) {
global $elemenBelul, $tag, $cim, $leiras, $hivatkozas;
if ($nev == "ITEM") {
printf("<b><a href='%s' target='_blank'>%s</a></b>",
trim($hivatkozas),htmlspecialchars(trim($cim)));
$cim = "";
$leiras = "";
$hivatkozas = "";
$elemenBelul = false;
}
}
function karakterAdat($elemzo, $adat) {
global $elemenBelul, $tag, $cim, $leiras, $hivatkozas;
if ($elemenBelul) {
switch ($tag) {
case "TITLE":
$cim .= $adat;
break;
case "DESCRIPTION":
$leiras .= $adat;
break;
case "LINK":
$hivatkozas .= $adat;
break;
}
}
}
$xml_elemzo = xml_parser_create();
xml_set_element_handler($xml_elemzo, "elemEleje", "elemVege");
xml_set_character_data_handler($xml_elemzo, "karakterAdat");
$fajl = fopen("http://reakcio.blogspot.com/feeds/posts/default?alt=rss","r")
or die("Hiba az RSS olvasása közben.");
while ($adat = fread($fajl, 4096))
xml_parse($xml_elemzo, $adat, feof($fajl))
or die(sprintf("XML hiba: %s ebben a sorban: %d",
xml_error_string(xml_get_error_code($xml_elemzo)),
xml_get_current_line_number($xml_elemzo)));
fclose($fajl);
xml_parser_free($xml_elemzo);
?>
</body>
</html>
A fenti kódot bárhol tudjuk használni, ahol van PHP 4-5 támogatás, és engedélyezve van a külső fájlok megnyitása (több helyen, például az extra.hu oldalain ez sajnos le van tiltva).
Ha pedig arra vagyunk kíváncsiak, hogy ezt a végeredményt hogyan tudjuk olyan módon berakni a blogunkba, mint ahogy én is megjelenítem az egyes RSS dobozokat ezen a blogon, akkor kattintsunk ide, mert nem is olyan régen ehhez is készítettem egy hasonlóan részletes tutorialt.
By SoDI
Kapcsolódó anyagok: