'http://feeds.feedburner.com/tekArtist', 'Slashdot' => 'http://rss.slashdot.org/Slashdot/slashdot', 'Digg' => 'http://digg.com/rss/containertechnology.xml', 'Google News' => 'http://news.google.com/?output=rss'); // Define items per page preferences // Start with a small number as a default view, because many // small devices have drastic memory requirements that can // lead to http errors (413: Request entity too large). $minLimit = 1; $maxLimit = 25; $defaultLimit = 5; // Define how many character we want to see from each entry // description, to provide only an excerpt when the feed // publishes very long content. Number of characters to remain // after the description has been stripped of all HT/XML tags. $descLimit = 1024; ### END USER CONFIGURABLE OPTIONS ############################################# ### START PROCESSING ########################################################## $defaultOffset = 0; define(COOKIE_DEFAULT_SOURCE, 'parseMe_defaultSource'); define(COOKIE_DEFAULT_LIMIT, 'parseMe_defaultLimit'); // start the xhtml interface print '<'.'?xml version="1.0"?'.">\n" . "\n" . "\n" . "\n"; // Define a default xml source key $sourceKeys = array_keys($xmlSourceList); $defaultSource = $sourceKeys[0]; // Get and clean user input $sourceChoice = rawurldecode($_GET['src']); $limit = intval($_GET['lmt']); $offset = intval($_GET['ofst']); $saveDefault = intval($_GET['sv']); // If no valid source key choice, then use default if( ($sourceChoice == '') or (!isset($xmlSourceList[$sourceChoice])) ){ // see if the user has a saved preference (cookie) $userDefaultSource = $_COOKIE[COOKIE_DEFAULT_SOURCE]; if($userDefaultSource != ''){ if(isset($xmlSourceList[$userDefaultSource])){ $sourceChoice = $userDefaultSource; } } else{ $sourceChoice = $defaultSource; } } elseif($saveDefault > 0){ // if a source is submitted and valid, save // it as a new user default if requested. setcookie (COOKIE_DEFAULT_SOURCE, $sourceChoice, strtotime('5 years'), '/'); } // Get the url of the chosen source $xmlSource = $xmlSourceList[$sourceChoice]; // Cleanup the source key for display in the header $cleanSourceChoice = htmlspecialchars($sourceChoice); // Set page title and header $title .= $appName.' » '.$cleanSourceChoice; // Cleanup the selected page offset and item limit if( ($limit < $minLimit) or ($limit > $maxLimit) ){ // see if the user has a saved preference (cookie) $userDefaultLimit = $_COOKIE[COOKIE_DEFAULT_LIMIT]; if($userDefaultLimit != ''){ $limit = $userDefaultLimit; } else{ $limit = $defaultLimit; } } elseif($saveDefault > 0){ // if a limit is submitted and valid, save // it as a new user default if requested. setcookie (COOKIE_DEFAULT_LIMIT, $limit, strtotime('5 years'), '/'); } if( ($offset < 1) or ($offset > 100) ) $offset = $defaultOffset; // Print head block w/ defined and clean info print " \n" . " {$title}\n" . " \n" . " \n" . " \n" . " \n" . " \n"; // Cache error handling if(!file_exists($cacheDir)){ print "

Sorry, but the cache directory cannot be found.

\n" . "

Please create a writable directory at {$cacheDir}.

\n"; } elseif(!is_dir($cacheDir)){ print "

Sorry, but the cache location is not a directory.

\n" . "

Please create a writable directory at {$cacheDir}.

\n"; } elseif(!is_writable($cacheDir)){ print "

Sorry, but the cache directory is not writable.

\n" . "

Please make the directory writable: {$cacheDir}.

