diff options
Diffstat (limited to 'plugins/FeedSub/extlib/XML/Feed/Parser')
-rw-r--r-- | plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php | 365 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php | 261 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php | 42 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php | 214 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php | 62 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php | 277 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php | 276 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php | 151 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php | 116 | ||||
-rw-r--r-- | plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php | 335 | ||||
-rwxr-xr-x | plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php | 171 | ||||
-rw-r--r-- | plugins/FeedSub/extlib/XML/Feed/Parser/Type.php | 467 |
12 files changed, 2737 insertions, 0 deletions
diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php b/plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php new file mode 100644 index 000000000..c7e218a1e --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/Atom.php @@ -0,0 +1,365 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Atom feed class for XML_Feed_Parser + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: Atom.php,v 1.29 2008/03/30 22:00:36 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ +*/ + +/** + * This is the class that determines how we manage Atom 1.0 feeds + * + * How we deal with constructs: + * date - return as unix datetime for use with the 'date' function unless specified otherwise + * text - return as is. optional parameter will give access to attributes + * person - defaults to name, but parameter based access + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_Atom extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'atom.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + public $xpath; + + /** + * When performing XPath queries we will use this prefix + * @var string + */ + private $xpathPrefix = '//'; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'Atom 1.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_AtomElement'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'entry'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'author' => array('Person'), + 'contributor' => array('Person'), + 'icon' => array('Text'), + 'logo' => array('Text'), + 'id' => array('Text', 'fail'), + 'rights' => array('Text'), + 'subtitle' => array('Text'), + 'title' => array('Text', 'fail'), + 'updated' => array('Date', 'fail'), + 'link' => array('Link'), + 'generator' => array('Text'), + 'category' => array('Category')); + + /** + * Here we provide a few mappings for those very special circumstances in + * which it makes sense to map back to the RSS2 spec. Key is RSS2 version + * value is an array consisting of the equivalent in atom and any attributes + * needed to make the mapping. + * @var array + */ + protected $compatMap = array( + 'guid' => array('id'), + 'links' => array('link'), + 'tags' => array('category'), + 'contributors' => array('contributor')); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + if ($strict) { + if (! $this->model->relaxNGValidateSource($this->relax)) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($this->model); + $this->xpath->registerNamespace('atom', 'http://www.w3.org/2005/Atom'); + $this->numberEntries = $this->count('entry'); + } + + /** + * Implement retrieval of an entry based on its ID for atom feeds. + * + * This function uses XPath to get the entry based on its ID. If DOMXPath::evaluate + * is available, we also use that to store a reference to the entry in the array + * used by getEntryByOffset so that method does not have to seek out the entry + * if it's requested that way. + * + * @param string $id any valid Atom ID. + * @return XML_Feed_Parser_AtomElement + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//atom:entry[atom:id='$id']"); + + if ($entries->length > 0) { + $xmlBase = $entries->item(0)->baseURI; + $entry = new $this->itemClass($entries->item(0), $this, $xmlBase); + + if (in_array('evaluate', get_class_methods($this->xpath))) { + $offset = $this->xpath->evaluate("count(preceding-sibling::atom:entry)", $entries->item(0)); + $this->entries[$offset] = $entry; + } + + $this->idMappings[$id] = $entry; + + return $entry; + } + + } + + /** + * Retrieves data from a person construct. + * + * Get a person construct. We default to the 'name' element but allow + * access to any of the elements. + * + * @param string $method The name of the person construct we want + * @param array $arguments An array which we hope gives a 'param' + * @return string|false + */ + protected function getPerson($method, $arguments) + { + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $parameter = empty($arguments[1]['param']) ? 'name' : $arguments[1]['param']; + $section = $this->model->getElementsByTagName($method); + + if ($parameter == 'url') { + $parameter = 'uri'; + } + + if ($section->length <= $offset) { + return false; + } + + $param = $section->item($offset)->getElementsByTagName($parameter); + if ($param->length == 0) { + return false; + } + return $param->item(0)->nodeValue; + } + + /** + * Retrieves an element's content where that content is a text construct. + * + * Get a text construct. When calling this method, the two arguments + * allowed are 'offset' and 'attribute', so $parser->subtitle() would + * return the content of the element, while $parser->subtitle(false, 'type') + * would return the value of the type attribute. + * + * @todo Clarify overlap with getContent() + * @param string $method The name of the text construct we want + * @param array $arguments An array which we hope gives a 'param' + * @return string + */ + protected function getText($method, $arguments) + { + $offset = empty($arguments[0]) ? 0: $arguments[0]; + $attribute = empty($arguments[1]) ? false : $arguments[1]; + $tags = $this->model->getElementsByTagName($method); + + if ($tags->length <= $offset) { + return false; + } + + $content = $tags->item($offset); + + if (! $content->hasAttribute('type')) { + $content->setAttribute('type', 'text'); + } + $type = $content->getAttribute('type'); + + if (! empty($attribute) and + ! ($method == 'generator' and $attribute == 'name')) { + if ($content->hasAttribute($attribute)) { + return $content->getAttribute($attribute); + } else if ($attribute == 'href' and $content->hasAttribute('uri')) { + return $content->getAttribute('uri'); + } + return false; + } + + return $this->parseTextConstruct($content); + } + + /** + * Extract content appropriately from atom text constructs + * + * Because of different rules applied to the content element and other text + * constructs, they are deployed as separate functions, but they share quite + * a bit of processing. This method performs the core common process, which is + * to apply the rules for different mime types in order to extract the content. + * + * @param DOMNode $content the text construct node to be parsed + * @return String + * @author James Stewart + **/ + protected function parseTextConstruct(DOMNode $content) + { + if ($content->hasAttribute('type')) { + $type = $content->getAttribute('type'); + } else { + $type = 'text'; + } + + if (strpos($type, 'text/') === 0) { + $type = 'text'; + } + + switch ($type) { + case 'text': + case 'html': + return $content->textContent; + break; + case 'xhtml': + $container = $content->getElementsByTagName('div'); + if ($container->length == 0) { + return false; + } + $contents = $container->item(0); + if ($contents->hasChildNodes()) { + /* Iterate through, applying xml:base and store the result */ + $result = ''; + foreach ($contents->childNodes as $node) { + $result .= $this->traverseNode($node); + } + return $result; + } + break; + case preg_match('@^[a-zA-Z]+/[a-zA-Z+]*xml@i', $type) > 0: + return $content; + break; + case 'application/octet-stream': + default: + return base64_decode(trim($content->nodeValue)); + break; + } + return false; + } + /** + * Get a category from the entry. + * + * A feed or entry can have any number of categories. A category can have the + * attributes term, scheme and label. + * + * @param string $method The name of the text construct we want + * @param array $arguments An array which we hope gives a 'param' + * @return string + */ + function getCategory($method, $arguments) + { + $offset = empty($arguments[0]) ? 0: $arguments[0]; + $attribute = empty($arguments[1]) ? 'term' : $arguments[1]; + $categories = $this->model->getElementsByTagName('category'); + if ($categories->length <= $offset) { + $category = $categories->item($offset); + if ($category->hasAttribute($attribute)) { + return $category->getAttribute($attribute); + } + } + return false; + } + + /** + * This element must be present at least once with rel="feed". This element may be + * present any number of further times so long as there is no clash. If no 'rel' is + * present and we're asked for one, we follow the example of the Universal Feed + * Parser and presume 'alternate'. + * + * @param int $offset the position of the link within the container + * @param string $attribute the attribute name required + * @param array an array of attributes to search by + * @return string the value of the attribute + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + if (is_array($params) and !empty($params)) { + $terms = array(); + $alt_predicate = ''; + $other_predicate = ''; + + foreach ($params as $key => $value) { + if ($key == 'rel' && $value == 'alternate') { + $alt_predicate = '[not(@rel) or @rel="alternate"]'; + } else { + $terms[] = "@$key='$value'"; + } + } + if (!empty($terms)) { + $other_predicate = '[' . join(' and ', $terms) . ']'; + } + $query = $this->xpathPrefix . 'atom:link' . $alt_predicate . $other_predicate; + $links = $this->xpath->query($query); + } else { + $links = $this->model->getElementsByTagName('link'); + } + if ($links->length > $offset) { + if ($links->item($offset)->hasAttribute($attribute)) { + $value = $links->item($offset)->getAttribute($attribute); + if ($attribute == 'href') { + $value = $this->addBase($value, $links->item($offset)); + } + return $value; + } else if ($attribute == 'rel') { + return 'alternate'; + } + } + return false; + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php b/plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php new file mode 100755 index 000000000..063ecb617 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/AtomElement.php @@ -0,0 +1,261 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * AtomElement class for XML_Feed_Parser package + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: AtomElement.php,v 1.19 2007/03/26 12:43:11 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class provides support for atom entries. It will usually be called by + * XML_Feed_Parser_Atom with which it shares many methods. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_AtomElement extends XML_Feed_Parser_Atom +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_Atom + */ + protected $parent; + + /** + * When performing XPath queries we will use this prefix + * @var string + */ + private $xpathPrefix = ''; + + /** + * xml:base values inherited by the element + * @var string + */ + protected $xmlBase; + + /** + * Here we provide a few mappings for those very special circumstances in + * which it makes sense to map back to the RSS2 spec or to manage other + * compatibilities (eg. with the Univeral Feed Parser). Key is the other version's + * name for the command, value is an array consisting of the equivalent in our atom + * api and any attributes needed to make the mapping. + * @var array + */ + protected $compatMap = array( + 'guid' => array('id'), + 'links' => array('link'), + 'tags' => array('category'), + 'contributors' => array('contributor')); + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'author' => array('Person', 'fallback'), + 'contributor' => array('Person'), + 'id' => array('Text', 'fail'), + 'published' => array('Date'), + 'updated' => array('Date', 'fail'), + 'title' => array('Text', 'fail'), + 'rights' => array('Text', 'fallback'), + 'summary' => array('Text'), + 'content' => array('Content'), + 'link' => array('Link'), + 'enclosure' => array('Enclosure'), + 'category' => array('Category')); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_Atom $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + $this->xmlBase = $xmlBase; + $this->xpathPrefix = "//atom:entry[atom:id='" . $this->id . "']/"; + $this->xpath = $this->parent->xpath; + } + + /** + * Provides access to specific aspects of the author data for an atom entry + * + * Author data at the entry level is more complex than at the feed level. + * If atom:author is not present for the entry we need to look for it in + * an atom:source child of the atom:entry. If it's not there either, then + * we look to the parent for data. + * + * @param array + * @return string + */ + function getAuthor($arguments) + { + /* Find out which part of the author data we're looking for */ + if (isset($arguments['param'])) { + $parameter = $arguments['param']; + } else { + $parameter = 'name'; + } + + $test = $this->model->getElementsByTagName('author'); + if ($test->length > 0) { + $item = $test->item(0); + return $item->getElementsByTagName($parameter)->item(0)->nodeValue; + } + + $source = $this->model->getElementsByTagName('source'); + if ($source->length > 0) { + $test = $this->model->getElementsByTagName('author'); + if ($test->length > 0) { + $item = $test->item(0); + return $item->getElementsByTagName($parameter)->item(0)->nodeValue; + } + } + return $this->parent->getAuthor($arguments); + } + + /** + * Returns the content of the content element or info on a specific attribute + * + * This element may or may not be present. It cannot be present more than + * once. It may have a 'src' attribute, in which case there's no content + * If not present, then the entry must have link with rel="alternate". + * If there is content we return it, if not and there's a 'src' attribute + * we return the value of that instead. The method can take an 'attribute' + * argument, in which case we return the value of that attribute if present. + * eg. $item->content("type") will return the type of the content. It is + * recommended that all users check the type before getting the content to + * ensure that their script is capable of handling the type of returned data. + * (data carried in the content element can be either 'text', 'html', 'xhtml', + * or any standard MIME type). + * + * @return string|false + */ + protected function getContent($method, $arguments = array()) + { + $attribute = empty($arguments[0]) ? false : $arguments[0]; + $tags = $this->model->getElementsByTagName('content'); + + if ($tags->length == 0) { + return false; + } + + $content = $tags->item(0); + + if (! $content->hasAttribute('type')) { + $content->setAttribute('type', 'text'); + } + if (! empty($attribute)) { + return $content->getAttribute($attribute); + } + + $type = $content->getAttribute('type'); + + if (! empty($attribute)) { + if ($content->hasAttribute($attribute)) + { + return $content->getAttribute($attribute); + } + return false; + } + + if ($content->hasAttribute('src')) { + return $content->getAttribute('src'); + } + + return $this->parseTextConstruct($content); + } + + /** + * For compatibility, this method provides a mapping to access enclosures. + * + * The Atom spec doesn't provide for an enclosure element, but it is + * generally supported using the link element with rel='enclosure'. + * + * @param string $method - for compatibility with our __call usage + * @param array $arguments - for compatibility with our __call usage + * @return array|false + */ + function getEnclosure($method, $arguments = array()) + { + $offset = isset($arguments[0]) ? $arguments[0] : 0; + $query = "//atom:entry[atom:id='" . $this->getText('id', false) . + "']/atom:link[@rel='enclosure']"; + + $encs = $this->parent->xpath->query($query); + if ($encs->length > $offset) { + try { + if (! $encs->item($offset)->hasAttribute('href')) { + return false; + } + $attrs = $encs->item($offset)->attributes; + $length = $encs->item($offset)->hasAttribute('length') ? + $encs->item($offset)->getAttribute('length') : false; + return array( + 'url' => $attrs->getNamedItem('href')->value, + 'type' => $attrs->getNamedItem('type')->value, + 'length' => $length); + } catch (Exception $e) { + return false; + } + } + return false; + } + + /** + * Get details of this entry's source, if available/relevant + * + * Where an atom:entry is taken from another feed then the aggregator + * is supposed to include an atom:source element which replicates at least + * the atom:id, atom:title, and atom:updated metadata from the original + * feed. Atom:source therefore has a very similar structure to atom:feed + * and if we find it we will return it as an XML_Feed_Parser_Atom object. + * + * @return XML_Feed_Parser_Atom|false + */ + function getSource() + { + $test = $this->model->getElementsByTagName('source'); + if ($test->length == 0) { + return false; + } + $source = new XML_Feed_Parser_Atom($test->item(0)); + } + + /** + * Get the entry as an XML string + * + * Return an XML serialization of the feed, should it be required. Most + * users however, will already have a serialization that they used when + * instantiating the object. + * + * @return string XML serialization of element + */ + function __toString() + { + $simple = simplexml_import_dom($this->model); + return $simple->asXML(); + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php b/plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php new file mode 100755 index 000000000..1e76e3f85 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/Exception.php @@ -0,0 +1,42 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Keeps the exception class for XML_Feed_Parser. + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL + * @version CVS: $Id: Exception.php,v 1.3 2005/11/07 01:52:35 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * We are extending PEAR_Exception + */ +require_once 'PEAR/Exception.php'; + +/** + * XML_Feed_Parser_Exception is a simple extension of PEAR_Exception, existing + * to help with identification of the source of exceptions. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_Exception extends PEAR_Exception +{ + +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php new file mode 100755 index 000000000..07f38f911 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09.php @@ -0,0 +1,214 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * RSS0.9 class for XML_Feed_Parser + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS09.php,v 1.5 2006/07/26 21:18:46 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS0.9 feeds. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + * @todo Find a Relax NG URI we can use + */ +class XML_Feed_Parser_RSS09 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = ''; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 0.9'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS09Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Text'), + 'description' => array('Text'), + 'image' => array('Image'), + 'textinput' => array('TextInput')); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'link' => array('link'), + 'subtitle' => array('description')); + + /** + * We will be working with multiple namespaces and it is useful to + * keep them together + * @var array + */ + protected $namespaces = array( + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#'); + + /** + * Our constructor does nothing more than its parent. + * + * @todo RelaxNG validation + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + $this->xpath = new DOMXPath($model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Included for compatibility -- will not work with RSS 0.9 + * + * This is not something that will work with RSS0.9 as it does not have + * clear restrictions on the global uniqueness of IDs. + * + * @param string $id any valid ID. + * @return false + */ + function getEntryById($id) + { + return false; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->model->getElementsByTagName('image'); + if ($images->length > 0) { + $image = $images->item(0); + $details = array(); + if ($image->hasChildNodes()) { + $details = array( + 'title' => $image->getElementsByTagName('title')->item(0)->value, + 'link' => $image->getElementsByTagName('link')->item(0)->value, + 'url' => $image->getElementsByTagName('url')->item(0)->value); + } else { + $details = array('title' => false, + 'link' => false, + 'url' => $image->attributes->getNamedItem('resource')->nodeValue); + } + $details = array_merge($details, + array('description' => false, 'height' => false, 'width' => false)); + if (! empty($details)) { + return $details; + } + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness we will support it. + * + * @return array|false + */ + protected function getTextInput() + { + $inputs = $this->model->getElementsByTagName('textinput'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + $results = array(); + $results['title'] = isset( + $input->getElementsByTagName('title')->item(0)->value) ? + $input->getElementsByTagName('title')->item(0)->value : null; + $results['description'] = isset( + $input->getElementsByTagName('description')->item(0)->value) ? + $input->getElementsByTagName('description')->item(0)->value : null; + $results['name'] = isset( + $input->getElementsByTagName('name')->item(0)->value) ? + $input->getElementsByTagName('name')->item(0)->value : null; + $results['link'] = isset( + $input->getElementsByTagName('link')->item(0)->value) ? + $input->getElementsByTagName('link')->item(0)->value : null; + if (empty($results['link']) && + $input->attributes->getNamedItem('resource')) { + $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue; + } + if (! empty($results)) { + return $results; + } + } + return false; + } + + /** + * Get details of a link from the feed. + * + * In RSS1 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. + * + * @return string + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + $links = $this->model->getElementsByTagName('link'); + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php new file mode 100755 index 000000000..d41f36e8d --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS09Element.php @@ -0,0 +1,62 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * RSS0.9 Element class for XML_Feed_Parser + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS09Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/* + * This class provides support for RSS 0.9 entries. It will usually be called by + * XML_Feed_Parser_RSS09 with which it shares many methods. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS09Element extends XML_Feed_Parser_RSS09 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS09 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Link')); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php new file mode 100755 index 000000000..60c9938ba --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1.php @@ -0,0 +1,277 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * RSS1 class for XML_Feed_Parser + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS1.php,v 1.10 2006/07/27 13:52:05 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS1.0 feeds. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + * @todo Find a Relax NG URI we can use + */ +class XML_Feed_Parser_RSS1 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'rss10.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 1.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS1Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Text'), + 'description' => array('Text'), + 'image' => array('Image'), + 'textinput' => array('TextInput'), + 'updatePeriod' => array('Text'), + 'updateFrequency' => array('Text'), + 'updateBase' => array('Date'), + 'rights' => array('Text'), # dc:rights + 'description' => array('Text'), # dc:description + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date') # dc:contributor + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'link' => array('link'), + 'subtitle' => array('description'), + 'author' => array('creator'), + 'updated' => array('date')); + + /** + * We will be working with multiple namespaces and it is useful to + * keep them together + * @var array + */ + protected $namespaces = array( + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rss' => 'http://purl.org/rss/1.0/', + 'dc' => 'http://purl.org/rss/1.0/modules/dc/', + 'content' => 'http://purl.org/rss/1.0/modules/content/', + 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/'); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + if ($strict) { + $validate = $this->model->relaxNGValidate(self::getSchemaDir . + DIRECTORY_SEPARATOR . $this->relax); + if (! $validate) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Allows retrieval of an entry by ID where the rdf:about attribute is used + * + * This is not really something that will work with RSS1 as it does not have + * clear restrictions on the global uniqueness of IDs. We will employ the + * _very_ hit and miss method of selecting entries based on the rdf:about + * attribute. If DOMXPath::evaluate is available, we also use that to store + * a reference to the entry in the array used by getEntryByOffset so that + * method does not have to seek out the entry if it's requested that way. + * + * @param string $id any valid ID. + * @return XML_Feed_Parser_RSS1Element + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//rss:item[@rdf:about='$id']"); + if ($entries->length > 0) { + $classname = $this->itemClass; + $entry = new $classname($entries->item(0), $this); + if (in_array('evaluate', get_class_methods($this->xpath))) { + $offset = $this->xpath->evaluate("count(preceding-sibling::rss:item)", $entries->item(0)); + $this->entries[$offset] = $entry; + } + $this->idMappings[$id] = $entry; + return $entry; + } + return false; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->model->getElementsByTagName('image'); + if ($images->length > 0) { + $image = $images->item(0); + $details = array(); + if ($image->hasChildNodes()) { + $details = array( + 'title' => $image->getElementsByTagName('title')->item(0)->value, + 'link' => $image->getElementsByTagName('link')->item(0)->value, + 'url' => $image->getElementsByTagName('url')->item(0)->value); + } else { + $details = array('title' => false, + 'link' => false, + 'url' => $image->attributes->getNamedItem('resource')->nodeValue); + } + $details = array_merge($details, array('description' => false, 'height' => false, 'width' => false)); + if (! empty($details)) { + return $details; + } + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness we will support it. + * + * @return array|false + */ + protected function getTextInput() + { + $inputs = $this->model->getElementsByTagName('textinput'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + $results = array(); + $results['title'] = isset( + $input->getElementsByTagName('title')->item(0)->value) ? + $input->getElementsByTagName('title')->item(0)->value : null; + $results['description'] = isset( + $input->getElementsByTagName('description')->item(0)->value) ? + $input->getElementsByTagName('description')->item(0)->value : null; + $results['name'] = isset( + $input->getElementsByTagName('name')->item(0)->value) ? + $input->getElementsByTagName('name')->item(0)->value : null; + $results['link'] = isset( + $input->getElementsByTagName('link')->item(0)->value) ? + $input->getElementsByTagName('link')->item(0)->value : null; + if (empty($results['link']) and + $input->attributes->getNamedItem('resource')) { + $results['link'] = + $input->attributes->getNamedItem('resource')->nodeValue; + } + if (! empty($results)) { + return $results; + } + } + return false; + } + + /** + * Employs various techniques to identify the author + * + * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher + * elements for defining authorship in RSS1. We will try each of those in + * turn in order to simulate the atom author element and will return it + * as text. + * + * @return array|false + */ + function getAuthor() + { + $options = array('creator', 'contributor', 'publisher'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length > 0) { + return $test->item(0)->value; + } + } + return false; + } + + /** + * Retrieve a link + * + * In RSS1 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. + * + * @return string + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + $links = $this->model->getElementsByTagName('link'); + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php new file mode 100755 index 000000000..3cd1ef15d --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11.php @@ -0,0 +1,276 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * RSS1.1 class for XML_Feed_Parser + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS11.php,v 1.6 2006/07/27 13:52:05 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS1.1 feeds. RSS1.1 is documented at: + * http://inamidst.com/rss1.1/ + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + * @todo Support for RDF:List + * @todo Ensure xml:lang is accessible to users + */ +class XML_Feed_Parser_RSS11 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'rss11.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 1.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS1Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'link' => array('Text'), + 'description' => array('Text'), + 'image' => array('Image'), + 'updatePeriod' => array('Text'), + 'updateFrequency' => array('Text'), + 'updateBase' => array('Date'), + 'rights' => array('Text'), # dc:rights + 'description' => array('Text'), # dc:description + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date') # dc:contributor + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'link' => array('link'), + 'subtitle' => array('description'), + 'author' => array('creator'), + 'updated' => array('date')); + + /** + * We will be working with multiple namespaces and it is useful to + * keep them together. We will retain support for some common RSS1.0 modules + * @var array + */ + protected $namespaces = array( + 'rdf' => 'http://www.w3.org/1999/02/22-rdf-syntax-ns#', + 'rss' => 'http://purl.org/net/rss1.1#', + 'dc' => 'http://purl.org/rss/1.0/modules/dc/', + 'content' => 'http://purl.org/rss/1.0/modules/content/', + 'sy' => 'http://web.resource.org/rss/1.0/modules/syndication/'); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + if ($strict) { + $validate = $this->model->relaxNGValidate(self::getSchemaDir . + DIRECTORY_SEPARATOR . $this->relax); + if (! $validate) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Attempts to identify an element by ID given by the rdf:about attribute + * + * This is not really something that will work with RSS1.1 as it does not have + * clear restrictions on the global uniqueness of IDs. We will employ the + * _very_ hit and miss method of selecting entries based on the rdf:about + * attribute. Please note that this is even more hit and miss with RSS1.1 than + * with RSS1.0 since RSS1.1 does not require the rdf:about attribute for items. + * + * @param string $id any valid ID. + * @return XML_Feed_Parser_RSS1Element + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//rss:item[@rdf:about='$id']"); + if ($entries->length > 0) { + $classname = $this->itemClass; + $entry = new $classname($entries->item(0), $this); + return $entry; + } + return false; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->model->getElementsByTagName('image'); + if ($images->length > 0) { + $image = $images->item(0); + $details = array(); + if ($image->hasChildNodes()) { + $details = array( + 'title' => $image->getElementsByTagName('title')->item(0)->value, + 'url' => $image->getElementsByTagName('url')->item(0)->value); + if ($image->getElementsByTagName('link')->length > 0) { + $details['link'] = + $image->getElementsByTagName('link')->item(0)->value; + } + } else { + $details = array('title' => false, + 'link' => false, + 'url' => $image->attributes->getNamedItem('resource')->nodeValue); + } + $details = array_merge($details, + array('description' => false, 'height' => false, 'width' => false)); + if (! empty($details)) { + return $details; + } + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness we will support it. + * + * @return array|false + */ + protected function getTextInput() + { + $inputs = $this->model->getElementsByTagName('textinput'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + $results = array(); + $results['title'] = isset( + $input->getElementsByTagName('title')->item(0)->value) ? + $input->getElementsByTagName('title')->item(0)->value : null; + $results['description'] = isset( + $input->getElementsByTagName('description')->item(0)->value) ? + $input->getElementsByTagName('description')->item(0)->value : null; + $results['name'] = isset( + $input->getElementsByTagName('name')->item(0)->value) ? + $input->getElementsByTagName('name')->item(0)->value : null; + $results['link'] = isset( + $input->getElementsByTagName('link')->item(0)->value) ? + $input->getElementsByTagName('link')->item(0)->value : null; + if (empty($results['link']) and + $input->attributes->getNamedItem('resource')) { + $results['link'] = $input->attributes->getNamedItem('resource')->nodeValue; + } + if (! empty($results)) { + return $results; + } + } + return false; + } + + /** + * Attempts to discern authorship + * + * Dublin Core provides the dc:creator, dc:contributor, and dc:publisher + * elements for defining authorship in RSS1. We will try each of those in + * turn in order to simulate the atom author element and will return it + * as text. + * + * @return array|false + */ + function getAuthor() + { + $options = array('creator', 'contributor', 'publisher'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length > 0) { + return $test->item(0)->value; + } + } + return false; + } + + /** + * Retrieve a link + * + * In RSS1 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. + * + * @return string + */ + function getLink($offset = 0, $attribute = 'href', $params = false) + { + $links = $this->model->getElementsByTagName('link'); + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php new file mode 100755 index 000000000..75918beda --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS11Element.php @@ -0,0 +1,151 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * RSS1 Element class for XML_Feed_Parser + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS11Element.php,v 1.4 2006/06/30 17:41:56 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/* + * This class provides support for RSS 1.1 entries. It will usually be called by + * XML_Feed_Parser_RSS11 with which it shares many methods. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS11Element extends XML_Feed_Parser_RSS11 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS1 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'id' => array('Id'), + 'title' => array('Text'), + 'link' => array('Link'), + 'description' => array('Text'), # or dc:description + 'category' => array('Category'), + 'rights' => array('Text'), # dc:rights + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date'), # dc:date + 'content' => array('Content') + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS1. + * @var array + */ + protected $compatMap = array( + 'content' => array('content'), + 'updated' => array('lastBuildDate'), + 'published' => array('pubdate'), + 'subtitle' => array('description'), + 'updated' => array('date'), + 'author' => array('creator'), + 'contributor' => array('contributor') + ); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } + + /** + * If an rdf:about attribute is specified, return that as an ID + * + * There is no established way of showing an ID for an RSS1 entry. We will + * simulate it using the rdf:about attribute of the entry element. This cannot + * be relied upon for unique IDs but may prove useful. + * + * @return string|false + */ + function getId() + { + if ($this->model->attributes->getNamedItem('about')) { + return $this->model->attributes->getNamedItem('about')->nodeValue; + } + return false; + } + + /** + * Return the entry's content + * + * The official way to include full content in an RSS1 entry is to use + * the content module's element 'encoded'. Often, however, the 'description' + * element is used instead. We will offer that as a fallback. + * + * @return string|false + */ + function getContent() + { + $options = array('encoded', 'description'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length == 0) { + continue; + } + if ($test->item(0)->hasChildNodes()) { + $value = ''; + foreach ($test->item(0)->childNodes as $child) { + if ($child instanceof DOMText) { + $value .= $child->nodeValue; + } else { + $simple = simplexml_import_dom($child); + $value .= $simple->asXML(); + } + } + return $value; + } else if ($test->length > 0) { + return $test->item(0)->nodeValue; + } + } + return false; + } + + /** + * How RSS1.1 should support for enclosures is not clear. For now we will return + * false. + * + * @return false + */ + function getEnclosure() + { + return false; + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php new file mode 100755 index 000000000..8e36d5a9b --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS1Element.php @@ -0,0 +1,116 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * RSS1 Element class for XML_Feed_Parser + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS1Element.php,v 1.6 2006/06/30 17:41:56 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/* + * This class provides support for RSS 1.0 entries. It will usually be called by + * XML_Feed_Parser_RSS1 with which it shares many methods. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS1Element extends XML_Feed_Parser_RSS1 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS1 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'id' => array('Id'), + 'title' => array('Text'), + 'link' => array('Link'), + 'description' => array('Text'), # or dc:description + 'category' => array('Category'), + 'rights' => array('Text'), # dc:rights + 'creator' => array('Text'), # dc:creator + 'publisher' => array('Text'), # dc:publisher + 'contributor' => array('Text'), # dc:contributor + 'date' => array('Date'), # dc:date + 'content' => array('Content') + ); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS1. + * @var array + */ + protected $compatMap = array( + 'content' => array('content'), + 'updated' => array('lastBuildDate'), + 'published' => array('pubdate'), + 'subtitle' => array('description'), + 'updated' => array('date'), + 'author' => array('creator'), + 'contributor' => array('contributor') + ); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS1 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } + + /** + * If an rdf:about attribute is specified, return it as an ID + * + * There is no established way of showing an ID for an RSS1 entry. We will + * simulate it using the rdf:about attribute of the entry element. This cannot + * be relied upon for unique IDs but may prove useful. + * + * @return string|false + */ + function getId() + { + if ($this->model->attributes->getNamedItem('about')) { + return $this->model->attributes->getNamedItem('about')->nodeValue; + } + return false; + } + + /** + * How RSS1 should support for enclosures is not clear. For now we will return + * false. + * + * @return false + */ + function getEnclosure() + { + return false; + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php new file mode 100644 index 000000000..0936bd2f5 --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2.php @@ -0,0 +1,335 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Class representing feed-level data for an RSS2 feed + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS2.php,v 1.12 2008/03/08 18:16:45 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class handles RSS2 feeds. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS2 extends XML_Feed_Parser_Type +{ + /** + * The URI of the RelaxNG schema used to (optionally) validate the feed + * @var string + */ + private $relax = 'rss20.rnc'; + + /** + * We're likely to use XPath, so let's keep it global + * @var DOMXPath + */ + protected $xpath; + + /** + * The feed type we are parsing + * @var string + */ + public $version = 'RSS 2.0'; + + /** + * The class used to represent individual items + * @var string + */ + protected $itemClass = 'XML_Feed_Parser_RSS2Element'; + + /** + * The element containing entries + * @var string + */ + protected $itemElement = 'item'; + + /** + * Here we map those elements we're not going to handle individually + * to the constructs they are. The optional second parameter in the array + * tells the parser whether to 'fall back' (not apt. at the feed level) or + * fail if the element is missing. If the parameter is not set, the function + * will simply return false and leave it to the client to decide what to do. + * @var array + */ + protected $map = array( + 'ttl' => array('Text'), + 'pubDate' => array('Date'), + 'lastBuildDate' => array('Date'), + 'title' => array('Text'), + 'link' => array('Link'), + 'description' => array('Text'), + 'language' => array('Text'), + 'copyright' => array('Text'), + 'managingEditor' => array('Text'), + 'webMaster' => array('Text'), + 'category' => array('Text'), + 'generator' => array('Text'), + 'docs' => array('Text'), + 'ttl' => array('Text'), + 'image' => array('Image'), + 'skipDays' => array('skipDays'), + 'skipHours' => array('skipHours')); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'title' => array('title'), + 'rights' => array('copyright'), + 'updated' => array('lastBuildDate'), + 'subtitle' => array('description'), + 'date' => array('pubDate'), + 'author' => array('managingEditor')); + + protected $namespaces = array( + 'dc' => 'http://purl.org/rss/1.0/modules/dc/', + 'content' => 'http://purl.org/rss/1.0/modules/content/'); + + /** + * Our constructor does nothing more than its parent. + * + * @param DOMDocument $xml A DOM object representing the feed + * @param bool (optional) $string Whether or not to validate this feed + */ + function __construct(DOMDocument $model, $strict = false) + { + $this->model = $model; + + if ($strict) { + if (! $this->model->relaxNGValidate($this->relax)) { + throw new XML_Feed_Parser_Exception('Failed required validation'); + } + } + + $this->xpath = new DOMXPath($this->model); + foreach ($this->namespaces as $key => $value) { + $this->xpath->registerNamespace($key, $value); + } + $this->numberEntries = $this->count('item'); + } + + /** + * Retrieves an entry by ID, if the ID is specified with the guid element + * + * This is not really something that will work with RSS2 as it does not have + * clear restrictions on the global uniqueness of IDs. But we can emulate + * it by allowing access based on the 'guid' element. If DOMXPath::evaluate + * is available, we also use that to store a reference to the entry in the array + * used by getEntryByOffset so that method does not have to seek out the entry + * if it's requested that way. + * + * @param string $id any valid ID. + * @return XML_Feed_Parser_RSS2Element + */ + function getEntryById($id) + { + if (isset($this->idMappings[$id])) { + return $this->entries[$this->idMappings[$id]]; + } + + $entries = $this->xpath->query("//item[guid='$id']"); + if ($entries->length > 0) { + $entry = new $this->itemElement($entries->item(0), $this); + if (in_array('evaluate', get_class_methods($this->xpath))) { + $offset = $this->xpath->evaluate("count(preceding-sibling::item)", $entries->item(0)); + $this->entries[$offset] = $entry; + } + $this->idMappings[$id] = $entry; + return $entry; + } + } + + /** + * Get a category from the element + * + * The category element is a simple text construct which can occur any number + * of times. We allow access by offset or access to an array of results. + * + * @param string $call for compatibility with our overloading + * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array + * @return string|array|false + */ + function getCategory($call, $arguments = array()) + { + $categories = $this->model->getElementsByTagName('category'); + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $array = empty($arguments[1]) ? false : true; + if ($categories->length <= $offset) { + return false; + } + if ($array) { + $list = array(); + foreach ($categories as $category) { + array_push($list, $category->nodeValue); + } + return $list; + } + return $categories->item($offset)->nodeValue; + } + + /** + * Get details of the image associated with the feed. + * + * @return array|false an array simply containing the child elements + */ + protected function getImage() + { + $images = $this->xpath->query("//image"); + if ($images->length > 0) { + $image = $images->item(0); + $desc = $image->getElementsByTagName('description'); + $description = $desc->length ? $desc->item(0)->nodeValue : false; + $heigh = $image->getElementsByTagName('height'); + $height = $heigh->length ? $heigh->item(0)->nodeValue : false; + $widt = $image->getElementsByTagName('width'); + $width = $widt->length ? $widt->item(0)->nodeValue : false; + return array( + 'title' => $image->getElementsByTagName('title')->item(0)->nodeValue, + 'link' => $image->getElementsByTagName('link')->item(0)->nodeValue, + 'url' => $image->getElementsByTagName('url')->item(0)->nodeValue, + 'description' => $description, + 'height' => $height, + 'width' => $width); + } + return false; + } + + /** + * The textinput element is little used, but in the interests of + * completeness... + * + * @return array|false + */ + function getTextInput() + { + $inputs = $this->model->getElementsByTagName('input'); + if ($inputs->length > 0) { + $input = $inputs->item(0); + return array( + 'title' => $input->getElementsByTagName('title')->item(0)->value, + 'description' => + $input->getElementsByTagName('description')->item(0)->value, + 'name' => $input->getElementsByTagName('name')->item(0)->value, + 'link' => $input->getElementsByTagName('link')->item(0)->value); + } + return false; + } + + /** + * Utility function for getSkipDays and getSkipHours + * + * This is a general function used by both getSkipDays and getSkipHours. It simply + * returns an array of the values of the children of the appropriate tag. + * + * @param string $tagName The tag name (getSkipDays or getSkipHours) + * @return array|false + */ + protected function getSkips($tagName) + { + $hours = $this->model->getElementsByTagName($tagName); + if ($hours->length == 0) { + return false; + } + $skipHours = array(); + foreach($hours->item(0)->childNodes as $hour) { + if ($hour instanceof DOMElement) { + array_push($skipHours, $hour->nodeValue); + } + } + return $skipHours; + } + + /** + * Retrieve skipHours data + * + * The skiphours element provides a list of hours on which this feed should + * not be checked. We return an array of those hours (integers, 24 hour clock) + * + * @return array + */ + function getSkipHours() + { + return $this->getSkips('skipHours'); + } + + /** + * Retrieve skipDays data + * + * The skipdays element provides a list of days on which this feed should + * not be checked. We return an array of those days. + * + * @return array + */ + function getSkipDays() + { + return $this->getSkips('skipDays'); + } + + /** + * Return content of the little-used 'cloud' element + * + * The cloud element is rarely used. It is designed to provide some details + * of a location to update the feed. + * + * @return array an array of the attributes of the element + */ + function getCloud() + { + $cloud = $this->model->getElementsByTagName('cloud'); + if ($cloud->length == 0) { + return false; + } + $cloudData = array(); + foreach ($cloud->item(0)->attributes as $attribute) { + $cloudData[$attribute->name] = $attribute->value; + } + return $cloudData; + } + + /** + * Get link URL + * + * In RSS2 a link is a text element but in order to ensure that we resolve + * URLs properly we have a special function for them. We maintain the + * parameter used by the atom getLink method, though we only use the offset + * parameter. + * + * @param int $offset The position of the link within the feed. Starts from 0 + * @param string $attribute The attribute of the link element required + * @param array $params An array of other parameters. Not used. + * @return string + */ + function getLink($offset, $attribute = 'href', $params = array()) + { + $xPath = new DOMXPath($this->model); + $links = $xPath->query('//link'); + + if ($links->length <= $offset) { + return false; + } + $link = $links->item($offset); + return $this->addBase($link->nodeValue, $link); + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php new file mode 100755 index 000000000..6edf910dc --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/RSS2Element.php @@ -0,0 +1,171 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Class representing entries in an RSS2 feed. + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: RSS2Element.php,v 1.11 2006/07/26 21:18:47 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This class provides support for RSS 2.0 entries. It will usually be + * called by XML_Feed_Parser_RSS2 with which it shares many methods. + * + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + * @package XML_Feed_Parser + */ +class XML_Feed_Parser_RSS2Element extends XML_Feed_Parser_RSS2 +{ + /** + * This will be a reference to the parent object for when we want + * to use a 'fallback' rule + * @var XML_Feed_Parser_RSS2 + */ + protected $parent; + + /** + * Our specific element map + * @var array + */ + protected $map = array( + 'title' => array('Text'), + 'guid' => array('Guid'), + 'description' => array('Text'), + 'author' => array('Text'), + 'comments' => array('Text'), + 'enclosure' => array('Enclosure'), + 'pubDate' => array('Date'), + 'source' => array('Source'), + 'link' => array('Text'), + 'content' => array('Content')); + + /** + * Here we map some elements to their atom equivalents. This is going to be + * quite tricky to pull off effectively (and some users' methods may vary) + * but is worth trying. The key is the atom version, the value is RSS2. + * @var array + */ + protected $compatMap = array( + 'id' => array('guid'), + 'updated' => array('lastBuildDate'), + 'published' => array('pubdate'), + 'guidislink' => array('guid', 'ispermalink'), + 'summary' => array('description')); + + /** + * Store useful information for later. + * + * @param DOMElement $element - this item as a DOM element + * @param XML_Feed_Parser_RSS2 $parent - the feed of which this is a member + */ + function __construct(DOMElement $element, $parent, $xmlBase = '') + { + $this->model = $element; + $this->parent = $parent; + } + + /** + * Get the value of the guid element, if specified + * + * guid is the closest RSS2 has to atom's ID. It is usually but not always a + * URI. The one attribute that RSS2 can posess is 'ispermalink' which specifies + * whether the guid is itself dereferencable. Use of guid is not obligatory, + * but is advisable. To get the guid you would call $item->id() (for atom + * compatibility) or $item->guid(). To check if this guid is a permalink call + * $item->guid("ispermalink"). + * + * @param string $method - the method name being called + * @param array $params - parameters required + * @return string the guid or value of ispermalink + */ + protected function getGuid($method, $params) + { + $attribute = (isset($params[0]) and $params[0] == 'ispermalink') ? + true : false; + $tag = $this->model->getElementsByTagName('guid'); + if ($tag->length > 0) { + if ($attribute) { + if ($tag->hasAttribute("ispermalink")) { + return $tag->getAttribute("ispermalink"); + } + } + return $tag->item(0)->nodeValue; + } + return false; + } + + /** + * Access details of file enclosures + * + * The RSS2 spec is ambiguous as to whether an enclosure element must be + * unique in a given entry. For now we will assume it needn't, and allow + * for an offset. + * + * @param string $method - the method being called + * @param array $parameters - we expect the first of these to be our offset + * @return array|false + */ + protected function getEnclosure($method, $parameters) + { + $encs = $this->model->getElementsByTagName('enclosure'); + $offset = isset($parameters[0]) ? $parameters[0] : 0; + if ($encs->length > $offset) { + try { + if (! $encs->item($offset)->hasAttribute('url')) { + return false; + } + $attrs = $encs->item($offset)->attributes; + return array( + 'url' => $attrs->getNamedItem('url')->value, + 'length' => $attrs->getNamedItem('length')->value, + 'type' => $attrs->getNamedItem('type')->value); + } catch (Exception $e) { + return false; + } + } + return false; + } + + /** + * Get the entry source if specified + * + * source is an optional sub-element of item. Like atom:source it tells + * us about where the entry came from (eg. if it's been copied from another + * feed). It is not a rich source of metadata in the same way as atom:source + * and while it would be good to maintain compatibility by returning an + * XML_Feed_Parser_RSS2 element, it makes a lot more sense to return an array. + * + * @return array|false + */ + protected function getSource() + { + $get = $this->model->getElementsByTagName('source'); + if ($get->length) { + $source = $get->item(0); + $array = array( + 'content' => $source->nodeValue); + foreach ($source->attributes as $attribute) { + $array[$attribute->name] = $attribute->value; + } + return $array; + } + return false; + } +} + +?>
\ No newline at end of file diff --git a/plugins/FeedSub/extlib/XML/Feed/Parser/Type.php b/plugins/FeedSub/extlib/XML/Feed/Parser/Type.php new file mode 100644 index 000000000..75052619b --- /dev/null +++ b/plugins/FeedSub/extlib/XML/Feed/Parser/Type.php @@ -0,0 +1,467 @@ +<?php +/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */ + +/** + * Abstract class providing common methods for XML_Feed_Parser feeds. + * + * PHP versions 5 + * + * LICENSE: This source file is subject to version 3.0 of the PHP license + * that is available through the world-wide-web at the following URI: + * http://www.php.net/license/3_0.txt. If you did not receive a copy of + * the PHP License and are unable to obtain it through the web, please + * send a note to license@php.net so we can mail you a copy immediately. + * + * @category XML + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @copyright 2005 James Stewart <james@jystewart.net> + * @license http://www.gnu.org/copyleft/lesser.html GNU LGPL 2.1 + * @version CVS: $Id: Type.php,v 1.25 2008/03/08 18:39:09 jystewart Exp $ + * @link http://pear.php.net/package/XML_Feed_Parser/ + */ + +/** + * This abstract class provides some general methods that are likely to be + * implemented exactly the same way for all feed types. + * + * @package XML_Feed_Parser + * @author James Stewart <james@jystewart.net> + * @version Release: 1.0.3 + */ +abstract class XML_Feed_Parser_Type +{ + /** + * Where we store our DOM object for this feed + * @var DOMDocument + */ + public $model; + + /** + * For iteration we'll want a count of the number of entries + * @var int + */ + public $numberEntries; + + /** + * Where we store our entry objects once instantiated + * @var array + */ + public $entries = array(); + + /** + * Store mappings between entry IDs and their position in the feed + */ + public $idMappings = array(); + + /** + * Proxy to allow use of element names as method names + * + * We are not going to provide methods for every entry type so this + * function will allow for a lot of mapping. We rely pretty heavily + * on this to handle our mappings between other feed types and atom. + * + * @param string $call - the method attempted + * @param array $arguments - arguments to that method + * @return mixed + */ + function __call($call, $arguments = array()) + { + if (! is_array($arguments)) { + $arguments = array(); + } + + if (isset($this->compatMap[$call])) { + $tempMap = $this->compatMap; + $tempcall = array_pop($tempMap[$call]); + if (! empty($tempMap)) { + $arguments = array_merge($arguments, $tempMap[$call]); + } + $call = $tempcall; + } + + /* To be helpful, we allow a case-insensitive search for this method */ + if (! isset($this->map[$call])) { + foreach (array_keys($this->map) as $key) { + if (strtoupper($key) == strtoupper($call)) { + $call = $key; + break; + } + } + } + + if (empty($this->map[$call])) { + return false; + } + + $method = 'get' . $this->map[$call][0]; + if ($method == 'getLink') { + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $attribute = empty($arguments[1]) ? 'href' : $arguments[1]; + $params = isset($arguments[2]) ? $arguments[2] : array(); + return $this->getLink($offset, $attribute, $params); + } + if (method_exists($this, $method)) { + return $this->$method($call, $arguments); + } + + return false; + } + + /** + * Proxy to allow use of element names as attribute names + * + * For many elements variable-style access will be desirable. This function + * provides for that. + * + * @param string $value - the variable required + * @return mixed + */ + function __get($value) + { + return $this->__call($value, array()); + } + + /** + * Utility function to help us resolve xml:base values + * + * We have other methods which will traverse the DOM and work out the different + * xml:base declarations we need to be aware of. We then need to combine them. + * If a declaration starts with a protocol then we restart the string. If it + * starts with a / then we add on to the domain name. Otherwise we simply tag + * it on to the end. + * + * @param string $base - the base to add the link to + * @param string $link + */ + function combineBases($base, $link) + { + if (preg_match('/^[A-Za-z]+:\/\//', $link)) { + return $link; + } else if (preg_match('/^\//', $link)) { + /* Extract domain and suffix link to that */ + preg_match('/^([A-Za-z]+:\/\/.*)?\/*/', $base, $results); + $firstLayer = $results[0]; + return $firstLayer . "/" . $link; + } else if (preg_match('/^\.\.\//', $base)) { + /* Step up link to find place to be */ + preg_match('/^((\.\.\/)+)(.*)$/', $link, $bases); + $suffix = $bases[3]; + $count = preg_match_all('/\.\.\//', $bases[1], $steps); + $url = explode("/", $base); + for ($i = 0; $i <= $count; $i++) { + array_pop($url); + } + return implode("/", $url) . "/" . $suffix; + } else if (preg_match('/^(?!\/$)/', $base)) { + $base = preg_replace('/(.*\/).*$/', '$1', $base) ; + return $base . $link; + } else { + /* Just stick it on the end */ + return $base . $link; + } + } + + /** + * Determine whether we need to apply our xml:base rules + * + * Gets us the xml:base data and then processes that with regard + * to our current link. + * + * @param string + * @param DOMElement + * @return string + */ + function addBase($link, $element) + { + if (preg_match('/^[A-Za-z]+:\/\//', $link)) { + return $link; + } + + return $this->combineBases($element->baseURI, $link); + } + + /** + * Get an entry by its position in the feed, starting from zero + * + * As well as allowing the items to be iterated over we want to allow + * users to be able to access a specific entry. This is one of two ways of + * doing that, the other being by ID. + * + * @param int $offset + * @return XML_Feed_Parser_RSS1Element + */ + function getEntryByOffset($offset) + { + if (! isset($this->entries[$offset])) { + $entries = $this->model->getElementsByTagName($this->itemElement); + if ($entries->length > $offset) { + $xmlBase = $entries->item($offset)->baseURI; + $this->entries[$offset] = new $this->itemClass( + $entries->item($offset), $this, $xmlBase); + if ($id = $this->entries[$offset]->id) { + $this->idMappings[$id] = $this->entries[$offset]; + } + } else { + throw new XML_Feed_Parser_Exception('No entries found'); + } + } + + return $this->entries[$offset]; + } + + /** + * Return a date in seconds since epoch. + * + * Get a date construct. We use PHP's strtotime to return it as a unix datetime, which + * is the number of seconds since 1970-01-01 00:00:00. + * + * @link http://php.net/strtotime + * @param string $method The name of the date construct we want + * @param array $arguments Included for compatibility with our __call usage + * @return int|false datetime + */ + protected function getDate($method, $arguments) + { + $time = $this->model->getElementsByTagName($method); + if ($time->length == 0 || empty($time->item(0)->nodeValue)) { + return false; + } + return strtotime($time->item(0)->nodeValue); + } + + /** + * Get a text construct. + * + * @param string $method The name of the text construct we want + * @param array $arguments Included for compatibility with our __call usage + * @return string + */ + protected function getText($method, $arguments = array()) + { + $tags = $this->model->getElementsByTagName($method); + if ($tags->length > 0) { + $value = $tags->item(0)->nodeValue; + return $value; + } + return false; + } + + /** + * Apply various rules to retrieve category data. + * + * There is no single way of declaring a category in RSS1/1.1 as there is in RSS2 + * and Atom. Instead the usual approach is to use the dublin core namespace to + * declare categories. For example delicious use both: + * <dc:subject>PEAR</dc:subject> and: <taxo:topics><rdf:Bag> + * <rdf:li resource="http://del.icio.us/tag/PEAR" /></rdf:Bag></taxo:topics> + * to declare a categorisation of 'PEAR'. + * + * We need to be sensitive to this where possible. + * + * @param string $call for compatibility with our overloading + * @param array $arguments - arg 0 is the offset, arg 1 is whether to return as array + * @return string|array|false + */ + protected function getCategory($call, $arguments) + { + $categories = $this->model->getElementsByTagName('subject'); + $offset = empty($arguments[0]) ? 0 : $arguments[0]; + $array = empty($arguments[1]) ? false : true; + if ($categories->length <= $offset) { + return false; + } + if ($array) { + $list = array(); + foreach ($categories as $category) { + array_push($list, $category->nodeValue); + } + return $list; + } + return $categories->item($offset)->nodeValue; + } + + /** + * Count occurrences of an element + * + * This function will tell us how many times the element $type + * appears at this level of the feed. + * + * @param string $type the element we want to get a count of + * @return int + */ + protected function count($type) + { + if ($tags = $this->model->getElementsByTagName($type)) { + return $tags->length; + } + return 0; + } + + /** + * Part of our xml:base processing code + * + * We need a couple of methods to access XHTML content stored in feeds. + * This is because we dereference all xml:base references before returning + * the element. This method handles the attributes. + * + * @param DOMElement $node The DOM node we are iterating over + * @return string + */ + function processXHTMLAttributes($node) { + $return = ''; + foreach ($node->attributes as $attribute) { + if ($attribute->name == 'src' or $attribute->name == 'href') { + $attribute->value = $this->addBase(htmlentities($attribute->value, NULL, 'utf-8'), $attribute); + } + if ($attribute->name == 'base') { + continue; + } + $return .= $attribute->name . '="' . htmlentities($attribute->value, NULL, 'utf-8') .'" '; + } + if (! empty($return)) { + return ' ' . trim($return); + } + return ''; + } + + /** + * Convert HTML entities based on the current character set. + * + * @param String + * @return String + */ + function processEntitiesForNodeValue($node) + { + if (function_exists('iconv')) { + $current_encoding = $node->ownerDocument->encoding; + $value = iconv($current_encoding, 'UTF-8', $node->nodeValue); + } else if ($current_encoding == 'iso-8859-1') { + $value = utf8_encode($node->nodeValue); + } else { + $value = $node->nodeValue; + } + + $decoded = html_entity_decode($value, NULL, 'UTF-8'); + return htmlentities($decoded, NULL, 'UTF-8'); + } + + /** + * Part of our xml:base processing code + * + * We need a couple of methods to access XHTML content stored in feeds. + * This is because we dereference all xml:base references before returning + * the element. This method recurs through the tree descending from the node + * and builds our string. + * + * @param DOMElement $node The DOM node we are processing + * @return string + */ + function traverseNode($node) + { + $content = ''; + + /* Add the opening of this node to the content */ + if ($node instanceof DOMElement) { + $content .= '<' . $node->tagName . + $this->processXHTMLAttributes($node) . '>'; + } + + /* Process children */ + if ($node->hasChildNodes()) { + foreach ($node->childNodes as $child) { + $content .= $this->traverseNode($child); + } + } + + if ($node instanceof DOMText) { + $content .= $this->processEntitiesForNodeValue($node); + } + + /* Add the closing of this node to the content */ + if ($node instanceof DOMElement) { + $content .= '</' . $node->tagName . '>'; + } + + return $content; + } + + /** + * Get content from RSS feeds (atom has its own implementation) + * + * The official way to include full content in an RSS1 entry is to use + * the content module's element 'encoded', and RSS2 feeds often duplicate that. + * Often, however, the 'description' element is used instead. We will offer that + * as a fallback. Atom uses its own approach and overrides this method. + * + * @return string|false + */ + protected function getContent() + { + $options = array('encoded', 'description'); + foreach ($options as $element) { + $test = $this->model->getElementsByTagName($element); + if ($test->length == 0) { + continue; + } + if ($test->item(0)->hasChildNodes()) { + $value = ''; + foreach ($test->item(0)->childNodes as $child) { + if ($child instanceof DOMText) { + $value .= $child->nodeValue; + } else { + $simple = simplexml_import_dom($child); + $value .= $simple->asXML(); + } + } + return $value; + } else if ($test->length > 0) { + return $test->item(0)->nodeValue; + } + } + return false; + } + + /** + * Checks if this element has a particular child element. + * + * @param String + * @param Integer + * @return bool + **/ + function hasKey($name, $offset = 0) + { + $search = $this->model->getElementsByTagName($name); + return $search->length > $offset; + } + + /** + * Return an XML serialization of the feed, should it be required. Most + * users however, will already have a serialization that they used when + * instantiating the object. + * + * @return string XML serialization of element + */ + function __toString() + { + $simple = simplexml_import_dom($this->model); + return $simple->asXML(); + } + + /** + * Get directory holding RNG schemas. Method is based on that + * found in Contact_AddressBook. + * + * @return string PEAR data directory. + * @access public + * @static + */ + static function getSchemaDir() + { + require_once 'PEAR/Config.php'; + $config = new PEAR_Config; + return $config->get('data_dir') . '/XML_Feed_Parser/schemas'; + } +} + +?>
\ No newline at end of file |