login  Naam:   Wachtwoord: 
Registreer je!
 Scripts:

Scripts > PHP > XML en PHP > RSS reader

RSS reader

Auteur: Thomas - 11 mei 2005 - 16:43 - Gekeurd door: Dennis - Hits: 29049 - Aantal punten: 4.58 (13 stemmen)



Zie comments in code en voorbeeld.

Code:
class.rss20.php
  1. <?php
  2. /*!
  3. @file class.rss20.php
  4. @short klasse voor het lezen (en cachen) van RSS-bestanden
  5. @date 2005-03-15 13:30
  6. @author Thomas van den Heuvel aka FangorN - the@stack.nl
  7. */
  8.  
  9. /*
  10. http://www.zend.com/zend/art/parsing.php
  11. http://www.sitepoint.com/article/php-xml-parsing-rss-1-0
  12. http://www.php.net/manual/en/function.xml-set-character-data-handler.php
  13.  
  14. PHP maakt gebruik van expat, een event-driven XML parser (in tegenstelling tot een tree-based parser)
  15. expat maakt gebruik van events (geregistreerde callback-functies worden aangeroepen wanneer er een
  16. event optreed, bijvoorbeeld als er een tag geopend op gesloten wordt)
  17. Het is de verantwoordelijkheid van de programmeur om tijdens het parsen een stack of lijst bij te houden
  18. waarin de structuur van het te parsen XML-bestand wordt vastgelegd.
  19. */
  20.  
  21. //! rss20 class
  22. /*!
  23. Met deze klasse kun je RSS-bestanden lezen (en cachen). De inhoud van het RSS-bestand wordt opgeslagen
  24. in een array (binnen het rss20-object) die eenvoudig gebruikt kan worden om de headlines van een RSS-
  25. bestand af te drukken.
  26. */
  27. class rss20 {
  28. var $file; //!< (string) XML-bestand dat geparsed dient te worden (URL)
  29. var $cached_file; //!< (string) naam van het XML-bestand dat gecached dient te worden / waaruit gelezen wordt
  30. var $from_file; //!< (bool) boolean die aangeeft of het XML-bestand werd gelezen van de site of uit cache
  31. var $site_retry; //!< (bool) boolean die aangeeft of de laatst gecachede versie van het XML-bestand leeg was
  32. var $rss; //!< (object) het XML-parser object
  33. var $stack = array(); //!< (array) stack van het XML-bestand (wordt gebruikt voor het parsen, houdt geopende tags bij)
  34. var $content = ""; //!< (string) buffer voor CDATA (character-data)
  35. var $channel_tags = array("title", "link", "description", "language", "copyright", "managingEditor", "webMaster", "pubDate", "lastBuildDate", "category", "generator", "docs"); //!< (array) lijst van channel-tags die opgeslagen dienen te worden
  36. var $item_tags = array("title", "link", "description", "author", "category", "pubDate", "content:encoded"); //!< (array) lijst van item-tags die opgeslagen dienen te worden
  37. var $output = array(); //!< (array) de output van het XML-bestand wordt hier in opgeslagen (om dit array draait deze klasse in feite)
  38. var $cur_chan = -1; //!< (int) huidige kanaal
  39. var $cur_item = -1; //!< (int) huidige item
  40.  
  41. //! constructor
  42. /*!
  43.   @post een parser voor het parsen van het XML-bestand is aangemaakt en geinitialiseerd
  44.   */
  45. function rss20() {
  46. $this->rss = xml_parser_create(); // creeer de parser
  47.  
  48. xml_set_object($this->rss, $this); // geeft aan dat de XML-parser in het rss20-object staat
  49.  
  50. xml_parser_set_option($this->rss, XML_OPTION_CASE_FOLDING, false); // uppercasing in XML? XML is case-sensitive!
  51. xml_parser_set_option($this->rss, XML_OPTION_SKIP_WHITE, true); // slaat "space characters" over in het XML-bestand
  52. xml_set_element_handler($this->rss, "startelement", "endelement"); // callback functie voor openings- and sluitings-tags
  53. xml_set_character_data_handler($this->rss, "characterdata"); // callback functie voor CDATA (character-data) binnenin tags
  54. }
  55.  
  56. //! initialisatie functie
  57. /*!
  58.   @param $file (string) het XML-bestand dat ingelezen dient te worden (URL)
  59.   @param $cache (string) wanneer je het XML-bestand wilt cachen, dient dit veld de naam van de lokale kopie te bevatten (default "")
  60.   @post het rss20-object is nu geinitialiseerd en gereed voor parse-data
  61.   */
  62. function newfile($file, $cache = "") {
  63. $this->cur_chan = -1;
  64. $this->cur_item = -1;
  65. $this->file = $file;
  66. $this->cached_file = $cache;
  67. $this->from_file = false;
  68. $this->site_retry = false;
  69. $this->output['channel'] = array();
  70. }
  71.  
  72. //! callback functie die wordt uitgevoerd wanneer er een tag wordt geopend
  73. /*!
  74.   @param $rss (object) verwijzing naar de XML-parser
  75.   @param $name (string) de naam van de tag die er voor zorgde dat deze methode werd aangeroepen
  76.   @param $attrs (array) associatief array met de attributen+waarden van de tag (als die er zijn, RSS heeft er iig geen)
  77.   */
  78. function startelement($rss, $name, $attrs) {
  79. // er is een nieuwe tag geopend - zet de naam van de openings-tag op de stack
  80. array_push($this->stack, $name);
  81.  
  82. switch($name) {
  83. case "channel":
  84. // maak een nieuw channel aan
  85. $this->cur_chan++; // verhoog de channel-teller
  86. $this->output['channel'][$this->cur_chan] = array(); // maak een array voor dit channel
  87. $this->output['channel'][$this->cur_chan]['item'] = array(); // maak een nieuw items-array voor binnen dit channel
  88. $this->cur_item = -1; // reset het aantal items voor dit kanaal
  89. break;
  90. case "item":
  91. $this->cur_item++; // verhoog de item-counter
  92. break;
  93. }
  94. }
  95.  
  96. //! callback functie die wordt uitgevoerd wanneer er een tag wordt afgesloten
  97. /*!
  98.   @param $rss (object) verwijzing naar de XML-parser
  99.   @param $name (string) de naam van de tag die er voor zorgde dat deze methode werd aangeroepen
  100.   */
  101. function endelement($rss, $name) {
  102.  
  103. /*
  104.   We zijn klaar met het lezen van (de inhoud van) een tag - $this->content bevat de inhoud van
  105.   de tag. Bepaal aan de hand van de laatst geopende tag op $this->stack wat de bedoeling is.
  106.   De tags "title", "link" en "description" kunnen zowel behoren tot een channel als een item.
  107.   Maak gebruik van de stack om te bepalen of we ons in een channel(tag) of item(tag) bevinden.
  108.   */
  109.  
  110. // Mysterieuze spaties en regelovergangen voor en na de content ?
  111. if(in_array("item", $this->stack) && in_array($name, $this->item_tags)) {
  112. // We zitten in een item-tag en we zijn geinteresseerd in deze specifieke tag
  113. $this->output['channel'][$this->cur_chan]['item'][$this->cur_item][$name] = $this->cdata(trim($this->content));
  114. } else {
  115. // We zitten in een channel-tag
  116. if(in_array($name, $this->channel_tags)) {
  117. // We zijn geinteresseerd in deze specifieke tag
  118. $this->output['channel'][$this->cur_chan][$name] = $this->cdata(trim($this->content));
  119. }
  120. }
  121.  
  122. // Reset de tag-inhoud
  123. $this->content = "";
  124. // De tag is afgesloten, dus deze kan verwijderd worden van de stack
  125. array_pop($this->stack);
  126. }
  127.  
  128. //! callback functie die wordt uitgevoerd wanneer er CDATA (character-data) wordt gelezen
  129. /*!
  130.   @param $rss (object) verwijzing naar de XML-parser
  131.   @param $data (string) data die tussen een openings- en sluitingstag staat
  132.   */
  133. function characterdata($rss, $data) {
  134. $this->content .= $data; // verzamel character-data in $this->content (niet alle tekst tussen de tags wordt in één keer gelezen)
  135. }
  136.  
  137. //! leest de feed afkomstig uit ofwel een lokale file of een externe URL, wat er voor zorgt dat de callback-functies worden aangeroepen
  138. /*!
  139.   @post de feed is geparsed en opgeslagen in lokale object-variabelen (tenzij de feed niet "well-formed" was)
  140.   */
  141. function parse() {
  142. $data_temp = $this->getfeed($this->file, $this->cached_file);
  143. if(!xml_parse($this->rss, $data_temp)) {
  144. die(sprintf("XML error: %s at line %d", xml_error_string(xml_get_error_code($this->rss)), xml_get_current_line_number($this->rss)));
  145. }
  146. }
  147.  
  148. //! retourneert het aantal channels
  149. /*!
  150.   @return (int) bevat het aantal channels dat de feed heeft
  151.   */
  152. function get_number_of_channels() {
  153. return sizeof($this->output['channel']);
  154. }
  155.  
  156. //! retourneert het aantal items van een bepaald channel
  157. /*!
  158.   @param $channel (int) het channel-nummer waarvan we het aantal items willen weten
  159.   @return (int) bevat het aantal items dat een specifiek channel heeft
  160.   */
  161. function get_number_of_items($channel) {
  162. return sizeof($this->output['channel'][$channel]['item']);
  163. }
  164.  
  165. //! retourneert informatie van een specifiek kanaal
  166. /*!
  167.   @param $channel (int) het channel-nummer waarvan we informatie willen hebben
  168.   @return (array) associatief array met hierin channel-informatie
  169.   */
  170. function get_channel_info($channel) {
  171. $ret = array();
  172. foreach($this->output['channel'][$channel] as $k => $v) {
  173. if($k != "item") {
  174. $ret[$k] = $v;
  175. }
  176. }
  177. return $ret;
  178. }
  179.  
  180. //! retouneert alle items van een bepaald channel - dit is de methode waar het in deze klasse in feite om draait
  181. /*!
  182.   @param $channel (int) het channel-nummer waar we de items van willen hebben
  183.   @return (array) associatief array met hierin alle items van het channel $channel
  184.   */
  185. function get_items($channel) {
  186. return $this->output['channel'][$channel]['item'];
  187. }
  188.  
  189. //! deze methode zou aangeroepen moeten worden na het parsen van een XML-file - je moet ook een nieuw rss20-object aanmaken als je meerdere RSS-bestanden wilt parsen in hetzelfde script
  190. /*!
  191.   @post de parser is vrijgegeven (waarmee in feite het rss20-object is "opgebruikt")
  192.   */
  193. function free_parser() {
  194. xml_parser_free($this->rss);
  195. }
  196.  
  197. //! stript de <![CDATA[...]]> tag en / of spaties uit de item-tags
  198. /*!
  199.   @param $input (string) inhoud van een tag-paar (mogelijk omringd door een <![CDATA[...]]> tag)
  200.   @return (string) getrimde inhoud van het tag-pair, ontdaan van de <![CDATA[...]]> tag als deze aanwezig was
  201.   */
  202. function cdata($input) {
  203. // verwijder <![CDATA[content]]> uit $input
  204. // trim() is ook nodig - anders blijf je zitten met (ongewenste) spaties die achter zijn gebleven in <![CDATA[...]]>
  205. return preg_replace("/<!\[CDATA\[(.*)\]\]>/se", "trim('\\1')", $input);
  206. }
  207.  
  208. //! leest een XML-feed van een URL zonder de pagina (al te lang :)) te laten hangen - www.php.net/file - function fetchUrlWithoutHanging()
  209. /*!
  210.   @param $url (string) URL van de externe feed
  211.   @param $cache (string) naam van de file die gebruikt dient te worden voor caching, laat deze leeg als je de feed niet wilt cachen (default "")
  212.   @param $refresh (int) max leeftijd van de gecachede feed in seconden, als de leeftijd groter is dan $refresh seconden dan zal de feed gelezen worden van $url (default 900)
  213.   @return (string) de complete feed
  214.  
  215.   NB: Deze methode is nog een beetje een work-in-progress, hier kunnen dus
  216.   waarschijnlijk nog zaken aan veranderd / verbeterd worden.
  217.   */
  218. function getfeed($url, $cache="", $refresh=900) {
  219. $path = $cache; // het pad waar je je XML-bestand naar wegschrijft in geval van caching - zorg dat dit klopt !
  220.  
  221. /*
  222.   Dient de feed uit cache gelezen te worden ?
  223.   filesize($path) == 0 zou kunnen aangeven dat de site plat was/is (of de socket-connectie is ge-timeout)
  224.   we willen iig niet $timeout seconden wachten om het nog eens te proberen
  225.   */
  226. if($cache != "" && file_exists($path) && time() - filemtime($path) < $refresh && filesize($path) > 0) {
  227. $this->from_file = true; // de feed wordt uit cache gelezen
  228.  
  229. $handle = fopen($path, "r");
  230. $size = (filesize($path) > 0) ? filesize($path) : 1024; // empty file fix
  231. $return = fread($handle, $size);
  232. fclose($handle);
  233. }
  234.  
  235. if($cache == "" ||
  236. !file_exists($path) ||
  237. (file_exists($path) && time() - filemtime($path) >= $refresh) ||
  238. ($cache != "" && file_exists($path) && filesize($path) == 0)) {
  239. // De file was niet gecached, bestond nog niet, is verlopen of is leeg, update het bestand
  240. // $this->from_file stond al op false, dit geeft aan dat het XML-bestand gelezen wordt van een externe URL
  241.  
  242. // Was het een tweede poging ?
  243. if(file_exists($path) && filesize($path) == 0 && $cache != "") {
  244. $this->site_retry = true;
  245. }
  246.  
  247. /*
  248.   Het volgende deel is een ietwat aangepaste variant van de fetchUrlWithoutHanging() functie
  249.   */
  250.  
  251. // Set maximum number of seconds (can have floating-point) to wait for feed before displaying page without feed
  252. // retrieving a feed can take (far) longer than this - cause ?
  253. $numberOfSeconds = 5;
  254.  
  255. // Suppress error reporting so Web site visitors are unaware if the feed fails
  256.  
  257. // Extract resource path and domain from URL ready for fsockopen
  258. $url = str_replace("http://", "", $url);
  259. $urlComponents = explode("/", $url);
  260. $domain = $urlComponents[0];
  261. $resourcePath = str_replace($domain, "", $url);
  262.  
  263. // Establish a connection - may take 5 seconds
  264. $socketConnection = fsockopen($domain, 80, $errno, $errstr, $numberOfSeconds);
  265.  
  266. // modification (doesn't seem to do much though :))
  267. stream_set_blocking($socketConnection, false);
  268. stream_set_timeout($socketConnection, $numberOfSeconds); // connection may take 5 seconds
  269.  
  270. if(!$socketConnection) {
  271. // you may wish to remove the following debugging line on a live Web site
  272. // print("<!-- Network error: $errstr ($errno) -->");
  273. } else {
  274. $xml = "";
  275. fputs($socketConnection, "GET /".$resourcePath." HTTP/1.0\r\nHost: ".$domain."\r\n\r\n");
  276.  
  277. // loop until end of file
  278. while (!feof($socketConnection)) {
  279. $xml .= fgets($socketConnection, 128);
  280. } // end while
  281.  
  282. fclose($socketConnection);
  283. }
  284.  
  285. // Aanpassing - sloop headers eruit
  286. preg_match("/(<\?xml|<rss)(.*)/s", $xml, $content);
  287. $return = $content[0];
  288.  
  289. // het volgende stuk code kan op sommige webservers problemen opleveren, dit ligt waarschijnlijk niet aan dit script
  290. // Dient de feed naar cache geschreven te worden ?
  291. if($cache != "") {
  292. $handle = fopen($path, "w");
  293. fwrite($handle, $return);
  294. fclose($handle);
  295. chmod($path, 0644); // dit kan op sommige webservers problemen opleveren, dit ligt niet aan dit script
  296. }
  297. }
  298. /*
  299.   Retourneer alleen de content. Als de content leeg is, dan is de URL naar de feed waarschijnlijk
  300.   verkeerd (403 - moved permanently)
  301.   Voor alle content (inclusief headers) retourneer $xml
  302.   */
  303.  
  304. return $return;
  305. }
  306.  
  307. //! geeft aan of de feed uit cache werd gelezen (true) of uit een externe file (false)
  308. /*!
  309.   @return (bool) boolean die aangeeft of de feed uit een lokaal bestand werd gelezen
  310.   */
  311. function read_from_file() {
  312. return $this->from_file;
  313. }
  314.  
  315. //! geeft aan of de laatste gecachede file leeg was - dit zou kunnen inhouden dat de site vanwaar de feed afkomstig is plat ligt
  316. /*!
  317.   @return (bool) boolean die aangeeft of de laatst gecachede file leeg was
  318.   */
  319. function read_site_retry() {
  320. return $this->site_retry;
  321. }
  322. }
  323. ?>


