123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574 |
- <?php
- /*
- * This file is part of the symfony package.
- * (c) 2004-2006 Fabien Potencier <fabien.potencier@symfony-project.com>
- *
- * For the full copyright and license information, please view the LICENSE
- * file that was distributed with this source code.
- */
- /**
- * sfDomCssSelector allows to navigate a DOM with CSS selector.
- *
- * Based on getElementsBySelector version 0.4 - Simon Willison, March 25th 2003
- * http://simon.incutio.com/archive/2003/03/25/getElementsBySelector
- *
- * Some methods based on the jquery library
- *
- * @package symfony
- * @subpackage util
- * @author Fabien Potencier <fabien.potencier@symfony-project.com>
- * @version SVN: $Id: sfDomCssSelector.class.php 14516 2009-01-06 20:07:40Z FabianLange $
- */
- class sfDomCssSelector
- {
- public $nodes = array();
- public function __construct($nodes)
- {
- if (!is_array($nodes))
- {
- $nodes = array($nodes);
- }
- $this->nodes = $nodes;
- }
- public function getNodes()
- {
- return $this->nodes;
- }
- public function getNode()
- {
- return $this->nodes ? $this->nodes[0] : null;
- }
- public function getValue()
- {
- return $this->nodes[0]->nodeValue;
- }
- public function getValues()
- {
- $values = array();
- foreach ($this->nodes as $node)
- {
- $values[] = $node->nodeValue;
- }
- return $values;
- }
- public function matchSingle($selector)
- {
- $nodes = $this->getElements($selector);
- return $nodes ? new sfDomCssSelector($nodes[0]) : new sfDomCssSelector(array());
- }
- public function matchAll($selector)
- {
- $nodes = $this->getElements($selector);
- return $nodes ? new sfDomCssSelector($nodes) : new sfDomCssSelector(array());
- }
- /* DEPRECATED */
- public function getTexts($selector)
- {
- $texts = array();
- foreach ($this->getElements($selector) as $element)
- {
- $texts[] = $element->nodeValue;
- }
- return $texts;
- }
- /* DEPRECATED */
- public function getElements($selector)
- {
- $nodes = array();
- foreach ($this->nodes as $node)
- {
- $result_nodes = $this->getElementsForNode($selector, $node);
- if ($result_nodes)
- {
- $nodes = array_merge($nodes, $result_nodes);
- }
- }
- foreach ($nodes as $node)
- {
- $node->removeAttribute('sf_matched');
- }
- return $nodes;
- }
- protected function getElementsForNode($selector, $root_node)
- {
- $all_nodes = array();
- foreach ($this->tokenize_selectors($selector) as $selector)
- {
- $nodes = array($root_node);
- foreach ($this->tokenize($selector) as $token)
- {
- $combinator = $token['combinator'];
- $selector = $token['selector'];
- $token = trim($token['name']);
- $pos = strpos($token, '#');
- if (false !== $pos && preg_match('/^[A-Za-z0-9]*$/', substr($token, 0, $pos)))
- {
- // Token is an ID selector
- $tagName = substr($token, 0, $pos);
- $id = substr($token, $pos + 1);
- $xpath = new DomXPath($root_node);
- $element = $xpath->query(sprintf("//*[@id = '%s']", $id))->item(0);
- if (!$element || ($tagName && strtolower($element->nodeName) != $tagName))
- {
- // tag with that ID not found
- return array();
- }
- // Set nodes to contain just this element
- $nodes = array($element);
- $nodes = $this->matchMultipleCustomSelectors($nodes, $selector);
- continue; // Skip to next token
- }
- $pos = strpos($token, '.');
- if (false !== $pos && preg_match('/^[A-Za-z0-9\*]*$/', substr($token, 0, $pos)))
- {
- // Token contains a class selector
- $tagName = substr($token, 0, $pos);
- if (!$tagName)
- {
- $tagName = '*';
- }
- $className = substr($token, $pos + 1);
- // Get elements matching tag, filter them for class selector
- $founds = $this->getElementsByTagName($nodes, $tagName, $combinator);
- $nodes = array();
- foreach ($founds as $found)
- {
- if (preg_match('/\b'.$className.'\b/', $found->getAttribute('class')))
- {
- $nodes[] = $found;
- }
- }
- $nodes = $this->matchMultipleCustomSelectors($nodes, $selector);
- continue; // Skip to next token
- }
- // Code to deal with attribute selectors
- if (preg_match('/^(\w+|\*)(\[.+\])$/', $token, $matches))
- {
- $tagName = $matches[1] ? $matches[1] : '*';
- preg_match_all('/
- \[
- ([\w\-]+) # attribute
- ([=~\|\^\$\*]?) # modifier (optional)
- =? # equal (optional)
- (
- "([^"]*)" # quoted value (optional)
- |
- ([^\]]*) # non quoted value (optional)
- )
- \]
- /x', $matches[2], $matches, PREG_SET_ORDER);
- // Grab all of the tagName elements within current node
- $founds = $this->getElementsByTagName($nodes, $tagName, $combinator);
- $nodes = array();
- foreach ($founds as $found)
- {
- $ok = false;
- foreach ($matches as $match)
- {
- $attrName = $match[1];
- $attrOperator = $match[2];
- $attrValue = $match[4];
- switch ($attrOperator)
- {
- case '=': // Equality
- $ok = $found->getAttribute($attrName) == $attrValue;
- break;
- case '~': // Match one of space seperated words
- $ok = preg_match('/\b'.preg_quote($attrValue, '/').'\b/', $found->getAttribute($attrName));
- break;
- case '|': // Match start with value followed by optional hyphen
- $ok = preg_match('/^'.preg_quote($attrValue, '/').'-?/', $found->getAttribute($attrName));
- break;
- case '^': // Match starts with value
- $ok = 0 === strpos($found->getAttribute($attrName), $attrValue);
- break;
- case '$': // Match ends with value
- $ok = $attrValue == substr($found->getAttribute($attrName), -strlen($attrValue));
- break;
- case '*': // Match ends with value
- $ok = false !== strpos($found->getAttribute($attrName), $attrValue);
- break;
- default :
- // Just test for existence of attribute
- $ok = $found->hasAttribute($attrName);
- }
- if (false == $ok)
- {
- break;
- }
- }
- if ($ok)
- {
- $nodes[] = $found;
- }
- }
- continue; // Skip to next token
- }
- // If we get here, token is JUST an element (not a class or ID selector)
- $nodes = $this->getElementsByTagName($nodes, $token, $combinator);
- $nodes = $this->matchMultipleCustomSelectors($nodes, $selector);
- }
- foreach ($nodes as $node)
- {
- if (!$node->getAttribute('sf_matched'))
- {
- $node->setAttribute('sf_matched', true);
- $all_nodes[] = $node;
- }
- }
- }
- return $all_nodes;
- }
- protected function getElementsByTagName($nodes, $tagName, $combinator = ' ')
- {
- $founds = array();
- foreach ($nodes as $node)
- {
- switch ($combinator)
- {
- case ' ':
- // Descendant selector
- foreach ($node->getElementsByTagName($tagName) as $element)
- {
- $founds[] = $element;
- }
- break;
- case '>':
- // Child selector
- foreach ($node->childNodes as $element)
- {
- if ($tagName == $element->nodeName)
- {
- $founds[] = $element;
- }
- }
- break;
- case '+':
- // Adjacent selector
- $element = $node->nextSibling;
- if ($element && '#text' == $element->nodeName)
- {
- $element = $element->nextSibling;
- }
- if ($element && $tagName == $element->nodeName)
- {
- $founds[] = $element;
- }
- break;
- default:
- throw new Exception(sprintf('Unrecognized combinator "%s".', $combinator));
- }
- }
- return $founds;
- }
- protected function tokenize_selectors($selector)
- {
- // split tokens by , except in an attribute selector
- $tokens = array();
- $quoted = false;
- $token = '';
- for ($i = 0, $max = strlen($selector); $i < $max; $i++)
- {
- if (',' == $selector[$i] && !$quoted)
- {
- $tokens[] = trim($token);
- $token = '';
- }
- else if ('"' == $selector[$i])
- {
- $token .= $selector[$i];
- $quoted = $quoted ? false : true;
- }
- else
- {
- $token .= $selector[$i];
- }
- }
- if ($token)
- {
- $tokens[] = trim($token);
- }
- return $tokens;
- }
- protected function tokenize($selector)
- {
- // split tokens by space except if space is in an attribute selector
- $tokens = array();
- $combinators = array(' ', '>', '+');
- $quoted = false;
- $token = array('combinator' => ' ', 'name' => '');
- for ($i = 0, $max = strlen($selector); $i < $max; $i++)
- {
- if (in_array($selector[$i], $combinators) && !$quoted)
- {
- // remove all whitespaces around the combinator
- $combinator = $selector[$i];
- while (in_array($selector[$i + 1], $combinators))
- {
- if (' ' != $selector[++$i])
- {
- $combinator = $selector[$i];
- }
- }
- $tokens[] = $token;
- $token = array('combinator' => $combinator, 'name' => '');
- }
- else if ('"' == $selector[$i])
- {
- $token['name'] .= $selector[$i];
- $quoted = $quoted ? false : true;
- }
- else
- {
- $token['name'] .= $selector[$i];
- }
- }
- if ($token['name'])
- {
- $tokens[] = $token;
- }
- foreach ($tokens as &$token)
- {
- list($token['name'], $token['selector']) = $this->tokenize_selector_name($token['name']);
- }
- return $tokens;
- }
- protected function tokenize_selector_name($token_name)
- {
- // split custom selector
- $quoted = false;
- $name = '';
- $selector = '';
- $in_selector = false;
- for ($i = 0, $max = strlen($token_name); $i < $max; $i++)
- {
- if ('"' == $token_name[$i])
- {
- $quoted = $quoted ? false : true;
- }
- if (!$quoted && ':' == $token_name[$i])
- {
- $in_selector = true;
- }
- if ($in_selector)
- {
- $selector .= $token_name[$i];
- }
- else
- {
- $name .= $token_name[$i];
- }
- }
- return array($name, $selector);
- }
- protected function matchMultipleCustomSelectors($nodes, $selector)
- {
- if (!$selector)
- {
- return $nodes;
- }
- foreach ($this->split_custom_selector($selector) as $selector) {
- $nodes = $this->matchCustomSelector($nodes, $selector);
- }
- return $nodes;
- }
- protected function matchCustomSelector($nodes, $selector)
- {
- if (!$selector)
- {
- return $nodes;
- }
- $selector = $this->tokenize_custom_selector($selector);
- $matchingNodes = array();
- for ($i = 0, $max = count($nodes); $i < $max; $i++)
- {
- switch ($selector['selector'])
- {
- case 'contains':
- if (false !== strpos($nodes[$i]->textContent, $selector['parameter']))
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'nth-child':
- if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->firstChild, (integer) $selector['parameter']))
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'first-child':
- if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->firstChild))
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'last-child':
- if ($nodes[$i] === $this->nth($nodes[$i]->parentNode->lastChild, 1, 'previousSibling'))
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'lt':
- if ($i < (integer) $selector['parameter'])
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'gt':
- if ($i > (integer) $selector['parameter'])
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'odd':
- if ($i % 2)
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'even':
- if (0 == $i % 2)
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'nth':
- case 'eq':
- if ($i == (integer) $selector['parameter'])
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'first':
- if ($i == 0)
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- case 'last':
- if ($i == $max - 1)
- {
- $matchingNodes[] = $nodes[$i];
- }
- break;
- default:
- throw new Exception(sprintf('Unrecognized selector "%s".', $selector['selector']));
- }
- }
- return $matchingNodes;
- }
- protected function split_custom_selector($selectors)
- {
- if (!preg_match_all('/
- :
- (?:[a-zA-Z0-9\-]+)
- (?:
- \(
- (?:
- ("|\')(?:.*?)?\1
- |
- (?:.*?)
- )
- \)
- )?
- /x', $selectors, $matches, PREG_PATTERN_ORDER))
- {
- throw new Exception(sprintf('Unable to split custom selector "%s".', $selectors));
- }
- return $matches[0];
- }
- protected function tokenize_custom_selector($selector)
- {
- if (!preg_match('/
- ([a-zA-Z0-9\-]+)
- (?:
- \(
- (?:
- ("|\')(.*)?\2
- |
- (.*?)
- )
- \)
- )?
- /x', substr($selector, 1), $matches))
- {
- throw new Exception(sprintf('Unable to parse custom selector "%s".', $selector));
- }
- return array('selector' => $matches[1], 'parameter' => isset($matches[3]) ? ($matches[3] ? $matches[3] : $matches[4]) : '');
- }
- protected function nth($cur, $result = 1, $dir = 'nextSibling')
- {
- $num = 0;
- for (; $cur; $cur = $cur->$dir)
- {
- if (1 == $cur->nodeType)
- {
- ++$num;
- }
- if ($num == $result)
- {
- return $cur;
- }
- }
- }
- }
|