Joomla Platform  13.1
Documentation des API du framework Joomla Platform
 Tout Classes Espaces de nommage Fichiers Fonctions Variables Pages
parser.php
Aller à la documentation de ce fichier.
1 <?php
2 /**
3  * @package Joomla.Platform
4  * @subpackage Feed
5  *
6  * @copyright Copyright (C) 2005 - 2013 Open Source Matters, Inc. All rights reserved.
7  * @license GNU General Public License version 2 or later; see LICENSE
8  */
9 
10 defined('JPATH_PLATFORM') or die;
11 
12 /**
13  * Feed Parser class.
14  *
15  * @package Joomla.Platform
16  * @subpackage Feed
17  * @since 12.3
18  */
19 abstract class JFeedParser
20 {
21  /**
22  * The feed element name for the entry elements.
23  *
24  * @var string
25  * @since 12.3
26  */
27  protected $entryElementName = 'entry';
28 
29  /**
30  * Array of JFeedParserNamespace objects
31  *
32  * @var array
33  * @since 12.3
34  */
35  protected $namespaces = array();
36 
37  /**
38  * The XMLReader stream object for the feed.
39  *
40  * @var XMLReader
41  * @since 12.3
42  */
43  protected $stream;
44 
45  /**
46  * Constructor.
47  *
48  * @param XMLReader $stream The XMLReader stream object for the feed.
49  *
50  * @since 12.3
51  */
52  public function __construct(XMLReader $stream)
53  {
54  $this->stream = $stream;
55  }
56 
57  /**
58  * Method to parse the feed into a JFeed object.
59  *
60  * @return JFeed
61  *
62  * @since 12.3
63  */
64  public function parse()
65  {
66  $feed = new JFeed;
67 
68  // Detect the feed version.
69  $this->initialise();
70 
71  // Let's get this party started...
72  do
73  {
74  // Expand the element for processing.
75  $el = new SimpleXMLElement($this->stream->readOuterXml());
76 
77  // Get the list of namespaces used within this element.
78  $ns = $el->getNamespaces(true);
79 
80  // Get an array of available namespace objects for the element.
81  $namespaces = array();
82 
83  foreach ($ns as $prefix => $uri)
84  {
85  // Ignore the empty namespace prefix.
86  if (empty($prefix))
87  {
88  continue;
89  }
90 
91  // Get the necessary namespace objects for the element.
92  $namespace = $this->fetchNamespace($prefix);
93 
94  if ($namespace)
95  {
96  $namespaces[] = $namespace;
97  }
98  }
99 
100  // Process the element.
101  $this->processElement($feed, $el, $namespaces);
102 
103  // Skip over this element's children since it has been processed.
104  $this->moveToClosingElement();
105  }
106  while ($this->moveToNextElement());
107 
108  return $feed;
109  }
110 
111  /**
112  * Method to register a namespace handler object.
113  *
114  * @param string $prefix The XML namespace prefix for which to register the namespace object.
115  * @param JFeedParserNamespace $namespace The namespace object to register.
116  *
117  * @return JFeed
118  *
119  * @since 12.3
120  */
121  public function registerNamespace($prefix, JFeedParserNamespace $namespace)
122  {
123  $this->namespaces[$prefix] = $namespace;
124 
125  return $this;
126  }
127 
128  /**
129  * Method to initialise the feed for parsing. If child parsers need to detect versions or other
130  * such things this is where you'll want to implement that logic.
131  *
132  * @return void
133  *
134  * @since 12.3
135  */
136  abstract protected function initialise();
137 
138  /**
139  * Method to parse a specific feed element.
140  *
141  * @param JFeed $feed The JFeed object being built from the parsed feed.
142  * @param SimpleXMLElement $el The current XML element object to handle.
143  * @param array $namespaces The array of relevant namespace objects to process for the element.
144  *
145  * @return void
146  *
147  * @since 12.3
148  */
149  protected function processElement(JFeed $feed, SimpleXMLElement $el, array $namespaces)
150  {
151  // Build the internal method name.
152  $method = 'handle' . ucfirst($el->getName());
153 
154  // If we are dealing with an item then it is feed entry time.
155  if ($el->getName() == $this->entryElementName)
156  {
157  // Create a new feed entry for the item.
158  $entry = new JFeedEntry;
159 
160  // First call the internal method.
161  $this->processFeedEntry($entry, $el);
162 
163  foreach ($namespaces as $namespace)
164  {
165  if ($namespace instanceof JFeedParserNamespace)
166  {
167  $namespace->processElementForFeedEntry($entry, $el);
168  }
169  }
170 
171  // Add the new entry to the feed.
172  $feed->addEntry($entry);
173  }
174  // Otherwise we treat it like any other element.
175  else
176  {
177  // First call the internal method.
178  if (is_callable(array($this, $method)))
179  {
180  $this->$method($feed, $el);
181  }
182 
183  foreach ($namespaces as $namespace)
184  {
185  if ($namespace instanceof JFeedParserNamespace)
186  {
187  $namespace->processElementForFeed($feed, $el);
188  }
189  }
190  }
191  }
192 
193  /**
194  * Method to get a namespace object for a given namespace prefix.
195  *
196  * @param string $prefix The XML prefix for which to fetch the namespace object.
197  *
198  * @return mixed JFeedParserNamespace or false if none exists.
199  *
200  * @since 12.3
201  */
202  protected function fetchNamespace($prefix)
203  {
204  if (isset($this->namespaces[$prefix]))
205  {
206  return $this->namespaces[$prefix];
207  }
208 
209  $className = get_class($this) . ucfirst($prefix);
210 
211  if (class_exists($className))
212  {
213  $this->namespaces[$prefix] = new $className;
214 
215  return $this->namespaces[$prefix];
216  }
217 
218  return false;
219  }
220 
221  /**
222  * Method to move the stream parser to the next XML element node.
223  *
224  * @param string $name The name of the element for which to move the stream forward until is found.
225  *
226  * @return boolean True if the stream parser is on an XML element node.
227  *
228  * @since 12.3
229  */
230  protected function moveToNextElement($name = null)
231  {
232  // Only keep looking until the end of the stream.
233  while ($this->stream->read())
234  {
235  // As soon as we get to the next ELEMENT node we are done.
236  if ($this->stream->nodeType == XMLReader::ELEMENT)
237  {
238  // If we are looking for a specific name make sure we have it.
239  if (isset($name) && ($this->stream->name != $name))
240  {
241  continue;
242  }
243 
244  return true;
245  }
246  }
247 
248  return false;
249  }
250 
251  /**
252  * Method to move the stream parser to the closing XML node of the current element.
253  *
254  * @return void
255  *
256  * @since 12.3
257  * @throws RuntimeException If the closing tag cannot be found.
258  */
259  protected function moveToClosingElement()
260  {
261  // If we are on a self-closing tag then there is nothing to do.
262  if ($this->stream->isEmptyElement)
263  {
264  return;
265  }
266 
267  // Get the name and depth for the current node so that we can match the closing node.
268  $name = $this->stream->name;
269  $depth = $this->stream->depth;
270 
271  // Only keep looking until the end of the stream.
272  while ($this->stream->read())
273  {
274  // If we have an END_ELEMENT node with the same name and depth as the node we started with we have a bingo. :-)
275  if (($this->stream->name == $name) && ($this->stream->depth == $depth) && ($this->stream->nodeType == XMLReader::END_ELEMENT))
276  {
277  return;
278  }
279  }
280 
281  throw new RuntimeException('Unable to find the closing XML node.');
282  }
283 }