PluginProbe
FeedWordPress / 2024.0428
FeedWordPress v2024.0428
trunk 0.8 0.9 0.91 0.95 0.96 0.97 0.98 0.981 0.99 0.991 0.992 0.993 2008.1030 2008.1101 2008.1105 2008.1214 2009.0612 2009.0613 2009.0618 2009.0707 2009.1111 2009.1112 2010.0127 2010.0528 All 65 releases
feedwordpress / syndicatedpostxpathquery.class.php

syndicatedpostxpathquery.class.php in FeedWordPress 2024.0428, at syndicatedpostxpathquery.class.php

466 lines 12.2 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * class SyndicatedPostXPathQuery: implements an XPath-like syntax used to query
4 * arbitrary elements within the syndicated item.
5 *
6 */
7 class SyndicatedPostXPathQuery {
8 private $path;
9 private $parsedPath;
10 private $feed_type;
11 private $xmlns;
12 private $urlHash = array();
13
14 /**
15 * SyndicatedPostXPathQuery::__construct
16 *
17 * @param array $args
18 * @uses wp_parse_args
19 *
20 */
21 public function __construct ($args = array()) {
22 if (is_string($args)) :
23 $args = array("path" => $args);
24 endif;
25
26 $args = wp_parse_args($args, array(
27 "path" => "",
28 ));
29
30 $this->setPath($args['path']);
31 } /* SyndicatedPostXPathQuery::__construct() */
32
33 /**
34 * SyndicatedPostXPathQuery::getPath
35 *
36 * @param array $args
37 * @return mixed
38 */
39 public function getPath ($args = array()) {
40 $args = wp_parse_args($args, array(
41 "parsed" => false,
42 ));
43
44 return ($args['parsed'] ? $this->parsedPath : $this->path);
45 } /* SyndicatedPostXPathQuery::getPath () */
46
47 /**
48 * SyndicatedPostXPathQuery::setPath
49 *
50 * @param string $path
51 */
52 public function setPath ($path) {
53 $this->urlHash = array();
54
55 $this->path = $path;
56
57 // Allow {url} notation for namespaces. URLs will contain : and /, so...
58 preg_match_all('/{([^}]+)}/', $path, $match, PREG_SET_ORDER);
59 foreach ($match as $ref) :
60 $this->urlHash[md5($ref[1])] = $ref[1];
61 endforeach;
62
63 foreach ($this->urlHash as $hash => $url) :
64 $path = str_replace('{'.$url.'}', '{#'.$hash.'}', $path);
65 endforeach;
66
67 $path = $this->parsePath(/*cur=*/ $path, /*orig=*/ $path);
68
69 $this->parsedPath = $path;
70
71 } /* SyndicatedPostXPathQuery::setPath() */
72
73 /**
74 * SyndicatedPostXPathQuery::snipSlug
75 *
76 * @return string
77 */
78 protected function snipSlug ($path, $start, $n) {
79 $slug = substr($path, $start, ($n-$start));
80 if (strlen($slug) > 0) :
81 if (preg_match('/{#([^}]+)}/', $slug, $ref)) :
82 if (isset($this->urlHash[$ref[1]])) :
83 $slug = str_replace(
84 '{#'.$ref[1].'}',
85 '{'.$this->urlHash[$ref[1]].'}',
86 $slug
87 );
88 endif;
89 endif;
90 endif;
91 return $slug;
92 } /* SyndicatedPostXPathQuery::snipSlug () */
93
94 /**
95 * SyndicatedPostXPathQuery::parsePath ()
96 *
97 * @param mixed $path
98 * @param mixed $rootPath
99 * @return array|object
100 */
101 public function parsePath ($path, $rootPath) {
102 if (is_array($path)) :
103 // This looks like it's already been parsed.
104 $pp = $path;
105 else :
106 $pp = array();
107
108 // Okay let's parse this thing.
109 $n = 0; $start = 0; $state = 'slug';
110 while ($state != '$') :
111 switch ($state) :
112 case 'slash' :
113 $slug = $this->snipSlug($path, $start, $n);
114 if (strlen($slug) > 0) :
115 $pp[] = $slug;
116 endif;
117
118 $n++;
119 // don't include the slash in our next slug
120 $start = $n;
121
122 $state = (($n < strlen($path)) ? 'slug' : '$');
123
124 break;
125 case 'brackets' :
126
127 // first, snip off what we've consumed so far
128 $slug = $this->snipSlug($path, $start, $n);
129 if (strlen($slug) > 0) :
130 $pp[] = $slug;
131 endif;
132
133 // now, chase the ]
134 $depth = 1;
135 $n++; $start = $n;
136
137 // find the end of the [square-bracketed] expression
138 while ($depth > 0 and $n != '') :
139 $tok = ((strlen($path) > $n) ? $path[$n] : '');
140 switch ($tok) :
141 case '' :
142 // ERROR STATE: syntax error
143 $depth = -1;
144 $state = 'syntax-error';
145 break;
146 case '[' :
147 $depth++;
148 break;
149 case ']' :
150 $depth--;
151 break;
152 default :
153 // NOOP
154 endswitch;
155 $n++;
156 endwhile;
157
158 if ($state != 'syntax-error') :
159 $bracketed = substr($path, $start, ($n-$start)-1);
160
161 // recursive parsing
162 $oFilter = new stdClass;
163 $oFilter->verb = 'has';
164 $oFilter->query = $this->parsePath($bracketed, $rootPath);
165 $pp[] = $oFilter;
166
167 $start = $n;
168
169 $state = 'slash-expected';
170 endif;
171 break;
172
173 case 'slash-expected' :
174 $tok = ((strlen($path) > $n) ? $path[$n] : '');
175 if ($tok == '/' or $tok == '') :
176 $state = 'slash';
177 else :
178 $state = 'syntax-error';
179 endif;
180 break;
181 case 'syntax-error' :
182 $pp = new WP_Error('xpath', __("Syntax error", "feedwordpress"));
183 $state = '$';
184 break;
185 case 'slug' :
186 default :
187 $tok = ((strlen($path) > $n) ? $path[$n] : '');
188 switch ($tok) :
189 case '' :
190 case '/' :
191 $state = 'slash';
192 break;
193 case '[' :
194 $state = 'brackets';
195 break;
196 default :
197 $n++;
198 endswitch;
199 endswitch;
200 endwhile;
201 endif;
202 return $pp;
203 } /* SyndicatedPostXPathQuery::parsePath() */
204
205 /**
206 * SyndicatedPostXPathQuery::match
207 *
208 * @param string $path
209 * @return array
210 */
211 public function match ($r = array()) {
212 $path = $this->parsedPath;
213
214 $r = wp_parse_args($r, array(
215 "type" => SIMPLEPIE_TYPE_ATOM_10,
216 "xmlns" => array(),
217 "map" => array(),
218 "context" => array(),
219 "parent" => array(),
220 "format" => "string",
221 ));
222
223 $this->feed_type = $r['type'];
224 $this->xmlns = $r['xmlns'];
225
226 // Start out with a get_item_tags query.
227 $node = '';
228 while (strlen($node)==0 and !is_null($node)) :
229 $node = array_shift($path);
230 endwhile;
231
232 if (is_string($node) and isset($r['map'][$node])) :
233 $data = $r['map'][$node];
234 $node = array_shift($path);
235 else :
236 $data = $r['map']['/'];
237 endif;
238
239 $matches = $data;
240 while ( !is_null($node)) :
241 if (is_object($node) OR strlen($node) > 0) :
242 list($axis, $element) = $this->xpath_name_and_axis($node);
243 if ('self'==$axis) :
244 if (is_object($element) and property_exists($element, 'verb')) :
245
246 $subq = new self(array("path" => $element->query));
247 $result = $subq->match(array(
248 "type" => $r['type'],
249 "xmlns" => $r['xmlns'],
250 "map" => array(
251 "/" => $matches,
252 ),
253 "context" => $matches,
254 "parent" => $r['parent'],
255 "format" => "object",
256 ));
257
258 // when format = 'object' we should get back
259 // a sparse array of arrays, with indices = indices
260 // from the input array, each element = an array of
261 // one or more matching elements
262
263 if ($element->verb = 'has' and is_array($result)) :
264
265 $results = array();
266 foreach (array_keys($result) as $a) :
267 $results[$a] = $matches[$a];
268 endforeach;
269
270 $matches = $results;
271 $data = $matches;
272 endif;
273
274 elseif (is_numeric($node)) :
275
276 // according to W3C, sequence starts at position 1, not 0
277 // so subtract 1 to line up with PHP array starting at 0
278 $idx = intval($element) - 1;
279 if (isset($matches[$idx])) :
280 $data = array($idx => $matches[$idx]);
281 else :
282 $data = array();
283 endif;
284
285 $matches = array($idx => $data);
286 endif;
287
288 else :
289 $matches = array();
290
291 foreach ($data as $idx => $datum) :
292 if ( !is_string($datum) and isset($datum[$axis])) :
293 foreach ($datum[$axis] as $ns => $elements) :
294 if (isset($elements[$element])) :
295 // Potential match.
296 // Check namespace.
297 if (is_string($elements[$element])) : // Attribute
298 $addenda = array($elements[$element]);
299 $contexts = array($datum);
300
301 // Element
302 else :
303 $addenda = $elements[$element];
304 $contexts = $elements[$element];
305 endif;
306
307 foreach ($addenda as $index => $addendum) :
308 $context = $contexts[$index];
309
310 $namespaces = $this->xpath_possible_namespaces($node, $context);
311 if (in_array($ns, $namespaces)) :
312 $matches[] = $addendum;
313 endif;
314 endforeach;
315 endif;
316 endforeach;
317 endif;
318 endforeach;
319
320 $data = $matches;
321 endif;
322 endif;
323 $node = array_shift($path);
324 endwhile;
325
326 $matches = array();
327 foreach ($data as $idx => $datum) :
328 if ($r['format'] == 'string') :
329 if (is_string($datum)) :
330 $matches[] = $datum;
331 elseif (isset($datum['data'])) :
332 $matches[] = $datum['data'];
333 endif;
334 else :
335 $matches[$idx] = $datum;
336 endif;
337 endforeach;
338
339 return $matches;
340 } /* SyndicatedPostXPathQuery::match() */
341
342 public function xpath_default_namespace () {
343 // Get the default namespace.
344 $type = $this->feed_type;
345 if ($type & SIMPLEPIE_TYPE_ATOM_10) :
346 $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_10;
347 elseif ($type & SIMPLEPIE_TYPE_ATOM_03) :
348 $defaultNS = SIMPLEPIE_NAMESPACE_ATOM_03;
349 elseif ($type & SIMPLEPIE_TYPE_RSS_090) :
350 $defaultNS = SIMPLEPIE_NAMESPACE_RSS_090;
351 elseif ($type & SIMPLEPIE_TYPE_RSS_10) :
352 $defaultNS = SIMPLEPIE_NAMESPACE_RSS_10;
353 elseif ($type & SIMPLEPIE_TYPE_RSS_20) :
354 $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
355 else :
356 $defaultNS = SIMPLEPIE_NAMESPACE_RSS_20;
357 endif;
358 return $defaultNS;
359 } /* SyndicatedPostXPathQuery::xpath_default_namespace() */
360
361 public function xpath_name_and_axis ($node) {
362 // $ns = NULL; // Was unused. (gwyneth 20230920)
363 $element = NULL;
364
365 $axis = 'child'; // "In effect, `child` is the default axis."
366 if (is_object($node) and property_exists($node, 'verb')):
367 if ('has'==$node->verb) :
368 $axis = 'self';
369 endif;
370 elseif (strpos($node, '::') !== false) :
371 list($axis, $node) = explode("::", $node, 2);
372 if ($axis=='attribute') :
373 $axis = 'attribs'; // map from W3C to SimplePie's idiosyncratic notation
374 endif;
375 elseif (substr($node, 0, 1)=='@') :
376 $axis = 'attribs'; $node = substr($node, 1);
377 elseif (is_numeric($node)) :
378 $axis = 'self';
379 elseif (substr($node, 0, 1)=='/') :
380 // FIXME: properly, we should check for // and if we have it,
381 // treat that as short for /descendent-or-self::node()/
382 $axis = 'child'; $node = substr($node, 1);
383 else :
384 // NOOP
385 endif;
386
387 if (is_string($node) and preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
388 $element = $ref[2];
389 elseif (is_string($node) and strpos($node, ':') !== FALSE) :
390 list($xmlns, $element) = explode(':', $node, 2);
391 else :
392 $element = $node;
393 endif;
394 return array($axis, $element);
395 } /* SyndicatedPostXPathQuery::xpath_name_and_axis () */
396
397 public function xpath_possible_namespaces ($node, $datum = array()) {
398 $ns = NULL;
399 // $element = NULL; // reverse of before: now it's the $element that's never used (gwyneth 20230920)
400
401 // Is $attr really the correct thing to assign to? It's never used... (gwyneth 20230920)
402 if (substr($node, 0, 1)=='@') :
403 $attr = '@'; $node = substr($node, 1);
404 else :
405 $attr = '';
406 endif;
407
408 if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
409 $ns = array($ref[1]);
410 elseif (strpos($node, ':') !== FALSE) :
411 list($xmlns, $element) = explode(':', $node, 2);
412
413 if (isset($this->xmlns['reverse'][$xmlns])) :
414 $ns = $this->xmlns['reverse'][$xmlns];
415 else :
416 $ns = array($xmlns);
417 endif;
418
419 // Fucking SimplePie. For attributes in default xmlns.
420 $defaultNS = $this->xpath_default_namespace();
421 if (isset($this->xmlns['forward'][$defaultNS])
422 and ($xmlns==$this->xmlns['forward'][$defaultNS])) :
423 $ns[] = '';
424 endif;
425
426 if (isset($datum['xmlns'])) :
427 if (isset($datum['xmlns'][$xmlns])) :
428 $ns[] = $datum['xmlns'][$xmlns];
429 endif;
430 endif;
431 else :
432 // Often in SimplePie, the default namespace gets stored
433 // as an empty string rather than a URL.
434 $ns = array($this->xpath_default_namespace(), '');
435 endif;
436 return array_unique($ns);
437 } /* SyndicatedPostXPathQuery::xpath_possible_namespaces() */
438
439 } /* class SyndicatedPostXPathQuery */
440
441 // When called directly, run through and perform some tests.
442 if (basename($_SERVER['SCRIPT_FILENAME'])==basename(__FILE__)) :
443 # some day when I am a grown-up developer I might include
444 # some test cases in this here section
445 # we need to implement wp_parse_args(), __(), and class WP_Error ...
446 #function wp_parse_args ($r, $defaults) {
447 # return array_merge($defaults, $r);
448 #}
449 #function __($text, $domain) {
450 # return $text;
451 #}
452 #class WP_Error {
453 # public function __construct ( $slug, $message ) {
454 # /*DBG*/ echo $slug;
455 # /*DBG*/ echo ": ";
456 # /*DBG*/ echo $message;
457 # }
458 #}
459 #
460 #header("Content-type: text/plain");
461 #
462 #$spxq = new SyndicatedPostXPathQuery(array("path" => $_REQUEST['p']));
463 #
464 #var_dump($spxq);
465 endif;
466