XML_Feed_Parser
[ class tree: XML_Feed_Parser ] [ index: XML_Feed_Parser ] [ all elements ]

Source for file Atom.php

Documentation is available at Atom.php

  1. <?php
  2. /* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
  3.  
  4. /**
  5.  * Atom feed class for XML_Feed_Parser
  6.  *
  7.  * PHP versions 5
  8.  *
  9.  * LICENSE: This source file is subject to version 3.0 of the PHP license
  10.  * that is available through the world-wide-web at the following URI:
  11.  * http://www.php.net/license/3_0.txt.  If you did not receive a copy of
  12.  * the PHP License and are unable to obtain it through the web, please
  13.  * send a note to license@php.net so we can mail you a copy immediately.
  14.  *
  15.  * @category   XML
  16.  * @package    XML_Feed_Parser
  17.  * @author     James Stewart <james@jystewart.net>
  18.  * @copyright  2005 James Stewart <james@jystewart.net>
  19.  * @license    http://www.gnu.org/copyleft/lesser.html  GNU LGPL 2.1
  20.  * @version    CVS: $Id: Atom.php,v 1.29 2008/03/30 22:00:36 jystewart Exp $
  21.  * @link       http://pear.php.net/package/XML_Feed_Parser/
  22. */
  23.  
  24. /**
  25.  * This is the class that determines how we manage Atom 1.0 feeds
  26.  * 
  27.  * How we deal with constructs:
  28.  *  date - return as unix datetime for use with the 'date' function unless specified otherwise
  29.  *  text - return as is. optional parameter will give access to attributes
  30.  *  person - defaults to name, but parameter based access
  31.  *
  32.  * @author    James Stewart <james@jystewart.net>
  33.  * @version    Release: 1.0.3
  34.  * @package XML_Feed_Parser
  35.  */
  36. {
  37.     /**
  38.      * The URI of the RelaxNG schema used to (optionally) validate the feed
  39.      * @var string 
  40.      */
  41.     private $relax 'atom.rnc';
  42.  
  43.     /**
  44.      * We're likely to use XPath, so let's keep it global
  45.      * @var DOMXPath 
  46.      */
  47.     public $xpath;
  48.  
  49.     /**
  50.      * When performing XPath queries we will use this prefix
  51.      * @var string 
  52.      */
  53.     private $xpathPrefix '//';
  54.  
  55.     /**
  56.      * The feed type we are parsing
  57.      * @var string 
  58.      */
  59.     public $version = 'Atom 1.0';
  60.  
  61.     /** 
  62.      * The class used to represent individual items
  63.      * @var string 
  64.      */
  65.     protected $itemClass = 'XML_Feed_Parser_AtomElement';
  66.     
  67.     /** 
  68.      * The element containing entries
  69.      * @var string 
  70.      */
  71.     protected $itemElement = 'entry';
  72.  
  73.     /**
  74.      * Here we map those elements we're not going to handle individually
  75.      * to the constructs they are. The optional second parameter in the array
  76.      * tells the parser whether to 'fall back' (not apt. at the feed level) or
  77.      * fail if the element is missing. If the parameter is not set, the function
  78.      * will simply return false and leave it to the client to decide what to do.
  79.      * @var array 
  80.      */
  81.     protected $map = array(
  82.         'author' => array('Person'),
  83.         'contributor' => array('Person'),
  84.         'icon' => array('Text'),
  85.         'logo' => array('Text'),
  86.         'id' => array('Text''fail'),
  87.         'rights' => array('Text'),
  88.         'subtitle' => array('Text'),
  89.         'title' => array('Text''fail'),
  90.         'updated' => array('Date''fail'),
  91.         'link' => array('Link'),
  92.         'generator' => array('Text'),
  93.         'category' => array('Category'));
  94.  
  95.     /**
  96.      * Here we provide a few mappings for those very special circumstances in
  97.      * which it makes sense to map back to the RSS2 spec. Key is RSS2 version
  98.      * value is an array consisting of the equivalent in atom and any attributes
  99.      * needed to make the mapping.
  100.      * @var array 
  101.      */
  102.     protected $compatMap = array(
  103.         'guid' => array('id'),
  104.         'links' => array('link'),
  105.         'tags' => array('category'),
  106.         'contributors' => array('contributor'));
  107.  
  108.     /**
  109.      * Our constructor does nothing more than its parent.
  110.      * 
  111.      * @param    DOMDocument    $xml    A DOM object representing the feed
  112.      * @param    bool (optional) $string    Whether or not to validate this feed
  113.      */
  114.     function __construct(DOMDocument $model$strict = false)
  115.     {
  116.         $this->model = $model;
  117.  
  118.         if ($strict{
  119.             if ($this->model->relaxNGValidateSource($this->relax)) {
  120.                 throw new XML_Feed_Parser_Exception('Failed required validation');
  121.             }
  122.         }
  123.  
  124.         $this->xpath = new DOMXPath($this->model);
  125.         $this->xpath->registerNamespace('atom''http://www.w3.org/2005/Atom');
  126.         $this->numberEntries = $this->count('entry');
  127.     }
  128.  
  129.     /**
  130.      * Implement retrieval of an entry based on its ID for atom feeds.
  131.      *
  132.      * This function uses XPath to get the entry based on its ID. If DOMXPath::evaluate
  133.      * is available, we also use that to store a reference to the entry in the array
  134.      * used by getEntryByOffset so that method does not have to seek out the entry
  135.      * if it's requested that way.
  136.      * 
  137.      * @param    string    $id    any valid Atom ID.
  138.      * @return    XML_Feed_Parser_AtomElement 
  139.      */
  140.     function getEntryById($id)
  141.     {
  142.         if (isset($this->idMappings[$id])) {
  143.             return $this->entries[$this->idMappings[$id]];
  144.         }
  145.  
  146.         $entries $this->xpath->query("//atom:entry[atom:id='$id']");
  147.  
  148.         if ($entries->length > 0{
  149.             $xmlBase $entries->item(0)->baseURI;
  150.             $entry = new $this->itemClass($entries->item(0)$this$xmlBase);
  151.             
  152.             if (in_array('evaluate'get_class_methods($this->xpath))) {
  153.                 $offset $this->xpath->evaluate("count(preceding-sibling::atom:entry)"$entries->item(0));
  154.                 $this->entries[$offset$entry;
  155.             }
  156.  
  157.             $this->idMappings[$id$entry;
  158.  
  159.             return $entry;
  160.         }
  161.         
  162.     }
  163.  
  164.     /**
  165.      * Retrieves data from a person construct.
  166.      *
  167.      * Get a person construct. We default to the 'name' element but allow
  168.      * access to any of the elements.
  169.      * 
  170.      * @param    string    $method    The name of the person construct we want
  171.      * @param    array     $arguments    An array which we hope gives a 'param'
  172.      * @return    string|false
  173.      */
  174.     protected function getPerson($method$arguments)
  175.     {
  176.         $offset = empty($arguments[0]? 0 : $arguments[0];
  177.         $parameter = empty($arguments[1]['param']'name' $arguments[1]['param'];
  178.         $section $this->model->getElementsByTagName($method);
  179.         
  180.         if ($parameter == 'url'{
  181.             $parameter 'uri';
  182.         }
  183.  
  184.         if ($section->length <= $offset{
  185.             return false;
  186.         }
  187.  
  188.         $param $section->item($offset)->getElementsByTagName($parameter);
  189.         if ($param->length == 0{
  190.             return false;
  191.         }
  192.         return $param->item(0)->nodeValue;
  193.     }
  194.  
  195.     /**
  196.      * Retrieves an element's content where that content is a text construct.
  197.      *
  198.      * Get a text construct. When calling this method, the two arguments
  199.      * allowed are 'offset' and 'attribute', so $parser->subtitle() would
  200.      * return the content of the element, while $parser->subtitle(false, 'type')
  201.      * would return the value of the type attribute.
  202.      *
  203.      * @todo    Clarify overlap with getContent()
  204.      * @param    string    $method    The name of the text construct we want
  205.      * @param    array     $arguments    An array which we hope gives a 'param'
  206.      * @return    string 
  207.      */
  208.     protected function getText($method$arguments)
  209.     {
  210.         $offset = empty($arguments[0]? 0: $arguments[0];
  211.         $attribute = empty($arguments[1]? false : $arguments[1];
  212.         $tags $this->model->getElementsByTagName($method);
  213.  
  214.         if ($tags->length <= $offset{
  215.             return false;
  216.         }
  217.  
  218.         $content $tags->item($offset);
  219.  
  220.         if ($content->hasAttribute('type')) {
  221.             $content->setAttribute('type''text');
  222.         }
  223.         $type $content->getAttribute('type');
  224.  
  225.         if (empty($attributeand 
  226.             ($method == 'generator' and $attribute == 'name')) {
  227.             if ($content->hasAttribute($attribute)) {
  228.                 return $content->getAttribute($attribute);
  229.             else if ($attribute == 'href' and $content->hasAttribute('uri')) {
  230.                 return $content->getAttribute('uri');
  231.             }
  232.             return false;
  233.         }
  234.  
  235.         return $this->parseTextConstruct($content);
  236.     }
  237.     
  238.     /**
  239.      * Extract content appropriately from atom text constructs
  240.      *
  241.      * Because of different rules applied to the content element and other text
  242.      * constructs, they are deployed as separate functions, but they share quite
  243.      * a bit of processing. This method performs the core common process, which is
  244.      * to apply the rules for different mime types in order to extract the content.
  245.      *
  246.      * @param   DOMNode $content    the text construct node to be parsed
  247.      * @return String 
  248.      * @author James Stewart
  249.      ***/
  250.     protected function parseTextConstruct(DOMNode $content)
  251.     {
  252.         if ($content->hasAttribute('type')) {
  253.             $type $content->getAttribute('type');
  254.         else {
  255.             $type 'text';
  256.         }
  257.  
  258.         if (strpos($type'text/'=== 0{
  259.             $type 'text';
  260.         }
  261.  
  262.         switch ($type{
  263.             case 'text':
  264.             case 'html':
  265.                 return $content->textContent;
  266.                 break;
  267.             case 'xhtml':
  268.                 $container $content->getElementsByTagName('div');
  269.                 if ($container->length == 0{
  270.                     return false;
  271.                 }
  272.                 $contents $container->item(0);
  273.                 if ($contents->hasChildNodes()) {
  274.                     /* Iterate through, applying xml:base and store the result */
  275.                     $result '';
  276.                     foreach ($contents->childNodes as $node{
  277.                         $result .= $this->traverseNode($node);
  278.                     }
  279.                     return $result;
  280.                 }
  281.                 break;
  282.             case preg_match('@^[a-zA-Z]+/[a-zA-Z+]*xml@i'$type> 0:
  283.                 return $content;
  284.                 break;
  285.             case 'application/octet-stream':
  286.             default:
  287.                 return base64_decode(trim($content->nodeValue));
  288.                 break;
  289.         }
  290.         return false;
  291.     }
  292.     /**
  293.      * Get a category from the entry.
  294.      *
  295.      * A feed or entry can have any number of categories. A category can have the
  296.      * attributes term, scheme and label.
  297.      * 
  298.      * @param    string    $method    The name of the text construct we want
  299.      * @param    array     $arguments    An array which we hope gives a 'param'
  300.      * @return    string 
  301.      */
  302.     function getCategory($method$arguments)
  303.     {
  304.         $offset = empty($arguments[0]? 0: $arguments[0];
  305.         $attribute = empty($arguments[1]'term' $arguments[1];
  306.         $categories $this->model->getElementsByTagName('category');
  307.         if ($categories->length <= $offset{
  308.             $category $categories->item($offset);
  309.             if ($category->hasAttribute($attribute)) {
  310.                 return $category->getAttribute($attribute);
  311.             }
  312.         }
  313.         return false;
  314.     }
  315.  
  316.     /**
  317.      * This element must be present at least once with rel="feed". This element may be
  318.      * present any number of further times so long as there is no clash. If no 'rel' is
  319.      * present and we're asked for one, we follow the example of the Universal Feed
  320.      * Parser and presume 'alternate'.
  321.      *
  322.      * @param    int    $offset    the position of the link within the container
  323.      * @param    string    $attribute    the attribute name required
  324.      * @param    array     an array of attributes to search by
  325.      * @return    string    the value of the attribute
  326.      */
  327.     function getLink($offset = 0$attribute 'href'$params = false)
  328.     {
  329.         if (is_array($paramsand !empty($params)) {
  330.             $terms = array();
  331.             $alt_predicate '';
  332.             $other_predicate '';
  333.  
  334.             foreach ($params as $key => $value{
  335.                 if ($key == 'rel' && $value == 'alternate'{
  336.                     $alt_predicate '[not(@rel) or @rel="alternate"]';
  337.                 else {
  338.                     $terms[= "@$key='$value'";
  339.                 }
  340.             }
  341.             if (!empty($terms)) {
  342.                 $other_predicate '[' join(' and '$terms']';
  343.             }
  344.             $query =  $this->xpathPrefix 'atom:link' $alt_predicate $other_predicate;
  345.             $links $this->xpath->query($query);
  346.         else {
  347.             $links $this->model->getElementsByTagName('link');
  348.         }
  349.         if ($links->length > $offset{
  350.             if ($links->item($offset)->hasAttribute($attribute)) {
  351.                 $value $links->item($offset)->getAttribute($attribute);
  352.                 if ($attribute == 'href'{
  353.                     $value $this->addBase($value$links->item($offset));
  354.                 }
  355.                 return $value;
  356.             else if ($attribute == 'rel'{
  357.                 return 'alternate';
  358.             }
  359.         }
  360.         return false;
  361.     }
  362. }
  363.  
  364. ?>

Documentation generated on Wed, 19 Nov 2008 08:30:06 -0500 by phpDocumentor 1.4.0. PEAR Logo Copyright © PHP Group 2004.