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 = XmlNode::parse($data); else if ($data instanceof XmlNode) $this->xmlNode = clone ($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); $result = array(); foreach ($request as $i) $this->checkQuery($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); } }