\n"; } else{ // Define the name and location of the local xml cache file $cacheFileName = str_replace('%','_',rawurlencode($xmlSource)).'.cache.xml'; $cacheDestination = "./cache/{$cacheFileName}"; // Set the cache creation/modification time $cacheMtime = time(); // Define a HTTP user agent to be nice to the sources' logs. $httpOptions = array( 'http' => array( 'method' => 'GET', 'header' => "User-Agent: parseMe (stephane.daury.com/parseMe/)\r\n" ) ); $context = stream_context_create($httpOptions); // Define if we should get new content from the source, // or use the local cache instead if(!file_exists($cacheDestination)){ file_put_contents($cacheDestination, file_get_contents($xmlSource, false, $context)); } elseif(filemtime($cacheDestination) < strtotime($cacheTTL.' ago')){ file_put_contents($cacheDestination, file_get_contents($xmlSource, false, $context)); } else{ $cacheMtime = filemtime($cacheDestination); } // Load and test xml content from RSS or Atom feed if(($xml = simplexml_load_file($cacheDestination)) === false){ print "

Sorry, but \"{$cleanSourceChoice}\" cannot be parsed at this time.

\n"; } else{ // Use the parseMe class (see below) to parse // whichever xml format we got into a clean, standard // structure, taking in consideration the current // paging preferences (offset, limit) $parseMe = new parseMe($limit, $offset, $descLimit); $parseMe->parseXML($xml); $feedData = $parseMe->feedData; // Print the file body header print "

" ."\"XML\"" ." " .$feedData['feedTitle']." " ."

\n"; // If we have any, loop on the entries if(count($feedData['feedEntries']) > 0){ print "
\n"; foreach($feedData['feedEntries'] as $entry){ print "
".$entry['title'] . "
\n" . "
".$entry['description']."
\n"; } print "
\n"; } // Deal with the paging toolbar if($feedData['entryCount'] > $limit){ print "

| \n"; $link = $_SERVER['REQUEST_URI']; if(strstr($link, 'ofst=') === false) $link .= '?'; // "Previous" link if($offset > 0){ if(strstr($link, 'ofst=')) $link = preg_replace('/(\&ofst=\d{1,2})/','',$link); $link .= '&ofst='.($offset - $limit); print " « previous\n"; } // "Next" link if($feedData['entryCount'] > ($limit + $offset)){ if($offset > 0) print " | \n"; if(strstr($link, 'ofst=')) $link = preg_replace('/(\&ofst=\d{1,2})/','',$link); $link .= '&ofst='.($limit + $offset); print " next »\n"; } print " |

\n"; } // Print the cache specs print "

\n" . " {$cleanSourceChoice} XML cached on ".date('Y-m-d \a\t H:i:s T',$cacheMtime)."
\n" . " ".date('i \m\i\n. s \s\e\c.',( 1800 - (time() - $cacheMtime) ))." to refresh.
\n" . " Powered by {$appName}\n" . "

