PageRenderTime 80ms CodeModel.GetById 25ms RepoModel.GetById 0ms app.codeStats 0ms

/sites/all/modules/contrib/querypath/QueryPath/CssEventHandler.php

https://bitbucket.org/antisocnet/drupal
PHP | 1095 lines | 981 code | 114 blank | 0 comment | 65 complexity | 4eb47cda81bf199a0bc82b305ed8d2da MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, LGPL-2.1
  1. <?php
  2. require_once 'CssParser.php';
  3. class QueryPathCssEventHandler implements CssEventHandler {
  4. protected $dom = NULL;
  5. protected $matches = NULL;
  6. protected $alreadyMatched = NULL;
  7. protected $findAnyElement = TRUE;
  8. public function __construct($dom) {
  9. $this->alreadyMatched = new SplObjectStorage();
  10. $matches = new SplObjectStorage();
  11. if (is_array($dom) || $dom instanceof SplObjectStorage) {
  12. foreach($dom as $item) {
  13. if ($item instanceof DOMNode && $item->nodeType == XML_ELEMENT_NODE) {
  14. $matches->attach($item);
  15. }
  16. }
  17. if ($matches->count() > 0) {
  18. $matches->rewind();
  19. $this->dom = $matches->current();
  20. }
  21. else {
  22. $this->dom = NULL;
  23. }
  24. $this->matches = $matches;
  25. }
  26. elseif ($dom instanceof DOMDocument) {
  27. $this->dom = $dom->documentElement;
  28. $matches->attach($dom->documentElement);
  29. }
  30. elseif ($dom instanceof DOMElement) {
  31. $this->dom = $dom;
  32. $matches->attach($dom);
  33. }
  34. elseif ($dom instanceof DOMNodeList) {
  35. $a = array();
  36. foreach ($dom as $item) {
  37. if ($item->nodeType == XML_ELEMENT_NODE) {
  38. $matches->attach($item);
  39. $a[] = $item;
  40. }
  41. }
  42. $this->dom = $a;
  43. }
  44. else {
  45. throw new Exception("Unhandled type: " . get_class($dom));
  46. }
  47. $this->matches = $matches;
  48. }
  49. public function find($filter) {
  50. $parser = new CssParser($filter, $this);
  51. $parser->parse();
  52. return $this;
  53. }
  54. public function getMatches() {
  55. $result = new SplObjectStorage();
  56. foreach($this->alreadyMatched as $m) $result->attach($m);
  57. foreach($this->matches as $m) $result->attach($m);
  58. return $result;
  59. }
  60. public function elementID($id) {
  61. $found = new SplObjectStorage();
  62. $matches = $this->candidateList();
  63. foreach ($matches as $item) {
  64. if ($item->hasAttribute('id') && $item->getAttribute('id') === $id) {
  65. $found->attach($item);
  66. break;
  67. }
  68. }
  69. $this->matches = $found;
  70. $this->findAnyElement = FALSE;
  71. }
  72. public function element($name) {
  73. $matches = $this->candidateList();
  74. $this->findAnyElement = FALSE;
  75. $found = new SplObjectStorage();
  76. foreach ($matches as $item) {
  77. if ($item->tagName == $name) {
  78. $found->attach($item);
  79. }
  80. }
  81. $this->matches = $found;
  82. }
  83. public function elementNS($lname, $namespace = NULL) {
  84. $this->findAnyElement = FALSE;
  85. $found = new SplObjectStorage();
  86. $matches = $this->candidateList();
  87. foreach ($matches as $item) {
  88. $nsuri = $this->dom->lookupNamespaceURI($namespace);
  89. if ($item instanceof DOMNode
  90. && $item->namespaceURI == $nsuri
  91. && $lname == $item->localName) {
  92. $found->attach($item);
  93. }
  94. if (!empty($nsuri)) {
  95. $nl = $item->getElementsByTagNameNS($nsuri, $lname);
  96. if (!empty($nl)) $this->attachNodeList($nl, $found);
  97. }
  98. else {
  99. $nl = $item->getElementsByTagName($lname);
  100. $tagname = $namespace . ':' . $lname;
  101. $nsmatches = array();
  102. foreach ($nl as $node) {
  103. if ($node->tagName == $tagname) {
  104. $found->attach($node);
  105. }
  106. }
  107. }
  108. }
  109. $this->matches = $found;
  110. }
  111. public function anyElement() {
  112. $found = new SplObjectStorage();
  113. $matches = $this->candidateList();
  114. foreach ($matches as $item) {
  115. $found->attach($item);
  116. }
  117. $this->matches = $found;
  118. $this->findAnyElement = FALSE;
  119. }
  120. public function anyElementInNS($ns) {
  121. $nsuri = $this->dom->lookupNamespaceURI($ns);
  122. $found = new SplObjectStorage();
  123. if (!empty($nsuri)) {
  124. $matches = $this->candidateList();
  125. foreach ($matches as $item) {
  126. if ($item instanceOf DOMNode && $nsuri == $item->namespaceURI) {
  127. $found->attach($item);
  128. }
  129. }
  130. }
  131. $this->matches = $found;
  132. $this->findAnyElement = FALSE;
  133. }
  134. public function elementClass($name) {
  135. $found = new SplObjectStorage();
  136. $matches = $this->candidateList();
  137. foreach ($matches as $item) {
  138. if ($item->hasAttribute('class')) {
  139. $classes = explode(' ', $item->getAttribute('class'));
  140. if (in_array($name, $classes)) $found->attach($item);
  141. }
  142. }
  143. $this->matches = $found;
  144. $this->findAnyElement = FALSE;
  145. }
  146. public function attribute($name, $value = NULL, $operation = CssEventHandler::isExactly) {
  147. $found = new SplObjectStorage();
  148. $matches = $this->candidateList();
  149. foreach ($matches as $item) {
  150. if ($item->hasAttribute($name)) {
  151. if (isset($value)) {
  152. if($this->attrValMatches($value, $item->getAttribute($name), $operation)) {
  153. $found->attach($item);
  154. }
  155. }
  156. else {
  157. $found->attach($item);
  158. }
  159. }
  160. }
  161. $this->matches = $found;
  162. $this->findAnyElement = FALSE;
  163. }
  164. protected function searchForAttr($name, $value = NULL) {
  165. $found = new SplObjectStorage();
  166. $matches = $this->candidateList();
  167. foreach ($matches as $candidate) {
  168. if ($candidate->hasAttribute($name)) {
  169. if (isset($value) && $value == $candidate->getAttribute($name)) {
  170. $found->attach($candidate);
  171. }
  172. else {
  173. $found->attach($candidate);
  174. }
  175. }
  176. }
  177. $this->matches = $found;
  178. }
  179. public function attributeNS($lname, $ns, $value = NULL, $operation = CssEventHandler::isExactly) {
  180. $matches = $this->candidateList();
  181. $found = new SplObjectStorage();
  182. if (count($matches) == 0) {
  183. $this->matches = $found;
  184. return;
  185. }
  186. $matches->rewind();
  187. $e = $matches->current();
  188. $uri = $e->lookupNamespaceURI($ns);
  189. foreach ($matches as $item) {
  190. if ($item->hasAttributeNS($uri, $lname)) {
  191. if (isset($value)) {
  192. if ($this->attrValMatches($value, $item->getAttributeNS($uri, $lname), $operation)) {
  193. $found->attach($item);
  194. }
  195. }
  196. else {
  197. $found->attach($item);
  198. }
  199. }
  200. }
  201. $this->matches = $found;
  202. $this->findAnyElement = FALSE;
  203. }
  204. public function pseudoClass($name, $value = NULL) {
  205. $name = strtolower($name);
  206. switch($name) {
  207. case 'visited':
  208. case 'hover':
  209. case 'active':
  210. case 'focus':
  211. case 'animated':
  212. case 'visible':
  213. case 'hidden':
  214. case 'target':
  215. $this->matches = new SplObjectStorage();
  216. break;
  217. case 'indeterminate':
  218. throw new NotImplementedException(":indeterminate is not implemented.");
  219. break;
  220. case 'lang':
  221. if (!isset($value)) {
  222. throw new NotImplementedException("No handler for lang pseudoclass without value.");
  223. }
  224. $this->lang($value);
  225. break;
  226. case 'link':
  227. $this->searchForAttr('href');
  228. break;
  229. case 'root':
  230. $found = new SplObjectStorage();
  231. if (empty($this->dom)) {
  232. $this->matches = $found;
  233. }
  234. elseif (is_array($this->dom)) {
  235. $found->attach($this->dom[0]->ownerDocument->documentElement);
  236. $this->matches = $found;
  237. }
  238. elseif ($this->dom instanceof DOMNode) {
  239. $found->attach($this->dom->ownerDocument->documentElement);
  240. $this->matches = $found;
  241. }
  242. elseif ($this->dom instanceof DOMNodeList && $this->dom->length > 0) {
  243. $found->attach($this->dom->item(0)->ownerDocument->documentElement);
  244. $this->matches = $found;
  245. }
  246. else {
  247. $found->attach($this->dom);
  248. $this->matches = $found;
  249. }
  250. break;
  251. case 'x-root':
  252. case 'x-reset':
  253. $this->matches = new SplObjectStorage();
  254. $this->matches->attach($this->dom);
  255. break;
  256. case 'even':
  257. $this->nthChild(2, 0);
  258. break;
  259. case 'odd':
  260. $this->nthChild(2, 1);
  261. break;
  262. case 'nth-child':
  263. list($aVal, $bVal) = $this->parseAnB($value);
  264. $this->nthChild($aVal, $bVal);
  265. break;
  266. case 'nth-last-child':
  267. list($aVal, $bVal) = $this->parseAnB($value);
  268. $this->nthLastChild($aVal, $bVal);
  269. break;
  270. case 'nth-of-type':
  271. list($aVal, $bVal) = $this->parseAnB($value);
  272. $this->nthOfTypeChild($aVal, $bVal);
  273. break;
  274. case 'nth-last-of-type':
  275. list($aVal, $bVal) = $this->parseAnB($value);
  276. $this->nthLastOfTypeChild($aVal, $bVal);
  277. break;
  278. case 'first-child':
  279. $this->nthChild(0, 1);
  280. break;
  281. case 'last-child':
  282. $this->nthLastChild(0, 1);
  283. break;
  284. case 'first-of-type':
  285. $this->firstOfType();
  286. break;
  287. case 'last-of-type':
  288. $this->lastOfType();
  289. break;
  290. case 'only-child':
  291. $this->onlyChild();
  292. break;
  293. case 'only-of-type':
  294. $this->onlyOfType();
  295. break;
  296. case 'empty':
  297. $this->emptyElement();
  298. break;
  299. case 'not':
  300. if (empty($value)) {
  301. throw new CssParseException(":not() requires a value.");
  302. }
  303. $this->not($value);
  304. break;
  305. case 'lt':
  306. case 'gt':
  307. case 'nth':
  308. case 'eq':
  309. case 'first':
  310. case 'last':
  311. $this->getByPosition($name, $value);
  312. break;
  313. case 'parent':
  314. $matches = $this->candidateList();
  315. $found = new SplObjectStorage();
  316. foreach ($matches as $match) {
  317. if (!empty($match->firstChild)) {
  318. $found->attach($match);
  319. }
  320. }
  321. $this->matches = $found;
  322. break;
  323. case 'enabled':
  324. case 'disabled':
  325. case 'checked':
  326. $this->attribute($name);
  327. break;
  328. case 'text':
  329. case 'radio':
  330. case 'checkbox':
  331. case 'file':
  332. case 'password':
  333. case 'submit':
  334. case 'image':
  335. case 'reset':
  336. case 'button':
  337. case 'submit':
  338. $this->attribute('type', $name);
  339. break;
  340. case 'header':
  341. $matches = $this->candidateList();
  342. $found = new SplObjectStorage();
  343. foreach ($matches as $item) {
  344. $tag = $item->tagName;
  345. $f = strtolower(substr($tag, 0, 1));
  346. if ($f == 'h' && strlen($tag) == 2 && ctype_digit(substr($tag, 1, 1))) {
  347. $found->attach($item);
  348. }
  349. }
  350. $this->matches = $found;
  351. break;
  352. case 'has':
  353. $this->has($value);
  354. break;
  355. case 'contains':
  356. $value = $this->removeQuotes($value);
  357. $matches = $this->candidateList();
  358. $found = new SplObjectStorage();
  359. foreach ($matches as $item) {
  360. if (strpos($item->textContent, $value) !== FALSE) {
  361. $found->attach($item);
  362. }
  363. }
  364. $this->matches = $found;
  365. break;
  366. case 'contains-exactly':
  367. $value = $this->removeQuotes($value);
  368. $matches = $this->candidateList();
  369. $found = new SplObjectStorage();
  370. foreach ($matches as $item) {
  371. if ($item->textContent == $value) {
  372. $found->attach($item);
  373. }
  374. }
  375. $this->matches = $found;
  376. break;
  377. default:
  378. throw new CssParseException("Unknown Pseudo-Class: " . $name);
  379. }
  380. $this->findAnyElement = FALSE;
  381. }
  382. private function removeQuotes($str) {
  383. $f = substr($str, 0, 1);
  384. $l = substr($str, -1);
  385. if ($f === $l && ($f == '"' || $f == "'")) {
  386. $str = substr($str, 1, -1);
  387. }
  388. return $str;
  389. }
  390. private function getByPosition($operator, $pos) {
  391. $matches = $this->candidateList();
  392. $found = new SplObjectStorage();
  393. if ($matches->count() == 0) {
  394. return;
  395. }
  396. switch ($operator) {
  397. case 'nth':
  398. case 'eq':
  399. if ($matches->count() >= $pos) {
  400. foreach ($matches as $match) {
  401. if ($matches->key() + 1 == $pos) {
  402. $found->attach($match);
  403. break;
  404. }
  405. }
  406. }
  407. break;
  408. case 'first':
  409. if ($matches->count() > 0) {
  410. $matches->rewind();
  411. $found->attach($matches->current());
  412. }
  413. break;
  414. case 'last':
  415. if ($matches->count() > 0) {
  416. foreach ($matches as $item) {};
  417. $found->attach($item);
  418. }
  419. break;
  420. case 'lt':
  421. $i = 0;
  422. foreach ($matches as $item) {
  423. if (++$i < $pos) {
  424. $found->attach($item);
  425. }
  426. }
  427. break;
  428. case 'gt':
  429. $i = 0;
  430. foreach ($matches as $item) {
  431. if (++$i > $pos) {
  432. $found->attach($item);
  433. }
  434. }
  435. break;
  436. }
  437. $this->matches = $found;
  438. }
  439. protected function parseAnB($rule) {
  440. if ($rule == 'even') {
  441. return array(2, 0);
  442. }
  443. elseif ($rule == 'odd') {
  444. return array(2, 1);
  445. }
  446. elseif ($rule == 'n') {
  447. return array(1, 0);
  448. }
  449. elseif (is_numeric($rule)) {
  450. return array(0, (int)$rule);
  451. }
  452. $rule = explode('n', $rule);
  453. if (count($rule) == 0) {
  454. throw new CssParseException("nth-child value is invalid.");
  455. }
  456. $aVal = (int)trim($rule[0]);
  457. $bVal = !empty($rule[1]) ? (int)trim($rule[1]) : 0;
  458. return array($aVal, $bVal);
  459. }
  460. protected function nthChild($groupSize, $elementInGroup, $lastChild = FALSE) {
  461. $parents = new SplObjectStorage();
  462. $matches = new SplObjectStorage();
  463. $i = 0;
  464. foreach ($this->matches as $item) {
  465. $parent = $item->parentNode;
  466. if (!$parents->contains($parent)) {
  467. $c = 0;
  468. foreach ($parent->childNodes as $child) {
  469. if ($child->nodeType == XML_ELEMENT_NODE && ($this->findAnyElement || $child->tagName == $item->tagName)) {
  470. $child->nodeIndex = ++$c;
  471. }
  472. }
  473. $parent->numElements = $c;
  474. $parents->attach($parent);
  475. }
  476. if ($lastChild) {
  477. $indexToMatch = $item->parentNode->numElements - $item->nodeIndex + 1;
  478. }
  479. else {
  480. $indexToMatch = $item->nodeIndex;
  481. }
  482. if ($groupSize == 0) {
  483. if ($indexToMatch == $elementInGroup)
  484. $matches->attach($item);
  485. }
  486. else {
  487. if (($indexToMatch - $elementInGroup) % $groupSize == 0
  488. && ($indexToMatch - $elementInGroup) / $groupSize >= 0) {
  489. $matches->attach($item);
  490. }
  491. }
  492. ++$i;
  493. }
  494. $this->matches = $matches;
  495. }
  496. protected function nthLastChild($groupSize, $elementInGroup) {
  497. $this->nthChild($groupSize, $elementInGroup, TRUE);
  498. }
  499. protected function nthOfTypeChild($groupSize, $elementInGroup, $lastChild) {
  500. $parents = new SplObjectStorage();
  501. $matches = new SplObjectStorage();
  502. $i = 0;
  503. foreach ($this->matches as $item) {
  504. $parent = $item->parentNode;
  505. if (!$parents->contains($parent)) {
  506. $c = 0;
  507. foreach ($parent->childNodes as $child) {
  508. if ($child->nodeType == XML_ELEMENT_NODE && $child->tagName == $item->tagName) {
  509. $child->nodeIndex = ++$c;
  510. }
  511. }
  512. $parent->numElements = $c;
  513. $parents->attach($parent);
  514. }
  515. if ($lastChild) {
  516. $indexToMatch = $item->parentNode->numElements - $item->nodeIndex + 1;
  517. }
  518. else {
  519. $indexToMatch = $item->nodeIndex;
  520. }
  521. if ($groupSize == 0) {
  522. if ($indexToMatch == $elementInGroup)
  523. $matches->attach($item);
  524. }
  525. else {
  526. if (($indexToMatch - $elementInGroup) % $groupSize == 0
  527. && ($indexToMatch - $elementInGroup) / $groupSize >= 0) {
  528. $matches->attach($item);
  529. }
  530. }
  531. ++$i;
  532. }
  533. $this->matches = $matches;
  534. }
  535. protected function nthLastOfTypeChild($groupSize, $elementInGroup) {
  536. $this->nthOfTypeChild($groupSize, $elementInGroup, TRUE);
  537. }
  538. protected function lang($value) {
  539. $operator = (strpos($value, '-') !== FALSE) ? self::isExactly : self::containsWithHyphen;
  540. $orig = $this->matches;
  541. $origDepth = $this->findAnyElement;
  542. $this->attribute('lang', $value, $operator);
  543. $lang = $this->matches;
  544. $this->matches = $orig;
  545. $this->findAnyElement = $origDepth;
  546. $this->attributeNS('lang', 'xml', $value, $operator);
  547. foreach ($this->matches as $added) $lang->attach($added);
  548. $this->matches = $lang;
  549. }
  550. protected function not($filter) {
  551. $matches = $this->candidateList();
  552. $found = new SplObjectStorage();
  553. foreach ($matches as $item) {
  554. $handler = new QueryPathCssEventHandler($item);
  555. $not_these = $handler->find($filter)->getMatches();
  556. if ($not_these->count() == 0) {
  557. $found->attach($item);
  558. }
  559. }
  560. $this->matches = $found;
  561. }
  562. public function has($filter) {
  563. $matches = $this->candidateList();
  564. $found = new SplObjectStorage();
  565. foreach ($matches as $item) {
  566. $handler = new QueryPathCssEventHandler($item);
  567. $these = $handler->find($filter)->getMatches();
  568. if (count($these) > 0) {
  569. $found->attach($item);
  570. }
  571. }
  572. $this->matches = $found;
  573. return $this;
  574. }
  575. protected function firstOfType() {
  576. $matches = $this->candidateList();
  577. $found = new SplObjectStorage();
  578. foreach ($matches as $item) {
  579. $type = $item->tagName;
  580. $parent = $item->parentNode;
  581. foreach ($parent->childNodes as $kid) {
  582. if ($kid->nodeType == XML_ELEMENT_NODE && $kid->tagName == $type) {
  583. if (!$found->contains($kid)) {
  584. $found->attach($kid);
  585. }
  586. break;
  587. }
  588. }
  589. }
  590. $this->matches = $found;
  591. }
  592. protected function lastOfType() {
  593. $matches = $this->candidateList();
  594. $found = new SplObjectStorage();
  595. foreach ($matches as $item) {
  596. $type = $item->tagName;
  597. $parent = $item->parentNode;
  598. for ($i = $parent->childNodes->length - 1; $i >= 0; --$i) {
  599. $kid = $parent->childNodes->item($i);
  600. if ($kid->nodeType == XML_ELEMENT_NODE && $kid->tagName == $type) {
  601. if (!$found->contains($kid)) {
  602. $found->attach($kid);
  603. }
  604. break;
  605. }
  606. }
  607. }
  608. $this->matches = $found;
  609. }
  610. protected function onlyChild() {
  611. $matches = $this->candidateList();
  612. $found = new SplObjectStorage();
  613. foreach($matches as $item) {
  614. $parent = $item->parentNode;
  615. $kids = array();
  616. foreach($parent->childNodes as $kid) {
  617. if ($kid->nodeType == XML_ELEMENT_NODE) {
  618. $kids[] = $kid;
  619. }
  620. }
  621. if (count($kids) == 1 && $kids[0] === $item) {
  622. $found->attach($kids[0]);
  623. }
  624. }
  625. $this->matches = $found;
  626. }
  627. protected function emptyElement() {
  628. $found = new SplObjectStorage();
  629. $matches = $this->candidateList();
  630. foreach ($matches as $item) {
  631. $empty = TRUE;
  632. foreach($item->childNodes as $kid) {
  633. if ($kid->nodeType == XML_ELEMENT_NODE || $kid->nodeType == XML_TEXT_NODE) {
  634. $empty = FALSE;
  635. break;
  636. }
  637. }
  638. if ($empty) {
  639. $found->attach($item);
  640. }
  641. }
  642. $this->matches = $found;
  643. }
  644. protected function onlyOfType() {
  645. $matches = $this->candidateList();
  646. $found = new SplObjectStorage();
  647. foreach ($matches as $item) {
  648. if (!$item->parentNode) {
  649. $this->matches = new SplObjectStorage();
  650. }
  651. $parent = $item->parentNode;
  652. $onlyOfType = TRUE;
  653. foreach($parent->childNodes as $kid) {
  654. if ($kid->nodeType == XML_ELEMENT_NODE
  655. && $kid->tagName == $item->tagName
  656. && $kid !== $item) {
  657. $onlyOfType = FALSE;
  658. break;
  659. }
  660. }
  661. if ($onlyOfType) $found->attach($item);
  662. }
  663. $this->matches = $found;
  664. }
  665. protected function attrValMatches($needle, $haystack, $operation) {
  666. if (strlen($haystack) < strlen($needle)) return FALSE;
  667. switch ($operation) {
  668. case CssEventHandler::isExactly:
  669. return $needle == $haystack;
  670. case CssEventHandler::containsWithSpace:
  671. return in_array($needle, explode(' ', $haystack));
  672. case CssEventHandler::containsWithHyphen:
  673. return in_array($needle, explode('-', $haystack));
  674. case CssEventHandler::containsInString:
  675. return strpos($haystack, $needle) !== FALSE;
  676. case CssEventHandler::beginsWith:
  677. return strpos($haystack, $needle) === 0;
  678. case CssEventHandler::endsWith:
  679. return preg_match('/' . $needle . '$/', $haystack) == 1;
  680. }
  681. return FALSE;
  682. }
  683. public function pseudoElement($name) {
  684. switch ($name) {
  685. case 'first-line':
  686. $matches = $this->candidateList();
  687. $found = new SplObjectStorage();
  688. $o = new stdClass();
  689. foreach ($matches as $item) {
  690. $str = $item->textContent;
  691. $lines = explode("\n", $str);
  692. if (!empty($lines)) {
  693. $line = trim($lines[0]);
  694. if (!empty($line))
  695. $o->textContent = $line;
  696. $found->attach($o);
  697. }
  698. }
  699. $this->matches = $found;
  700. break;
  701. case 'first-letter':
  702. $matches = $this->candidateList();
  703. $found = new SplObjectStorage();
  704. $o = new stdClass();
  705. foreach ($matches as $item) {
  706. $str = $item->textContent;
  707. if (!empty($str)) {
  708. $str = substr($str,0, 1);
  709. $o->textContent = $str;
  710. $found->attach($o);
  711. }
  712. }
  713. $this->matches = $found;
  714. break;
  715. case 'before':
  716. case 'after':
  717. case 'selection':
  718. throw new NotImplementedException("The $name pseudo-element is not implemented.");
  719. break;
  720. }
  721. $this->findAnyElement = FALSE;
  722. }
  723. public function directDescendant() {
  724. $this->findAnyElement = FALSE;
  725. $kids = new SplObjectStorage();
  726. foreach ($this->matches as $item) {
  727. $kidsNL = $item->childNodes;
  728. foreach ($kidsNL as $kidNode) {
  729. if ($kidNode->nodeType == XML_ELEMENT_NODE) {
  730. $kids->attach($kidNode);
  731. }
  732. }
  733. }
  734. $this->matches = $kids;
  735. }
  736. public function adjacent() {
  737. $this->findAnyElement = FALSE;
  738. $found = new SplObjectStorage();
  739. foreach ($this->matches as $item) {
  740. if (isset($item->nextSibling) && $item->nextSibling->nodeType === XML_ELEMENT_NODE) {
  741. $found->attach($item->nextSibling);
  742. }
  743. }
  744. $this->matches = $found;
  745. }
  746. public function anotherSelector() {
  747. $this->findAnyElement = FALSE;
  748. if ($this->matches->count() > 0) {
  749. foreach ($this->matches as $item) $this->alreadyMatched->attach($item);
  750. }
  751. $this->findAnyElement = TRUE;
  752. $this->matches = new SplObjectStorage();
  753. $this->matches->attach($this->dom);
  754. }
  755. public function sibling() {
  756. $this->findAnyElement = FALSE;
  757. if ($this->matches->count() > 0) {
  758. $sibs = new SplObjectStorage();
  759. foreach ($this->matches as $item) {
  760. while ($item->nextSibling != NULL) {
  761. $item = $item->nextSibling;
  762. if ($item->nodeType === XML_ELEMENT_NODE) $sibs->attach($item);
  763. }
  764. }
  765. $this->matches = $sibs;
  766. }
  767. }
  768. public function anyDescendant() {
  769. $found = new SplObjectStorage();
  770. foreach ($this->matches as $item) {
  771. $kids = $item->getElementsByTagName('*');
  772. $this->attachNodeList($kids, $found);
  773. }
  774. $this->matches = $found;
  775. $this->findAnyElement = TRUE;
  776. }
  777. private function candidateList() {
  778. if ($this->findAnyElement) {
  779. return $this->getAllCandidates($this->matches);
  780. }
  781. return $this->matches;
  782. }
  783. private function getAllCandidates($elements) {
  784. $found = new SplObjectStorage();
  785. foreach ($elements as $item) {
  786. $found->attach($item);
  787. $nl = $item->getElementsByTagName('*');
  788. $this->attachNodeList($nl, $found);
  789. }
  790. return $found;
  791. }
  792. public function attachNodeList(DOMNodeList $nodeList, SplObjectStorage $splos) {
  793. foreach ($nodeList as $item) $splos->attach($item);
  794. }
  795. }
  796. class NotImplementedException extends Exception {}