Chris@0: Chris@0: * Chris@0: * For the full copyright and license information, please view the LICENSE Chris@0: * file that was distributed with this source code. Chris@0: */ Chris@0: Chris@0: namespace Symfony\Component\CssSelector\XPath\Extension; Chris@0: Chris@0: use Symfony\Component\CssSelector\Node; Chris@0: use Symfony\Component\CssSelector\XPath\Translator; Chris@0: use Symfony\Component\CssSelector\XPath\XPathExpr; Chris@0: Chris@0: /** Chris@0: * XPath expression translator node extension. Chris@0: * Chris@0: * This component is a port of the Python cssselect library, Chris@0: * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. Chris@0: * Chris@0: * @author Jean-François Simon Chris@0: * Chris@0: * @internal Chris@0: */ Chris@0: class NodeExtension extends AbstractExtension Chris@0: { Chris@0: const ELEMENT_NAME_IN_LOWER_CASE = 1; Chris@0: const ATTRIBUTE_NAME_IN_LOWER_CASE = 2; Chris@0: const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4; Chris@0: Chris@0: private $flags; Chris@0: Chris@0: /** Chris@0: * @param int $flags Chris@0: */ Chris@0: public function __construct($flags = 0) Chris@0: { Chris@0: $this->flags = $flags; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param int $flag Chris@0: * @param bool $on Chris@0: * Chris@0: * @return $this Chris@0: */ Chris@0: public function setFlag($flag, $on) Chris@0: { Chris@0: if ($on && !$this->hasFlag($flag)) { Chris@0: $this->flags += $flag; Chris@0: } Chris@0: Chris@0: if (!$on && $this->hasFlag($flag)) { Chris@0: $this->flags -= $flag; Chris@0: } Chris@0: Chris@0: return $this; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @param int $flag Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: public function hasFlag($flag) Chris@0: { Chris@0: return (bool) ($this->flags & $flag); Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getNodeTranslators() Chris@0: { Chris@17: return [ Chris@17: 'Selector' => [$this, 'translateSelector'], Chris@17: 'CombinedSelector' => [$this, 'translateCombinedSelector'], Chris@17: 'Negation' => [$this, 'translateNegation'], Chris@17: 'Function' => [$this, 'translateFunction'], Chris@17: 'Pseudo' => [$this, 'translatePseudo'], Chris@17: 'Attribute' => [$this, 'translateAttribute'], Chris@17: 'Class' => [$this, 'translateClass'], Chris@17: 'Hash' => [$this, 'translateHash'], Chris@17: 'Element' => [$this, 'translateElement'], Chris@17: ]; Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateSelector(Node\SelectorNode $node, Translator $translator) Chris@0: { Chris@0: return $translator->nodeToXPath($node->getTree()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator) Chris@0: { Chris@0: return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateNegation(Node\NegationNode $node, Translator $translator) Chris@0: { Chris@0: $xpath = $translator->nodeToXPath($node->getSelector()); Chris@0: $subXpath = $translator->nodeToXPath($node->getSubSelector()); Chris@0: $subXpath->addNameTest(); Chris@0: Chris@0: if ($subXpath->getCondition()) { Chris@0: return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition())); Chris@0: } Chris@0: Chris@0: return $xpath->addCondition('0'); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateFunction(Node\FunctionNode $node, Translator $translator) Chris@0: { Chris@0: $xpath = $translator->nodeToXPath($node->getSelector()); Chris@0: Chris@0: return $translator->addFunction($xpath, $node); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translatePseudo(Node\PseudoNode $node, Translator $translator) Chris@0: { Chris@0: $xpath = $translator->nodeToXPath($node->getSelector()); Chris@0: Chris@0: return $translator->addPseudoClass($xpath, $node->getIdentifier()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateAttribute(Node\AttributeNode $node, Translator $translator) Chris@0: { Chris@0: $name = $node->getAttribute(); Chris@0: $safe = $this->isSafeName($name); Chris@0: Chris@0: if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) { Chris@0: $name = strtolower($name); Chris@0: } Chris@0: Chris@0: if ($node->getNamespace()) { Chris@0: $name = sprintf('%s:%s', $node->getNamespace(), $name); Chris@0: $safe = $safe && $this->isSafeName($node->getNamespace()); Chris@0: } Chris@0: Chris@0: $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name)); Chris@0: $value = $node->getValue(); Chris@0: $xpath = $translator->nodeToXPath($node->getSelector()); Chris@0: Chris@0: if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) { Chris@0: $value = strtolower($value); Chris@0: } Chris@0: Chris@0: return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateClass(Node\ClassNode $node, Translator $translator) Chris@0: { Chris@0: $xpath = $translator->nodeToXPath($node->getSelector()); Chris@0: Chris@0: return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateHash(Node\HashNode $node, Translator $translator) Chris@0: { Chris@0: $xpath = $translator->nodeToXPath($node->getSelector()); Chris@0: Chris@0: return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId()); Chris@0: } Chris@0: Chris@0: /** Chris@0: * @return XPathExpr Chris@0: */ Chris@0: public function translateElement(Node\ElementNode $node) Chris@0: { Chris@0: $element = $node->getElement(); Chris@0: Chris@0: if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) { Chris@0: $element = strtolower($element); Chris@0: } Chris@0: Chris@0: if ($element) { Chris@0: $safe = $this->isSafeName($element); Chris@0: } else { Chris@0: $element = '*'; Chris@0: $safe = true; Chris@0: } Chris@0: Chris@0: if ($node->getNamespace()) { Chris@0: $element = sprintf('%s:%s', $node->getNamespace(), $element); Chris@0: $safe = $safe && $this->isSafeName($node->getNamespace()); Chris@0: } Chris@0: Chris@0: $xpath = new XPathExpr('', $element); Chris@0: Chris@0: if (!$safe) { Chris@0: $xpath->addNameTest(); Chris@0: } Chris@0: Chris@0: return $xpath; Chris@0: } Chris@0: Chris@0: /** Chris@0: * {@inheritdoc} Chris@0: */ Chris@0: public function getName() Chris@0: { Chris@0: return 'node'; Chris@0: } Chris@0: Chris@0: /** Chris@0: * Tests if given name is safe. Chris@0: * Chris@0: * @param string $name Chris@0: * Chris@0: * @return bool Chris@0: */ Chris@0: private function isSafeName($name) Chris@0: { Chris@0: return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name); Chris@0: } Chris@0: }