voorbeeld:
  1. <?php
  2. /////////////////////////////////////////////////////////////////////////////////
  3. // voorbeeld #1 //
  4. // lees een feed zonder caching //
  5. /////////////////////////////////////////////////////////////////////////////////
  6.  
  7. require("class.rss20.php"); // include class-file
  8.  
  9. $rss = new rss20(); // maak een rss-object aan
  10.  
  11. $rss->newfile("http://www.sitemasters.be/rss/xml/nieuws.xml"); // lees een externe feed (zonder caching)
  12.  
  13. $rss->parse(); // parse de XML-file
  14.  
  15. // druk de items af
  16. ?>
  17. <b>sitemasters.be - nieuws</b> (<?=$rss->get_number_of_items(0) ?> items)<br />
  18. <?php
  19. $items = $rss->get_items(0); // aantal items in channel 0
  20. // de informatie die je wilt afdrukken hangt af van wat er in de feed aangeboden wordt en
  21. // wat je zelf wilt laten zien, deze zal dus niet altjd hetzelfde zijn
  22. for($i=0; $i < sizeof($items); $i++) {
  23. echo date("Y-m-d H:i", strtotime($items[$i]['pubDate']))." - ";
  24. echo "<a href=\"".$items[$i]['link']."\" target=\"_blank\">".$items[$i]['title']."</a> ";
  25. echo "door ".htmlentities($items[$i]['author'])."<br />\n";
  26. }
  27.  
  28. $rss->free_parser(); // geef de parser weer vrij
  29. unset($rss); // als je meerdere feeds op één pagina wilt zetten moet je het object unsetten (of een andere var-naam gebruiken)
  30.  
  31. /////////////////////////////////////////////////////////////////////////////////
  32. // voorbeeld #2 //
  33. // lees een feed met caching (cache het bestand als "sitemasters.scripts.xml") //
  34. /////////////////////////////////////////////////////////////////////////////////
  35. $rss = new rss20();
  36. $rss->newfile("http://www.sitemasters.be/rss/xml/scripts.xml", "sitemasters.scripts.xml");
  37. $rss->parse();
  38. ?>
  39. <br />
  40. <b>sitemasters.be - scripts</b> (<?=$rss->get_number_of_items(0) ?> items)<br />
  41. <?php
  42. $items = $rss->get_items(0);
  43. for($i=0; $i < sizeof($items); $i++) {
  44. echo date("Y-m-d H:i", strtotime($items[$i]['pubDate']))." - <a href=\"".$items[$i]['link']."\" target=\"_blank\">".$items[$i]['title']."</a><br />\n";
  45. }
  46.  
  47. $from_file = $rss->read_from_file(); // wordt de file uit cache gelezen ?
  48. $site_retry = $rss->read_site_retry(); // mislukte de laatste poging om de feed te cachen ?
  49.  
  50. $rss->free_parser(); // geef de parser vrij
  51. unset($rss); // unset het RSS-object
  52. echo "gelezen van ".($from_file ? "file" : "site");
  53. echo ($site_retry ? " (retry)" : "")."<br /><br />\n";
  54. ?>
Download code! Download code (.txt)

 Stemmen
Niet ingelogd.

 Reacties
Post een reactie
Lees de reacties (16)
© 2002-2019 Sitemasters.be - Regels - Laadtijd: 0.123s