* All rights reserved. * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions * are met: * * * Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. * * Redistributions in binary form must reproduce the above copyright * notice, this list of conditions and the following disclaimer in the * documentation and/or other materials provided with the distribution. * * The names of the authors may not be used to endorse or promote products * derived from this software without specific prior written permission. * * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. * * @category Net * @package Net_URL_Mapper * @author Bertrand Mansion * @license http://opensource.org/licenses/bsd-license.php New BSD License * @version CVS: $Id: Path.php,v 1.1 2007/03/28 10:23:04 mansion Exp $ * @link http://pear.php.net/package/Net_URL_Mapper */ require_once 'Net/URL.php'; require_once 'Net/URL/Mapper/Part/Dynamic.php'; require_once 'Net/URL/Mapper/Part/Wildcard.php'; require_once 'Net/URL/Mapper/Part/Fixed.php'; class Net_URL_Mapper_Path { private $path = ''; private $N = 0; public $token; public $value; private $line = 1; private $state = 1; protected $alias; protected $rules = array(); protected $defaults = array(); protected $parts = array(); protected $rule; protected $format; protected $minKeys; protected $maxKeys; protected $fixed = true; protected $required; public function __construct($path = '', $defaults = array(), $rules = array()) { $this->path = '/'.trim(Net_URL::resolvePath($path), '/'); $this->setDefaults($defaults); $this->setRules($rules); try { $this->parsePath(); } catch (Exception $e) { // The path could not be parsed correctly, treat it as fixed $this->fixed = true; $part = self::createPart(Net_URL_Mapper_Part::FIXED, $this->path, $this->path); $this->parts = array($part); } $this->getRequired(); } public function getPath() { return $this->path; } protected function parsePath() { while ($this->yylex()) { } } /** * Get the path alias * Path aliases can be used instead of full path * @return null|string */ public function getAlias() { return $this->alias; } /** * Set the path name * @param string Set the path name * @see getAlias() */ public function setAlias($alias) { $this->alias = $alias; return $this; } /** * Get the path parts default values * @return null|array */ public function getDefaults() { return $this->defaults; } /** * Set the path parts default values * @param array Associative array with format partname => value */ public function setDefaults($defaults) { if (is_array($defaults)) { $this->defaults = $defaults; } else { $this->defaults = array(); } } /** * Set the path parts default values * @param array Associative array with format partname => value */ public function setRules($rules) { if (is_array($rules)) { $this->rules = $rules; } else { $this->rules = array(); } } /** * Returns the regular expression used to match this path * @return string PERL Regular expression */ public function getRule() { if (is_null($this->rule)) { $this->rule = '/^'; foreach ($this->parts as $path => $part) { $this->rule .= $part->getRule(); } $this->rule .= '$/'; } return $this->rule; } public function getFormat() { if (is_null($this->format)) { $this->format = '/^'; foreach ($this->parts as $path => $part) { $this->format .= $part->getFormat(); } $this->format .= '$/'; } return $this->format; } protected function addPart($part) { if (array_key_exists($part->content, $this->defaults)) { $part->setRequired(false); $part->setDefaults($this->defaults[$part->content]); } if (isset($this->rules[$part->content])) { $part->setRule($this->rules[$part->content]); } $this->rule = null; if ($part->getType() != Net_URL_Mapper_Part::FIXED) { $this->fixed = false; $this->parts[$part->content] = $part; } else { $this->parts[] = $part; } return $part; } public static function createPart($type, $content, $path) { switch ($type) { case Net_URL_Mapper_Part::DYNAMIC: return new Net_URL_Mapper_Part_Dynamic($content, $path); break; case Net_URL_Mapper_Part::WILDCARD: return new Net_URL_Mapper_Part_Wildcard($content, $path); break; default: return new Net_URL_Mapper_Part_Fixed($content, $path); } } /** * Checks whether the path contains the given part by name * If value parameter is given, the part also checks if the * given value conforms to the part rule. * @param string Part name * @param mixed The value to check against */ public function hasKey($partName, $value = null) { if (array_key_exists($partName, $this->parts)) { if (!is_null($value) && $value !== false) { return $this->parts[$partName]->match($value); } else { return true; } } elseif (array_key_exists($partName, $this->defaults) && $value == $this->defaults[$partName]) { return true; } return false; } public function generate($values = array(), $qstring = array(), $anchor = '') { $path = ''; foreach ($this->parts as $part) { $path .= $part->generate($values); } $path = '/'.trim(Net_URL::resolvePath($path), '/'); if (!empty($qstring)) { $path .= '?'.http_build_query($qstring); } if (!empty($anchor)) { $path .= '#'.ltrim($anchor, '#'); } return $path; } public function getRequired() { if (!isset($this->required)) { $req = array(); foreach ($this->parts as $part) { if ($part->isRequired()) { $req[] = $part->content; } } $this->required = $req; } return $this->required; } public function getMaxKeys() { if (is_null($this->maxKeys)) { $this->maxKeys = count($this->required); $this->maxKeys += count($this->defaults); } return $this->maxKeys; } private $_yy_state = 1; private $_yy_stack = array(); function yylex() { return $this->{'yylex' . $this->_yy_state}(); } function yypushstate($state) { array_push($this->_yy_stack, $this->_yy_state); $this->_yy_state = $state; } function yypopstate() { $this->_yy_state = array_pop($this->_yy_stack); } function yybegin($state) { $this->_yy_state = $state; } function yylex1() { $tokenMap = array ( 1 => 1, 3 => 1, 5 => 1, 7 => 1, 9 => 1, ); if ($this->N >= strlen($this->path)) { return false; // end of input } $yy_global_pattern = "/^(\/?:\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))/"; do { if (preg_match($yy_global_pattern, substr($this->path, $this->N), $yymatches)) { $yysubmatches = $yymatches; $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns if (!count($yymatches)) { throw new Exception('Error: lexing failed because a rule matched' . 'an empty string. Input "' . substr($this->path, $this->N, 5) . '... state START'); } next($yymatches); // skip global match $this->token = key($yymatches); // token number if ($tokenMap[$this->token]) { // extract sub-patterns for passing to lex function $yysubmatches = array_slice($yysubmatches, $this->token + 1, $tokenMap[$this->token]); } else { $yysubmatches = array(); } $this->value = current($yymatches); // token value $r = $this->{'yy_r1_' . $this->token}($yysubmatches); if ($r === null) { $this->N += strlen($this->value); $this->line += substr_count("\n", $this->value); // accept this token return true; } elseif ($r === true) { // we have changed state // process this token in the new state return $this->yylex(); } elseif ($r === false) { $this->N += strlen($this->value); $this->line += substr_count("\n", $this->value); if ($this->N >= strlen($this->path)) { return false; // end of input } // skip this token continue; } else { $yy_yymore_patterns = array( 1 => "^(\/?\\*\/?\\(([a-zA-Z0-9_]+)\\))|^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", 3 => "^(\/?:([a-zA-Z0-9_]+))|^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", 5 => "^(\/?\\*([a-zA-Z0-9_]+))|^(\/?([^\/:*]+))", 7 => "^(\/?([^\/:*]+))", 9 => "", ); // yymore is needed do { if (!strlen($yy_yymore_patterns[$this->token])) { throw new Exception('cannot do yymore for the last token'); } if (preg_match($yy_yymore_patterns[$this->token], substr($this->path, $this->N), $yymatches)) { $yymatches = array_filter($yymatches, 'strlen'); // remove empty sub-patterns next($yymatches); // skip global match $this->token = key($yymatches); // token number $this->value = current($yymatches); // token value $this->line = substr_count("\n", $this->value); } } while ($this->{'yy_r1_' . $this->token}() !== null); // accept $this->N += strlen($this->value); $this->line += substr_count("\n", $this->value); return true; } } else { throw new Exception('Unexpected input at line' . $this->line . ': ' . $this->path[$this->N]); } break; } while (true); } // end function const START = 1; function yy_r1_1($yy_subpatterns) { $c = $yy_subpatterns[0]; $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value); $this->addPart($part); } function yy_r1_3($yy_subpatterns) { $c = $yy_subpatterns[0]; $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value); $this->addPart($part); } function yy_r1_5($yy_subpatterns) { $c = $yy_subpatterns[0]; $part = self::createPart(Net_URL_Mapper_Part::DYNAMIC, $c, $this->value); $this->addPart($part); } function yy_r1_7($yy_subpatterns) { $c = $yy_subpatterns[0]; $part = self::createPart(Net_URL_Mapper_Part::WILDCARD, $c, $this->value); $this->addPart($part); } function yy_r1_9($yy_subpatterns) { $c = $yy_subpatterns[0]; $part = self::createPart(Net_URL_Mapper_Part::FIXED, $c, $this->value); $this->addPart($part); } } ?>