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;
44 public function __construct($flags = 0)
46 $this->flags = $flags;
55 public function setFlag($flag, $on)
57 if ($on && !$this->hasFlag($flag)) {
58 $this->flags += $flag;
61 if (!$on && $this->hasFlag($flag)) {
62 $this->flags -= $flag;
73 public function hasFlag($flag)
75 return (bool) ($this->flags & $flag);
81 public function getNodeTranslators()
84 'Selector' => array($this, 'translateSelector'),
85 'CombinedSelector' => array($this, 'translateCombinedSelector'),
86 'Negation' => array($this, 'translateNegation'),
87 'Function' => array($this, 'translateFunction'),
88 'Pseudo' => array($this, 'translatePseudo'),
89 'Attribute' => array($this, 'translateAttribute'),
90 'Class' => array($this, 'translateClass'),
91 'Hash' => array($this, 'translateHash'),
92 'Element' => array($this, 'translateElement'),
97 * @param Node\SelectorNode $node
98 * @param Translator $translator
102 public function translateSelector(Node\SelectorNode $node, Translator $translator)
104 return $translator->nodeToXPath($node->getTree());
108 * @param Node\CombinedSelectorNode $node
109 * @param Translator $translator
113 public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator)
115 return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector());
119 * @param Node\NegationNode $node
120 * @param Translator $translator
124 public function translateNegation(Node\NegationNode $node, Translator $translator)
126 $xpath = $translator->nodeToXPath($node->getSelector());
127 $subXpath = $translator->nodeToXPath($node->getSubSelector());
128 $subXpath->addNameTest();
130 if ($subXpath->getCondition()) {
131 return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition()));
134 return $xpath->addCondition('0');
138 * @param Node\FunctionNode $node
139 * @param Translator $translator
143 public function translateFunction(Node\FunctionNode $node, Translator $translator)
145 $xpath = $translator->nodeToXPath($node->getSelector());
147 return $translator->addFunction($xpath, $node);
151 * @param Node\PseudoNode $node
152 * @param Translator $translator
156 public function translatePseudo(Node\PseudoNode $node, Translator $translator)
158 $xpath = $translator->nodeToXPath($node->getSelector());
160 return $translator->addPseudoClass($xpath, $node->getIdentifier());
164 * @param Node\AttributeNode $node
165 * @param Translator $translator
169 public function translateAttribute(Node\AttributeNode $node, Translator $translator)
171 $name = $node->getAttribute();
172 $safe = $this->isSafeName($name);
174 if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) {
175 $name = strtolower($name);
178 if ($node->getNamespace()) {
179 $name = sprintf('%s:%s', $node->getNamespace(), $name);
180 $safe = $safe && $this->isSafeName($node->getNamespace());
183 $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));
184 $value = $node->getValue();
185 $xpath = $translator->nodeToXPath($node->getSelector());
187 if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) {
188 $value = strtolower($value);
191 return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value);
195 * @param Node\ClassNode $node
196 * @param Translator $translator
200 public function translateClass(Node\ClassNode $node, Translator $translator)
202 $xpath = $translator->nodeToXPath($node->getSelector());
204 return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName());
208 * @param Node\HashNode $node
209 * @param Translator $translator
213 public function translateHash(Node\HashNode $node, Translator $translator)
215 $xpath = $translator->nodeToXPath($node->getSelector());
217 return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId());
221 * @param Node\ElementNode $node
225 public function translateElement(Node\ElementNode $node)
227 $element = $node->getElement();
229 if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) {
230 $element = strtolower($element);
234 $safe = $this->isSafeName($element);
240 if ($node->getNamespace()) {
241 $element = sprintf('%s:%s', $node->getNamespace(), $element);
242 $safe = $safe && $this->isSafeName($node->getNamespace());
245 $xpath = new XPathExpr('', $element);
248 $xpath->addNameTest();
257 public function getName()
263 * Tests if given name is safe.
265 * @param string $name
269 private function isSafeName($name)
271 return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name);