PluginProbe
FeedWordPress / 2020.0118
FeedWordPress v2020.0118
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 2020.0118, at syndicatedpostxpathquery.class.php

463 lines 12.1 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; $element = NULL;
363
364 $axis = 'child'; // "In effect, `child` is the default axis."
365 if (is_object($node) and property_exists($node, 'verb')):
366 if ('has'==$node->verb) :
367 $axis = 'self';
368 endif;
369 elseif (strpos($node, '::') !== false) :
370 list($axis, $node) = explode("::", $node, 2);
371 if ($axis=='attribute') :
372 $axis = 'attribs'; // map from W3C to SimplePie's idiosyncratic notation
373 endif;
374 elseif (substr($node, 0, 1)=='@') :
375 $axis = 'attribs'; $node = substr($node, 1);
376 elseif (is_numeric($node)) :
377 $axis = 'self';
378 elseif (substr($node, 0, 1)=='/') :
379 // FIXME: properly, we should check for // and if we have it,
380 // treat that as short for /descendent-or-self::node()/
381 $axis = 'child'; $node = substr($node, 1);
382 else :
383 // NOOP
384 endif;
385
386 if (is_string($node) and preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
387 $element = $ref[2];
388 elseif (is_string($node) and strpos($node, ':') !== FALSE) :
389 list($xmlns, $element) = explode(':', $node, 2);
390 else :
391 $element = $node;
392 endif;
393 return array($axis, $element);
394 } /* SyndicatedPostXPathQuery::xpath_name_and_axis () */
395
396 public function xpath_possible_namespaces ($node, $datum = array()) {
397 $ns = NULL; $element = NULL;
398
399 if (substr($node, 0, 1)=='@') :
400 $attr = '@'; $node = substr($node, 1);
401 else :
402 $attr = '';
403 endif;
404
405 if (preg_match('/^{([^}]*)}(.*)$/', $node, $ref)) :
406 $ns = array($ref[1]);
407 elseif (strpos($node, ':') !== FALSE) :
408 list($xmlns, $element) = explode(':', $node, 2);
409
410 if (isset($this->xmlns['reverse'][$xmlns])) :
411 $ns = $this->xmlns['reverse'][$xmlns];
412 else :
413 $ns = array($xmlns);
414 endif;
415
416 // Fucking SimplePie. For attributes in default xmlns.
417 $defaultNS = $this->xpath_default_namespace();
418 if (isset($this->xmlns['forward'][$defaultNS])
419 and ($xmlns==$this->xmlns['forward'][$defaultNS])) :
420 $ns[] = '';
421 endif;
422
423 if (isset($datum['xmlns'])) :
424 if (isset($datum['xmlns'][$xmlns])) :
425 $ns[] = $datum['xmlns'][$xmlns];
426 endif;
427 endif;
428 else :
429 // Often in SimplePie, the default namespace gets stored
430 // as an empty string rather than a URL.
431 $ns = array($this->xpath_default_namespace(), '');
432 endif;
433 return array_unique($ns);
434 } /* SyndicatedPostXPathQuery::xpath_possible_namespaces() */
435
436 } /* class SyndicatedPostXPathQuery */
437
438 // When called directly, run through and perform some tests.
439 if (basename($_SERVER['SCRIPT_FILENAME'])==basename(__FILE__)) :
440 # some day when I am a grown-up developer I might include
441 # some test cases in this here section
442 # we need to implement wp_parse_args(), __(), and class WP_Error ...
443 #function wp_parse_args ($r, $defaults) {
444 # return array_merge($defaults, $r);
445 #}
446 #function __($text, $domain) {
447 # return $text;
448 #}
449 #class WP_Error {
450 # public function __construct ( $slug, $message ) {
451 # /*DBG*/ echo $slug;
452 # /*DBG*/ echo ": ";
453 # /*DBG*/ echo $message;
454 # }
455 #}
456 #
457 #header("Content-type: text/plain");
458 #
459 #$spxq = new SyndicatedPostXPathQuery(array("path" => $_REQUEST['p']));
460 #
461 #var_dump($spxq);
462 endif;
463