\n"; } // Now let's display the preference form // List the available sources print "
\n" . "
\n" . "
\n"; foreach($xmlSourceList as $sourceName => $sourceURL){ $checkedSource = ''; if($sourceName == $sourceChoice) $checkedSource = 'checked="checked"'; print " {$sourceName}
\n"; } // A submit button print "
\n" . "
\n"; // List the available items per page limits $checkedLimit = ''; if($limit == $minLimit) $checkedLimit = 'checked="checked"'; print "
\n" . " {$minLimit}\n"; for($i=5; $i<=$maxLimit; $i+=5){ $checkedLimit = ''; if($i == $limit) $checkedLimit = 'checked="checked"'; print " {$i}\n"; if($i % 10 != 0) print "
\n"; } // A submit button print "
\n"; // And for extra fluff, the "set as default" checkbox so users can save the // results of the form submission as the prefs when first accessing the script. print " Set as default
\n" . "
\n" . "
\n"; } // Close the xhtml file print " \n" . "\n"; // And we're done with the processing! exit; ### END PROCESSING ############################################################ ### START CLASS ############################################################### class parseMe { // @var int $_limit Only list x items per page private $_limit = 5; // @var int $_offset Start listing from item number x private $_offset = 0; // @var int $_descLimit Show only x characters from the descrition private $_descLimit = 1024; /** * @var array $feedData Associative array containing the * unified data to display, regardless of the feed type. * * feedData => feedTitle * ........ => feedLink * ........ => feedEntries => idx => link * .............................. => title * .............................. => description * ........ => entryCount */ public $feedData = array(); /** * Constructor * * @param int $limit Only list x items per page * @param int $offset Start listing from item number x * @param int $descLimit Show only x characters from the descrition * @return void */ public function __construct(&$limit, &$offset, &$descLimit) { $this->_limit = intval($limit); $this->_offset = intval($offset); $this->_descLimit = intval($descLimit); } /** * parseXML is a simple decisional wrapper. It simply * defines type of feed we are dealing with (RSS or Atom) * and calls the approriate xml parser. * * @param object $xml SimpleXML object (http://php.net/simplexml) * @return void */ public function parseXML(&$xml){ if(isset($xml->channel)){ $this->_parseRSS($xml); } elseif(isset($xml->entry)){ $this->_parseAtom($xml); } } /** * ParseRSS, as the name implies, will parse an RSS * feed into the standard array we expect for display. * See http://en.wikipedia.org/wiki/RSS_%28file_format%29 * * @param object $xml RSS, SimpleXML object (http://php.net/simplexml) * @return void */ private function _parseRSS(&$xml){ // Get the feed basic info $this->feedData['feedTitle'] = $xml->channel->title; $this->feedData['feedLink'] = $xml->channel->link; $this->feedData['feedEntries'] = array(); $this->feedData['entryCount'] = 0; // Test for the doc entries structure, depending on // the RSS version (items in/outside of channel) if(isset($xml->item)){ $feedEntries = $xml->item; } elseif(isset($xml->channel->item)){ $feedEntries = $xml->channel->item; } else{ $feedEntries = false; } // Loop on the found entries, based on paging prefs if(is_object($feedEntries)){ $this->feedData['entryCount'] = count($feedEntries); $i=0; foreach($feedEntries as $entry){ if($i >= $this->_offset){ $this->feedData['feedEntries'][$i] = array(); $this->feedData['feedEntries'][$i]['link'] = $entry->link; $this->feedData['feedEntries'][$i]['title'] = $entry->title; $this->feedData['feedEntries'][$i]['description'] = substr(strip_tags(str_replace("\n",' ', $entry->description)),0,$this->_descLimit); if(strlen($entry->description) >= $this->_descLimit) $this->feedData['feedEntries'][$i]['description'] .= '...'; } $i++; if($i >= ($this->_limit + $this->_offset)) break; } } } /** * ParseAtom, as the name implies, will parse an Atom * feed into the standard array we expect for display. * See http://en.wikipedia.org/wiki/Atom_%28standard%29 * * @param object $xml Atom, SimpleXML object (http://php.net/simplexml) * @return void */ private function _parseAtom(&$xml){ // Get the feed basic info $this->feedData['feedTitle'] = $xml->title; $feedLinkAttributes = $xml->link->attributes(); $this->feedData['feedLink'] = $feedLinkAttributes['href']; $this->feedData['feedEntries'] = array(); $this->feedData['entryCount'] = 0; // Test for the doc entries structure if(isset($xml->entry)){ $feedEntries = $xml->entry; } else{ $feedEntries = false; } // Loop on the found entries, based on paging prefs if(is_object($feedEntries)){ $this->feedData['entryCount'] = count($feedEntries); $i=0; foreach($feedEntries as $entry){ if($i >= $this->_offset){ $this->feedData['feedEntries'][$i] = array(); $entryAttributes = $entry->link->attributes(); $this->feedData['feedEntries'][$i]['link'] = $entryAttributes['href']; $this->feedData['feedEntries'][$i]['title'] = $entry->title; $this->feedData['feedEntries'][$i]['description'] = substr(strip_tags(str_replace("\n",' ', $entry->content)),0,$this->_descLimit); if(strlen($this->feedData['feedEntries'][$i]['description']) >= $this->_descLimit) $this->feedData['feedEntries'][$i]['description'] .= strlen($entry->content).'...'; } $i++; if($i >= ($this->_limit + $this->_offset)) break; } } } } // end class parseMe ### END CLASS ################################################################# // Fin - Still well under 500 lines, despite all the inline comments. :) ?>