4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\CssSelector\XPath\Extension;
14 use Symfony\Component\CssSelector\Node;
15 use Symfony\Component\CssSelector\XPath\Translator;
16 use Symfony\Component\CssSelector\XPath\XPathExpr;
19 * XPath expression translator node extension.
21 * This component is a port of the Python cssselect library,
22 * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
24 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
28 class NodeExtension extends AbstractExtension
30 const ELEMENT_NAME_IN_LOWER_CASE = 1;
31 const ATTRIBUTE_NAME_IN_LOWER_CASE = 2;
32 const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4;
39 public function __construct($flags = 0)
41 $this->flags = $flags;
50 public function setFlag($flag, $on)
52 if ($on && !$this->hasFlag($flag)) {
53 $this->flags += $flag;
56 if (!$on && $this->hasFlag($flag)) {
57 $this->flags -= $flag;
68 public function hasFlag($flag)
70 return (bool) ($this->flags & $flag);
76 public function getNodeTranslators()
79 'Selector' => array($this, 'translateSelector'),
80 'CombinedSelector' => array($this, 'translateCombinedSelector'),
81 'Negation' => array($this, 'translateNegation'),
82 'Function' => array($this, 'translateFunction'),
83 'Pseudo' => array($this, 'translatePseudo'),
84 'Attribute' => array($this, 'translateAttribute'),
85 'Class' => array($this, 'translateClass'),
86 'Hash' => array($this, 'translateHash'),
87 'Element' => array($this, 'translateElement'),
94 public function translateSelector(Node\SelectorNode $node, Translator $translator)
96 return $translator->nodeToXPath($node->getTree());
102 public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator)
104 return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector());
110 public function translateNegation(Node\NegationNode $node, Translator $translator)
112 $xpath = $translator->nodeToXPath($node->getSelector());
113 $subXpath = $translator->nodeToXPath($node->getSubSelector());
114 $subXpath->addNameTest();
116 if ($subXpath->getCondition()) {
117 return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition()));
120 return $xpath->addCondition('0');
126 public function translateFunction(Node\FunctionNode $node, Translator $translator)
128 $xpath = $translator->nodeToXPath($node->getSelector());
130 return $translator->addFunction($xpath, $node);
136 public function translatePseudo(Node\PseudoNode $node, Translator $translator)
138 $xpath = $translator->nodeToXPath($node->getSelector());
140 return $translator->addPseudoClass($xpath, $node->getIdentifier());
146 public function translateAttribute(Node\AttributeNode $node, Translator $translator)
148 $name = $node->getAttribute();
149 $safe = $this->isSafeName($name);
151 if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) {
152 $name = strtolower($name);
155 if ($node->getNamespace()) {
156 $name = sprintf('%s:%s', $node->getNamespace(), $name);
157 $safe = $safe && $this->isSafeName($node->getNamespace());
160 $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));
161 $value = $node->getValue();
162 $xpath = $translator->nodeToXPath($node->getSelector());
164 if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) {
165 $value = strtolower($value);
168 return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value);
174 public function translateClass(Node\ClassNode $node, Translator $translator)
176 $xpath = $translator->nodeToXPath($node->getSelector());
178 return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName());
184 public function translateHash(Node\HashNode $node, Translator $translator)
186 $xpath = $translator->nodeToXPath($node->getSelector());
188 return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId());
194 public function translateElement(Node\ElementNode $node)
196 $element = $node->getElement();
198 if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) {
199 $element = strtolower($element);
203 $safe = $this->isSafeName($element);
209 if ($node->getNamespace()) {
210 $element = sprintf('%s:%s', $node->getNamespace(), $element);
211 $safe = $safe && $this->isSafeName($node->getNamespace());
214 $xpath = new XPathExpr('', $element);
217 $xpath->addNameTest();
226 public function getName()
232 * Tests if given name is safe.
234 * @param string $name
238 private function isSafeName($name)
240 return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name);