3 * URL parser and mapper
9 * Copyright (c) 2006, Bertrand Mansion <golgote@mamasam.com>
10 * All rights reserved.
12 * Redistribution and use in source and binary forms, with or without
13 * modification, are permitted provided that the following conditions
16 * * Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 * * Redistributions in binary form must reproduce the above copyright
19 * notice, this list of conditions and the following disclaimer in the
20 * documentation and/or other materials provided with the distribution.
21 * * The names of the authors may not be used to endorse or promote products
22 * derived from this software without specific prior written permission.
24 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
25 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
26 * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
27 * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
28 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
29 * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
30 * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
31 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
32 * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
33 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
34 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
37 * @package Net_URL_Mapper
38 * @author Bertrand Mansion <golgote@mamasam.com>
39 * @license http://opensource.org/licenses/bsd-license.php New BSD License
40 * @version CVS: $Id: Path.php 296456 2010-03-20 00:41:08Z kguest $
41 * @link http://pear.php.net/package/Net_URL_Mapper
44 require_once 'Net/URL.php';
45 require_once 'Net/URL/Mapper/Part/Dynamic.php';
46 require_once 'Net/URL/Mapper/Part/Wildcard.php';
47 require_once 'Net/URL/Mapper/Part/Fixed.php';
49 class Net_URL_Mapper_Path
60 protected $rules = array();
61 protected $defaults = array();
62 protected $parts = array();
67 protected $fixed = true;
70 public function __construct($path = '', $defaults = array(), $rules = array())
72 $this->path = '/'.trim(Net_URL::resolvePath($path), '/');
73 $this->setDefaults($defaults);
74 $this->setRules($rules);
78 } catch (Exception $e) {
79 // The path could not be parsed correctly, treat it as fixed
81 $part = self::createPart(Net_URL_Mapper_Part::FIXED, $this->path, $this->path);
82 $this->parts = array($part);
88 * Called when the object is serialized
89 * Make sure we do not store too much info when the object is serialized
90 * and call the regular expressions generator functions so that they will
91 * not need to be generated again on wakeup.
93 * @return array Name of properties to store when serialized
95 public function __sleep()
99 return array('alias', 'path', 'defaults', 'rule', 'format',
100 'parts', 'minKeys', 'maxKeys', 'fixed', 'required');
103 public function getPath()
108 protected function parsePath()
110 while ($this->yylex()) { }
115 * Path aliases can be used instead of full path
116 * @return null|string
118 public function getAlias()
125 * @param string Set the path name
128 public function setAlias($alias)
130 $this->alias = $alias;
135 * Get the path parts default values
138 public function getDefaults()
140 return $this->defaults;
144 * Set the path parts default values
145 * @param array Associative array with format partname => value
147 public function setDefaults($defaults)
149 if (is_array($defaults)) {
150 $this->defaults = $defaults;
152 $this->defaults = array();
157 * Set the path parts default values
158 * @param array Associative array with format partname => value
160 public function setRules($rules)
162 if (is_array($rules)) {
163 $this->rules = $rules;
165 $this->rules = array();
170 * Returns the regular expression used to match this path
171 * @return string PERL Regular expression
173 public function getRule()
175 if (is_null($this->rule)) {
177 foreach ($this->parts as $path => $part) {
178 $this->rule .= $part->getRule();
185 public function getFormat()
187 if (is_null($this->format)) {
188 $this->format = '/^';
189 foreach ($this->parts as $path => $part) {
190 $this->format .= $part->getFormat();
192 $this->format .= '$/';
194 return $this->format;
197 protected function addPart($part)
199 if (array_key_exists($part->content, $this->defaults)) {
200 $part->setRequired(false);
201 $part->setDefaults($this->defaults[$part->content]);
203 if (isset($this->rules[$part->content])) {
204 $part->setRule($this->rules[$part->content]);
207 if ($part->getType() != Net_URL_Mapper_Part::FIXED) {
208 $this->fixed = false;
209 $this->parts[$part->content] = $part;
211 $this->parts[] = $part;
216 public static function createPart($type, $content, $path)
219 case Net_URL_Mapper_Part::DYNAMIC:
220 return new Net_URL_Mapper_Part_Dynamic($content, $path);
222 case Net_URL_Mapper_Part::WILDCARD:
223 return new Net_URL_Mapper_Part_Wildcard($content, $path);
226 return new Net_URL_Mapper_Part_Fixed($content, $path);
231 * Checks whether the path contains the given part by name
232 * If value parameter is given, the part also checks if the
233 * given value conforms to the part rule.
234 * @param string Part name
235 * @param mixed The value to check against
237 public function hasKey($partName, $value = null)
239 if (array_key_exists($partName, $this->parts)) {
240 if (!is_null($value) && $value !== false) {
241 return $this->parts[$partName]->match($value);
245 } elseif (array_key_exists($partName, $this->defaults) &&
246 $value == $this->defaults[$partName]) {
252 public function generate($values = array(), $qstring = array(), $anchor = '')
255 foreach ($this->parts as $part) {
256 $path .= $part->generate($values);
258 $path = '/'.trim(Net_URL::resolvePath($path), '/');
259 if (!empty($qstring)) {
260 if(strpos($path, '?') === false) {
265 $path .= http_build_query($qstring);
267 if (!empty($anchor)) {
268 $path .= '#'.ltrim($anchor, '#');
273 public function getRequired()
275 if (!isset($this->required)) {
277 foreach ($this->parts as $part) {
278 if ($part->isRequired()) {
279 $req[] = $part->content;
282 $this->required = $req;
284 return $this->required;
287 public function getMaxKeys()
289 if (is_null($this->maxKeys)) {
290 $this->maxKeys = count($this->required);
291 $this->maxKeys += count($this->defaults);
293 return $this->maxKeys;
299 private $_yy_state = 1;
300 private $_yy_stack = array();
304 return $this->{'yylex' . $this->_yy_state}();
307 function yypushstate($state)
309 array_push($this->_yy_stack, $this->_yy_state);
310 $this->_yy_state = $state;
313 function yypopstate()
315 $this->_yy_state = array_pop($this->_yy_stack);
318 function yybegin($state)
320 $this->_yy_state = $state;
334 if ($this->N >= strlen($this->path)) {
335 return false; // end of input
337 $yy_global_pattern = "/^(\/?:\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))/";
340 if (preg_match($yy_global_pattern, substr($this->path, $this->N), $yymatches)) {
341 $yysubmatches = $yymatches;
342 $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
343 if (!count($yymatches)) {
344 throw new Exception('Error: lexing failed because a rule matched' .
345 'an empty string. Input "' . substr($this->path,
346 $this->N, 5) . '... state START');
348 next($yymatches); // skip global match
349 $this->token = key($yymatches); // token number
350 if ($tokenMap[$this->token]) {
351 // extract sub-patterns for passing to lex function
352 $yysubmatches = array_slice($yysubmatches, $this->token + 1,
353 $tokenMap[$this->token]);
355 $yysubmatches = array();
357 $this->value = current($yymatches); // token value
358 $r = $this->{'yy_r1_' . $this->token}($yysubmatches);
360 $this->N += strlen($this->value);
361 $this->line += substr_count("\n", $this->value);
364 } elseif ($r === true) {
365 // we have changed state
366 // process this token in the new state
367 return $this->yylex();
368 } elseif ($r === false) {
369 $this->N += strlen($this->value);
370 $this->line += substr_count("\n", $this->value);
371 if ($this->N >= strlen($this->path)) {
372 return false; // end of input
376 } else { $yy_yymore_patterns = array(
377 1 => "^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))",
378 3 => "^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))",
379 5 => "^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))",
380 7 => "^(\/?([^\/:*]+))",
386 if (!strlen($yy_yymore_patterns[$this->token])) {
387 throw new Exception('cannot do yymore for the last token');
389 if (preg_match($yy_yymore_patterns[$this->token],
390 substr($this->path, $this->N), $yymatches)) {
391 $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns
392 next($yymatches); // skip global match
393 $this->token = key($yymatches); // token number
394 $this->value = current($yymatches); // token value
395 $this->line = substr_count("\n", $this->value);
397 } while ($this->{'yy_r1_' . $this->token}() !== null);
399 $this->N += strlen($this->value);
400 $this->line += substr_count("\n", $this->value);
404 throw new Exception('Unexpected input at line' . $this->line .
405 ': ' . $this->path[$this->N]);
413 function yy_r1_1($yy_subpatterns)
416 $c = $yy_subpatterns[0];
417 $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value);
418 $this->addPart($part);
420 function yy_r1_3($yy_subpatterns)
423 $c = $yy_subpatterns[0];
424 $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value);
425 $this->addPart($part);
427 function yy_r1_5($yy_subpatterns)
430 $c = $yy_subpatterns[0];
431 $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value);
432 $this->addPart($part);
434 function yy_r1_7($yy_subpatterns)
437 $c = $yy_subpatterns[0];
438 $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value);
439 $this->addPart($part);
441 function yy_r1_9($yy_subpatterns)
444 $c = $yy_subpatterns[0];
445 $part = self::createPart(Net_URL_Mapper_Part::FIXED, $c, $this->value);
446 $this->addPart($part);