xmlStroller.php 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. <?php
  2. class XmlNode extends SimpleXmlIterator
  3. {
  4. public function parent()
  5. { return $this->xpath(".."); }
  6. }
  7. class XmlStroller
  8. {
  9. private $xmlNode;
  10. private $fncList = array(
  11. "empty" => false,
  12. "first-child" => false,
  13. "first-of-type" => false,
  14. "last-child" => false,
  15. "last-of-type" => false,
  16. "not" => true,
  17. "nth-child" => true,
  18. "nth-last-child" => true,
  19. "nth-last-of-type" => true,
  20. "nth-of-type" => true,
  21. "only-of-type" => false,
  22. "only-child" => false,
  23. "root" => false
  24. );
  25. public $debug = false;
  26. /**
  27. * Create new Stroller for xml $data
  28. * @param $data mixed xml data to parse
  29. * if $data is a string, implement SimpleXmlIterator to evaluate it
  30. * if $data is a SimpleXmlIterator
  31. **/
  32. public function __construct($data)
  33. {
  34. if (is_string($data))
  35. $this->xmlNode = new \XmlNode($data);
  36. else if ($data instanceof \XmlNode)
  37. $this->xmlNode = $data;
  38. }
  39. private function getTokens($str)
  40. {
  41. $tokens = preg_split("/([^A-Za-z0-9\*\-\.:])+/", $str, -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY | PREG_SPLIT_OFFSET_CAPTURE);
  42. $tokens_final = array();
  43. $nbTokens = count($tokens);
  44. for ($i = 0; $i < $nbTokens; $i++)
  45. {
  46. $tokens_final[] = $tokens[$i][0];
  47. if ($i < $nbTokens -1)
  48. {
  49. $tokenOffsetRight = strlen($tokens[$i][0]) + $tokens[$i][1];
  50. if ($tokenOffsetRight < $tokens[$i +1][1])
  51. {
  52. $token = substr($str, $tokenOffsetRight, $tokens[$i +1][1] - $tokenOffsetRight);
  53. $t = trim($token);
  54. if (ltrim($token) != $token)
  55. $tokens_final[] = " ";
  56. if (!empty($t))
  57. $tokens_final[] = $t;
  58. if (rtrim($token) != $token)
  59. $tokens_final[] = " ";
  60. }
  61. }
  62. }
  63. $tokens_final[] = ',';
  64. return $tokens_final;
  65. }
  66. private function compile($str)
  67. {
  68. $tokens = $this->getTokens($str);
  69. $result = array();
  70. $currentBranch = array();
  71. $nbTokens = count($tokens);
  72. $childTokenDictionnary = array(">", "+", "~", " ");
  73. $inAttribute = false;
  74. $attrs = array();
  75. $inToken = false;
  76. $inParam = false;
  77. for ($i=0; $i < $nbTokens; $i++)
  78. {
  79. if ($tokens[$i] == ',')
  80. {
  81. if (!empty($attrs))
  82. $currentBranch[] = array('', array( "value" => "", "fnc" => array(), "attr" => $attrs ));
  83. $attrs = false;
  84. $result[] = $currentBranch;
  85. $currentBranch = array();
  86. }
  87. else if ($tokens[$i] == '[' && $inAttribute)
  88. {
  89. $expected = ']';
  90. if (!$inAttribute["key"])
  91. $expected = 'key';
  92. else if (!$inAttribute["sep"])
  93. $expected = 'separator or \']\'';
  94. else if (!$inAttribute["value"])
  95. $expected = 'value';
  96. throw new \Exception("Unexpected token '[', expected {$expected}");
  97. }
  98. else if ($tokens[$i] == '[' && !$inAttribute)
  99. {
  100. $params = explode(':', $tokens[$i]);
  101. $inAttribute = array( "key" => null, "sep" => false, "value" => null );
  102. }
  103. else if ($inAttribute)
  104. {
  105. if ($tokens[$i] == ']')
  106. {
  107. if (!$inAttribute["key"])
  108. throw new \Exception("Unexpected token ']', expected name");
  109. if ($inAttribute["sep"] !== false && $inAttribute["value"] === null)
  110. throw new \Exception("Unexpected token ']', expected value");
  111. if ($inToken !== false)
  112. $attrs[] = $inAttribute;
  113. else
  114. $currentBranch[count($currentBranch) -1][1]["attr"][] = $inAttribute;
  115. $inAttribute = false;
  116. }
  117. else
  118. {
  119. if (!$inAttribute["key"])
  120. $inAttribute["key"] = $tokens[$i];
  121. else if (!$inAttribute["sep"])
  122. $inAttribute["sep"] = $tokens[$i];
  123. else if (!$inAttribute["value"])
  124. $inAttribute["value"] = $tokens[$i];
  125. }
  126. }
  127. else if (in_array($tokens[$i], $childTokenDictionnary) || empty(trim($tokens[$i])))
  128. {
  129. if ($inToken && !empty(trim($tokens[$i])))
  130. throw new \Exception("Unexpected token '{$tokens[$i]}', expected name");
  131. elseif ($inToken)
  132. continue;
  133. $inToken = empty(trim($tokens[$i])) ? '' : $tokens[$i];
  134. }
  135. else
  136. {
  137. $params = explode(':', $tokens[$i]);
  138. $currentBranch[] = array($inToken === false ? '' : $inToken, array("value" => $params[0], "fnc" => array_slice($params, 1), "attr" => $attrs));
  139. $inToken = false;
  140. $attrs = array();
  141. }
  142. }
  143. if ($attrs !== false)
  144. $result[count($result) -1] = array('', array( "value" => "", "fnc" => array(), "attr" => $attrs ));
  145. return $result;
  146. }
  147. private function checkAttr($item, $attr)
  148. {
  149. $attributes = $item->attributes();
  150. if ($attr["sep"] == "=")
  151. return isset($attributes[$attr["key"]]) && $attributes[$attr["key"]] == $attr["value"];
  152. else if ($attr["sep"] === false)
  153. return isset($attributes[$attr["key"]]);
  154. return false;
  155. }
  156. private function checkQuery($items, &$result, $criteria)
  157. {
  158. if (empty($criteria))
  159. {
  160. $result[] = $items;
  161. return;
  162. }
  163. $childCriteria = array_slice($criteria, 1);
  164. foreach ($items as $i)
  165. {
  166. //Check if items match $criteria[0]
  167. if (($criteria[0][0] == '' || $criteria[0][0] == '>') && ($i->getName() == $criteria[0][1]["value"] || $criteria[0][1]["value"] == '' || $criteria[0][1]["value"] == '*'))
  168. {
  169. foreach ($criteria[0][1]["fnc"] as $fnc)
  170. {
  171. if (!array_key_exists($fnc, $this->fncList))
  172. throw new Exception("Unexpected token '{$fnc}', expected function name");
  173. //TODO check fnc parameters
  174. //TODO exec fnc
  175. }
  176. $attr = true;
  177. foreach ($criteria[0][1]["attr"] as $attr)
  178. {
  179. if (!$this->checkAttr($i, $attr))
  180. {
  181. $attr = false;
  182. break;
  183. }
  184. }
  185. if ($attr)
  186. $this->checkQuery($i, $result, $childCriteria);
  187. }
  188. else if ($criteria[0][0] == '')
  189. $this->checkQuery($i, $result, $criteria);
  190. //TODO ~, +
  191. }
  192. }
  193. /**
  194. * Find in xml the request
  195. **/
  196. public function select($criteria_str)
  197. {
  198. $request = $this->compile($criteria_str);
  199. if ($this->debug) { echo "Compiler output: "; var_dump($request); }
  200. $result = array();
  201. foreach ($request as $i)
  202. $this->checkQuery(array($this->xmlNode), $result, $i);
  203. $r = array();
  204. foreach ($result as $i)
  205. $r[] = new self($i);
  206. return $r;
  207. }
  208. public function getAttributes()
  209. {
  210. $result = array();
  211. foreach ($this->xmlNode->attributes() as $i => $j)
  212. $result[$i] = (string) $j;
  213. return $result;
  214. }
  215. public function attr($key =null, $defaultValue =null)
  216. {
  217. if ($key == null)
  218. return $this->getAttributes();
  219. if (isset($this->xmlNode->attributes()[$key]))
  220. return (string)$this->xmlNode->attributes()[$key];
  221. return $defaultValue;
  222. }
  223. public function valueString()
  224. {
  225. return $this->value;
  226. }
  227. public function __tostring()
  228. {
  229. return $this->valueString();
  230. }
  231. public function value()
  232. {
  233. return (string) $this->xmlNode;
  234. }
  235. public function valueFloat()
  236. {
  237. return (float) $this->value();
  238. }
  239. public function valueDouble()
  240. {
  241. return (double) $this->value();
  242. }
  243. public function valueInt()
  244. {
  245. return (int) $this->value();
  246. }
  247. public function parent()
  248. {
  249. return new self($this->xmlNode->parent());
  250. }
  251. /**
  252. * Find in xml the request
  253. **/
  254. public function __get($name)
  255. {
  256. return $this->select($name);
  257. }
  258. }