|
|
@@ -0,0 +1,279 @@
|
|
|
+<?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);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|