| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279 |
- <?php
- class XmlNode extends SimpleXmlIterator
- {
- public function parent()
- { return $this->xpath(".."); }
- }
- class XmlStroller
- {
- private $xmlNode;
- private $fncList = array(
- "empty" => false,
- "first-child" => false,
- "first-of-type" => false,
- "last-child" => false,
- "last-of-type" => false,
- "not" => true,
- "nth-child" => true,
- "nth-last-child" => true,
- "nth-last-of-type" => true,
- "nth-of-type" => true,
- "only-of-type" => false,
- "only-child" => false,
- "root" => false
- );
- public $debug = false;
- /**
- * Create new Stroller for xml $data
- * @param $data mixed xml data to parse
- * if $data is a string, implement SimpleXmlIterator to evaluate it
- * if $data is a SimpleXmlIterator
- **/
- public function __construct($data)
- {
- if (is_string($data))
- $this->xmlNode = new \XmlNode($data);
- else if ($data instanceof \XmlNode)
- $this->xmlNode = $data;
- }
- private function getTokens($str)
- {
- $tokens = preg_split("/([^A-Za-z0-9\*\-\.:])+/", $str, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
- $tokens_final = array();
- $nbTokens = count($tokens);
- for ($i = 0; $i < $nbTokens; $i++)
- {
- $tokens_final[] = $tokens[$i][0];
- if ($i < $nbTokens -1)
- {
- $tokenOffsetRight = strlen($tokens[$i][0]) + $tokens[$i][1];
- if ($tokenOffsetRight < $tokens[$i +1][1])
- {
- $token = substr($str, $tokenOffsetRight, $tokens[$i +1][1] - $tokenOffsetRight);
- $t = trim($token);
- if (ltrim($token) != $token)
- $tokens_final[] = " ";
- if (!empty($t))
- $tokens_final[] = $t;
- if (rtrim($token) != $token)
- $tokens_final[] = " ";
- }
- }
- }
- $tokens_final[] = ',';
- return $tokens_final;
- }
- private function compile($str)
- {
- $tokens = $this->getTokens($str);
- $result = array();
- $currentBranch = array();
- $nbTokens = count($tokens);
- $childTokenDictionnary = array(">", "+", "~", " ");
- $inAttribute = false;
- $attrs = array();
- $inToken = false;
- $inParam = false;
- for ($i=0; $i < $nbTokens; $i++)
- {
- if ($tokens[$i] == ',')
- {
- if (!empty($attrs))
- $currentBranch[] = array('', array( "value" => "", "fnc" => array(), "attr" => $attrs ));
- $attrs = false;
- $result[] = $currentBranch;
- $currentBranch = array();
- }
- else if ($tokens[$i] == '[' && $inAttribute)
- {
- $expected = ']';
- if (!$inAttribute["key"])
- $expected = 'key';
- else if (!$inAttribute["sep"])
- $expected = 'separator or \']\'';
- else if (!$inAttribute["value"])
- $expected = 'value';
- throw new \Exception("Unexpected token '[', expected {$expected}");
- }
- else if ($tokens[$i] == '[' && !$inAttribute)
- {
- $params = explode(':', $tokens[$i]);
- $inAttribute = array( "key" => null, "sep" => false, "value" => null );
- }
- else if ($inAttribute)
- {
- if ($tokens[$i] == ']')
- {
- if (!$inAttribute["key"])
- throw new \Exception("Unexpected token ']', expected name");
- if ($inAttribute["sep"] !== false && $inAttribute["value"] === null)
- throw new \Exception("Unexpected token ']', expected value");
- if ($inToken !== false)
- $attrs[] = $inAttribute;
- else
- $currentBranch[count($currentBranch) -1][1]["attr"][] = $inAttribute;
- $inAttribute = false;
- }
- else
- {
- if (!$inAttribute["key"])
- $inAttribute["key"] = $tokens[$i];
- else if (!$inAttribute["sep"])
- $inAttribute["sep"] = $tokens[$i];
- else if (!$inAttribute["value"])
- $inAttribute["value"] = $tokens[$i];
- }
- }
- else if (in_array($tokens[$i], $childTokenDictionnary) || empty(trim($tokens[$i])))
- {
- if ($inToken && !empty(trim($tokens[$i])))
- throw new \Exception("Unexpected token '{$tokens[$i]}', expected name");
- elseif ($inToken)
- continue;
- $inToken = empty(trim($tokens[$i])) ? '' : $tokens[$i];
- }
- else
- {
- $params = explode(':', $tokens[$i]);
- $currentBranch[] = array($inToken === false ? '' : $inToken, array("value" => $params[0], "fnc" => array_slice($params, 1), "attr" => $attrs));
- $inToken = false;
- $attrs = array();
- }
- }
- if ($attrs !== false)
- $result[count($result) -1] = array('', array( "value" => "", "fnc" => array(), "attr" => $attrs ));
- return $result;
- }
- private function checkAttr($item, $attr)
- {
- $attributes = $item->attributes();
- if ($attr["sep"] == "=")
- return isset($attributes[$attr["key"]]) && $attributes[$attr["key"]] == $attr["value"];
- else if ($attr["sep"] === false)
- return isset($attributes[$attr["key"]]);
- return false;
- }
- private function checkQuery($items, &$result, $criteria)
- {
- if (empty($criteria))
- {
- $result[] = $items;
- return;
- }
- $childCriteria = array_slice($criteria, 1);
- foreach ($items as $i)
- {
- //Check if items match $criteria[0]
- if (($criteria[0][0] == '' || $criteria[0][0] == '>') && ($i->getName() == $criteria[0][1]["value"] || $criteria[0][1]["value"] == '' || $criteria[0][1]["value"] == '*'))
- {
- foreach ($criteria[0][1]["fnc"] as $fnc)
- {
- if (!array_key_exists($fnc, $this->fncList))
- throw new Exception("Unexpected token '{$fnc}', expected function name");
- //TODO check fnc parameters
- //TODO exec fnc
- }
- $attr = true;
- foreach ($criteria[0][1]["attr"] as $attr)
- {
- if (!$this->checkAttr($i, $attr))
- {
- $attr = false;
- break;
- }
- }
- if ($attr)
- $this->checkQuery($i, $result, $childCriteria);
- }
- else if ($criteria[0][0] == '')
- $this->checkQuery($i, $result, $criteria);
- //TODO ~, +
- }
- }
- /**
- * Find in xml the request
- **/
- public function select($criteria_str)
- {
- $request = $this->compile($criteria_str);
- if ($this->debug) { echo "Compiler output: "; var_dump($request); }
- $result = array();
- foreach ($request as $i)
- $this->checkQuery(array($this->xmlNode), $result, $i);
- $r = array();
- foreach ($result as $i)
- $r[] = new self($i);
- return $r;
- }
- public function getAttributes()
- {
- $result = array();
- foreach ($this->xmlNode->attributes() as $i => $j)
- $result[$i] = (string) $j;
- return $result;
- }
- public function attr($key =null, $defaultValue =null)
- {
- if ($key == null)
- return $this->getAttributes();
- if (isset($this->xmlNode->attributes()[$key]))
- return (string)$this->xmlNode->attributes()[$key];
- return $defaultValue;
- }
- public function valueString()
- {
- return $this->value;
- }
- public function __tostring()
- {
- return $this->valueString();
- }
- public function value()
- {
- return (string) $this->xmlNode;
- }
- public function valueFloat()
- {
- return (float) $this->value();
- }
- public function valueDouble()
- {
- return (double) $this->value();
- }
- public function valueInt()
- {
- return (int) $this->value();
- }
- public function parent()
- {
- return new self($this->xmlNode->parent());
- }
- /**
- * Find in xml the request
- **/
- public function __get($name)
- {
- return $this->select($name);
- }
- }
|