xmlStroller.php 7.5 KB

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