PageRenderTime 43ms CodeModel.GetById 16ms RepoModel.GetById 0ms app.codeStats 0ms

/vendor/swiftmailer/swiftmailer/test-suite/lib/simpletest/extensions/dom_tester/css_selector.php

https://bitbucket.org/laborautonomo/laborautonomo-site
PHP | 539 lines | 445 code | 55 blank | 39 comment | 78 complexity | 67d9a42800da41adce0248e1f57f12d4 MD5 | raw file
  1. <?php
  2. /**
  3. * @package SimpleTest
  4. * @subpackage Extensions
  5. * @author Perrick Penet <perrick@noparking.net>
  6. * @version $Id: css_selector.php 1802 2008-09-08 10:43:58Z maetl_ $
  7. */
  8. /**
  9. * CssSelector
  10. *
  11. * Allow to navigate a DOM with CSS selector.
  12. *
  13. * based on getElementsBySelector version 0.4 - Simon Willison, 2003-03-25
  14. * http://simon.incutio.com/archive/2003/03/25/getElementsBySelector
  15. *
  16. * derived from sfDomCssSelector Id 3053 - Fabien Potencier, 2006-12-16
  17. * http://www.symfony-project.com/api/symfony/util/sfDomCssSelector.html
  18. *
  19. * @package SimpleTest
  20. * @subpackage Extensions
  21. * @param DomDocument $dom
  22. */
  23. class CssSelector
  24. {
  25. public $nodes = array();
  26. public function __construct($nodes)
  27. {
  28. if (!is_array($nodes))
  29. {
  30. $nodes = array($nodes);
  31. }
  32. $this->nodes = $nodes;
  33. }
  34. public function getNodes()
  35. {
  36. return $this->nodes;
  37. }
  38. public function getNode()
  39. {
  40. return $this->nodes ? $this->nodes[0] : null;
  41. }
  42. public function getValue()
  43. {
  44. return $this->nodes[0]->nodeValue;
  45. }
  46. public function getValues()
  47. {
  48. $values = array();
  49. foreach ($this->nodes as $node)
  50. {
  51. $values[] = $node->nodeValue;
  52. }
  53. return $values;
  54. }
  55. public function matchSingle($selector)
  56. {
  57. $nodes = $this->getElements($selector);
  58. return $nodes ? new CssSelector($nodes[0]) : new CssSelector(array());
  59. }
  60. public function matchAll($selector)
  61. {
  62. $nodes = $this->getElements($selector);
  63. return $nodes ? new CssSelector($nodes) : new CssSelector(array());
  64. }
  65. /* DEPRECATED */
  66. public function getTexts($selector)
  67. {
  68. $texts = array();
  69. foreach ($this->getElements($selector) as $element)
  70. {
  71. $texts[] = $element->nodeValue;
  72. }
  73. return $texts;
  74. }
  75. /* DEPRECATED */
  76. public function getElements($selector)
  77. {
  78. $nodes = array();
  79. foreach ($this->nodes as $node)
  80. {
  81. $result_nodes = $this->getElementsForNode($selector, $node);
  82. if ($result_nodes)
  83. {
  84. $nodes = array_merge($nodes, $result_nodes);
  85. }
  86. }
  87. foreach ($nodes as $node)
  88. {
  89. $node->removeAttribute('sf_matched');
  90. }
  91. return $nodes;
  92. }
  93. protected function getElementsForNode($selector, $root_node)
  94. {
  95. $all_nodes = array();
  96. foreach ($this->tokenize_selectors($selector) as $selector)
  97. {
  98. $nodes = array($root_node);
  99. foreach ($this->tokenize($selector) as $token)
  100. {
  101. $combinator = $token['combinator'];
  102. $selector = $token['selector'];
  103. $token = trim($token['name']);
  104. $pos = strpos($token, '#');
  105. if (false !== $pos && preg_match('/^[A-Za-z0-9]*$/', substr($token, 0, $pos)))
  106. {
  107. // Token is an ID selector
  108. $tagName = substr($token, 0, $pos);
  109. $id = substr($token, $pos + 1);
  110. $xpath = new DomXPath($root_node);
  111. $element = $xpath->query(sprintf("//*[@id = '%s']", $id))->item(0);
  112. if (!$element || ($tagName && strtolower($element->nodeName) != $tagName))
  113. {
  114. // tag with that ID not found
  115. return array();
  116. }
  117. // Set nodes to contain just this element
  118. $nodes = array($element);
  119. $nodes = $this->matchCustomSelector($nodes, $selector);
  120. continue; // Skip to next token
  121. }
  122. $pos = strpos($token, '.');
  123. if (false !== $pos && preg_match('/^[A-Za-z0-9\*]*$/', substr($token, 0, $pos)))
  124. {
  125. // Token contains a class selector
  126. $tagName = substr($token, 0, $pos);
  127. if (!$tagName)
  128. {
  129. $tagName = '*';
  130. }
  131. $className = substr($token, $pos + 1);
  132. // Get elements matching tag, filter them for class selector
  133. $founds = $this->getElementsByTagName($nodes, $tagName, $combinator);
  134. $nodes = array();
  135. foreach ($founds as $found)
  136. {
  137. if (preg_match('/\b'.$className.'\b/', $found->getAttribute('class')))
  138. {
  139. $nodes[] = $found;
  140. }
  141. }
  142. $nodes = $this->matchCustomSelector($nodes, $selector);
  143. continue; // Skip to next token
  144. }
  145. // Code to deal with attribute selectors
  146. if (preg_match('/^(\w+|\*)(\[.+\])$/', $token, $matches))
  147. {
  148. $tagName = $matches[1] ? $matches[1] : '*';
  149. preg_match_all('/
  150. \[
  151. (\w+) # attribute
  152. ([=~\|\^\$\*]?) # modifier (optional)
  153. =? # equal (optional)
  154. (
  155. "([^"]*)" # quoted value (optional)
  156. |
  157. ([^\]]*) # non quoted value (optional)
  158. )
  159. \]
  160. /x', $matches[2], $matches, PREG_SET_ORDER);
  161. // Grab all of the tagName elements within current node
  162. $founds = $this->getElementsByTagName($nodes, $tagName, $combinator);
  163. $nodes = array();
  164. foreach ($founds as $found)
  165. {
  166. $ok = false;
  167. foreach ($matches as $match)
  168. {
  169. $attrName = $match[1];
  170. $attrOperator = $match[2];
  171. $attrValue = $match[4];
  172. switch ($attrOperator)
  173. {
  174. case '=': // Equality
  175. $ok = $found->getAttribute($attrName) == $attrValue;
  176. break;
  177. case '~': // Match one of space seperated words
  178. $ok = preg_match('/\b'.preg_quote($attrValue, '/').'\b/', $found->getAttribute($attrName));
  179. break;
  180. case '|': // Match start with value followed by optional hyphen
  181. $ok = preg_match('/^'.preg_quote($attrValue, '/').'-?/', $found->getAttribute($attrName));
  182. break;
  183. case '^': // Match starts with value
  184. $ok = 0 === strpos($found->getAttribute($attrName), $attrValue);
  185. break;
  186. case '$': // Match ends with value
  187. $ok = $attrValue == substr($found->getAttribute($attrName), -strlen($attrValue));
  188. break;
  189. case '*': // Match ends with value
  190. $ok = false !== strpos($found->getAttribute($attrName), $attrValue);
  191. break;
  192. default :
  193. // Just test for existence of attribute
  194. $ok = $found->hasAttribute($attrName);
  195. }
  196. if (false == $ok)
  197. {
  198. break;
  199. }
  200. }
  201. if ($ok)
  202. {
  203. $nodes[] = $found;
  204. }
  205. }
  206. continue; // Skip to next token
  207. }
  208. // If we get here, token is JUST an element (not a class or ID selector)
  209. $nodes = $this->getElementsByTagName($nodes, $token, $combinator);
  210. $nodes = $this->matchCustomSelector($nodes, $selector);
  211. }
  212. foreach ($nodes as $node)
  213. {
  214. if (!$node->getAttribute('sf_matched'))
  215. {
  216. $node->setAttribute('sf_matched', true);
  217. $all_nodes[] = $node;
  218. }
  219. }
  220. }
  221. return $all_nodes;
  222. }
  223. protected function getElementsByTagName($nodes, $tagName, $combinator = ' ')
  224. {
  225. $founds = array();
  226. foreach ($nodes as $node)
  227. {
  228. switch ($combinator)
  229. {
  230. case ' ':
  231. // Descendant selector
  232. foreach ($node->getElementsByTagName($tagName) as $element)
  233. {
  234. $founds[] = $element;
  235. }
  236. break;
  237. case '>':
  238. // Child selector
  239. foreach ($node->childNodes as $element)
  240. {
  241. if ($tagName == $element->nodeName)
  242. {
  243. $founds[] = $element;
  244. }
  245. }
  246. break;
  247. case '+':
  248. // Adjacent selector
  249. $element = $node->nextSibling;
  250. if ($element && '#text' == $element->nodeName)
  251. {
  252. $element = $element->nextSibling;
  253. }
  254. if ($element && $tagName == $element->nodeName)
  255. {
  256. $founds[] = $element;
  257. }
  258. break;
  259. default:
  260. throw new Exception(sprintf('Unrecognized combinator "%s".', $combinator));
  261. }
  262. }
  263. return $founds;
  264. }
  265. protected function tokenize_selectors($selector)
  266. {
  267. // split tokens by , except in an attribute selector
  268. $tokens = array();
  269. $quoted = false;
  270. $token = '';
  271. for ($i = 0, $max = strlen($selector); $i < $max; $i++)
  272. {
  273. if (',' == $selector[$i] && !$quoted)
  274. {
  275. $tokens[] = trim($token);
  276. $token = '';
  277. }
  278. else if ('"' == $selector[$i])
  279. {
  280. $token .= $selector[$i];
  281. $quoted = $quoted ? false : true;
  282. }
  283. else
  284. {
  285. $token .= $selector[$i];
  286. }
  287. }
  288. if ($token)
  289. {
  290. $tokens[] = trim($token);
  291. }
  292. return $tokens;
  293. }
  294. protected function tokenize($selector)
  295. {
  296. // split tokens by space except if space is in an attribute selector
  297. $tokens = array();
  298. $combinators = array(' ', '>', '+');
  299. $quoted = false;
  300. $token = array('combinator' => ' ', 'name' => '');
  301. for ($i = 0, $max = strlen($selector); $i < $max; $i++)
  302. {
  303. if (in_array($selector[$i], $combinators) && !$quoted)
  304. {
  305. // remove all whitespaces around the combinator
  306. $combinator = $selector[$i];
  307. while (in_array($selector[$i + 1], $combinators))
  308. {
  309. if (' ' != $selector[++$i])
  310. {
  311. $combinator = $selector[$i];
  312. }
  313. }
  314. $tokens[] = $token;
  315. $token = array('combinator' => $combinator, 'name' => '');
  316. }
  317. else if ('"' == $selector[$i])
  318. {
  319. $token['name'] .= $selector[$i];
  320. $quoted = $quoted ? false : true;
  321. }
  322. else
  323. {
  324. $token['name'] .= $selector[$i];
  325. }
  326. }
  327. if ($token['name'])
  328. {
  329. $tokens[] = $token;
  330. }
  331. foreach ($tokens as &$token)
  332. {
  333. list($token['name'], $token['selector']) = $this->tokenize_selector_name($token['name']);
  334. }
  335. return $tokens;
  336. }
  337. protected function tokenize_selector_name($token_name)
  338. {
  339. // split custom selector
  340. $quoted = false;
  341. $name = '';
  342. $selector = '';
  343. $in_selector = false;
  344. for ($i = 0, $max = strlen($token_name); $i < $max; $i++)
  345. {
  346. if ('"' == $token_name[$i])
  347. {
  348. $quoted = $quoted ? false : true;
  349. }
  350. if (!$quoted && ':' == $token_name[$i])
  351. {
  352. $in_selector = true;
  353. }
  354. if ($in_selector)
  355. {
  356. $selector .= $token_name[$i];
  357. }
  358. else
  359. {
  360. $name .= $token_name[$i];
  361. }
  362. }
  363. return array($name, $selector);
  364. }
  365. protected function matchCustomSelector($nodes, $selector)
  366. {
  367. if (!$selector)
  368. {
  369. return $nodes;
  370. }
  371. $selector = $this->tokenize_custom_selector($selector);
  372. $matchingNodes = array();
  373. for ($i = 0, $max = count($nodes); $i < $max; $i++)
  374. {
  375. switch ($selector['selector'])
  376. {
  377. case 'contains':
  378. if (false !== strpos($nodes[$i]->textContent, $selector['parameter']))
  379. {
  380. $matchingNodes[] = $nodes[$i];
  381. }
  382. break;
  383. case 'nth-child':
  384. if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->firstChild, (integer) $selector['parameter']))
  385. {
  386. $matchingNodes[] = $nodes[$i];
  387. }
  388. break;
  389. case 'first-child':
  390. if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->firstChild))
  391. {
  392. $matchingNodes[] = $nodes[$i];
  393. }
  394. break;
  395. case 'last-child':
  396. if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->lastChild, 1, 'previousSibling'))
  397. {
  398. $matchingNodes[] = $nodes[$i];
  399. }
  400. break;
  401. case 'lt':
  402. if ($i < (integer) $selector['parameter'])
  403. {
  404. $matchingNodes[] = $nodes[$i];
  405. }
  406. break;
  407. case 'gt':
  408. if ($i > (integer) $selector['parameter'])
  409. {
  410. $matchingNodes[] = $nodes[$i];
  411. }
  412. break;
  413. case 'odd':
  414. if ($i % 2)
  415. {
  416. $matchingNodes[] = $nodes[$i];
  417. }
  418. break;
  419. case 'even':
  420. if (0 == $i % 2)
  421. {
  422. $matchingNodes[] = $nodes[$i];
  423. }
  424. break;
  425. case 'nth':
  426. case 'eq':
  427. if ($i == (integer) $selector['parameter'])
  428. {
  429. $matchingNodes[] = $nodes[$i];
  430. }
  431. break;
  432. case 'first':
  433. if ($i == 0)
  434. {
  435. $matchingNodes[] = $nodes[$i];
  436. }
  437. break;
  438. case 'last':
  439. if ($i == $max - 1)
  440. {
  441. $matchingNodes[] = $nodes[$i];
  442. }
  443. break;
  444. default:
  445. throw new Exception(sprintf('Unrecognized selector "%s".', $selector['selector']));
  446. }
  447. }
  448. return $matchingNodes;
  449. }
  450. protected function tokenize_custom_selector($selector)
  451. {
  452. if (!preg_match('/
  453. ([a-zA-Z0-9\-]+)
  454. (?:
  455. \(
  456. (?:
  457. ("|\')(.*)?\2
  458. |
  459. (.*?)
  460. )
  461. \)
  462. )?
  463. /x', substr($selector, 1), $matches))
  464. {
  465. throw new Exception(sprintf('Unable to parse custom selector "%s".', $selector));
  466. }
  467. return array('selector' => $matches[1], 'parameter' => isset($matches[3]) ? ($matches[3] ? $matches[3] : $matches[4]) : '');
  468. }
  469. protected function nth($cur, $result = 1, $dir = 'nextSibling')
  470. {
  471. $num = 0;
  472. for (; $cur; $cur = $cur->$dir)
  473. {
  474. if (1 == $cur->nodeType)
  475. {
  476. ++$num;
  477. }
  478. if ($num == $result)
  479. {
  480. return $cur;
  481. }
  482. }
  483. }
  484. }