Spaces:
Sleeping
Sleeping
| /* | |
| * This file is part of the Symfony package. | |
| * | |
| * (c) Fabien Potencier <fabien@symfony.com> | |
| * | |
| * For the full copyright and license information, please view the LICENSE | |
| * file that was distributed with this source code. | |
| */ | |
| namespace Symfony\Component\CssSelector\XPath; | |
| use Symfony\Component\CssSelector\Exception\ExpressionErrorException; | |
| use Symfony\Component\CssSelector\Node\FunctionNode; | |
| use Symfony\Component\CssSelector\Node\NodeInterface; | |
| use Symfony\Component\CssSelector\Node\SelectorNode; | |
| use Symfony\Component\CssSelector\Parser\Parser; | |
| use Symfony\Component\CssSelector\Parser\ParserInterface; | |
| /** | |
| * XPath expression translator interface. | |
| * | |
| * This component is a port of the Python cssselect library, | |
| * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect. | |
| * | |
| * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com> | |
| * | |
| * @internal | |
| */ | |
| class Translator implements TranslatorInterface | |
| { | |
| /** | |
| * @var ParserInterface | |
| */ | |
| private $mainParser; | |
| /** | |
| * @var ParserInterface[] | |
| */ | |
| private $shortcutParsers = array(); | |
| /** | |
| * @var Extension\ExtensionInterface | |
| */ | |
| private $extensions = array(); | |
| /** | |
| * @var array | |
| */ | |
| private $nodeTranslators = array(); | |
| /** | |
| * @var array | |
| */ | |
| private $combinationTranslators = array(); | |
| /** | |
| * @var array | |
| */ | |
| private $functionTranslators = array(); | |
| /** | |
| * @var array | |
| */ | |
| private $pseudoClassTranslators = array(); | |
| /** | |
| * @var array | |
| */ | |
| private $attributeMatchingTranslators = array(); | |
| public function __construct(ParserInterface $parser = null) | |
| { | |
| $this->mainParser = $parser ?: new Parser(); | |
| $this | |
| ->registerExtension(new Extension\NodeExtension()) | |
| ->registerExtension(new Extension\CombinationExtension()) | |
| ->registerExtension(new Extension\FunctionExtension()) | |
| ->registerExtension(new Extension\PseudoClassExtension()) | |
| ->registerExtension(new Extension\AttributeMatchingExtension()) | |
| ; | |
| } | |
| /** | |
| * @param string $element | |
| * | |
| * @return string | |
| */ | |
| public static function getXpathLiteral($element) | |
| { | |
| if (false === strpos($element, "'")) { | |
| return "'".$element."'"; | |
| } | |
| if (false === strpos($element, '"')) { | |
| return '"'.$element.'"'; | |
| } | |
| $string = $element; | |
| $parts = array(); | |
| while (true) { | |
| if (false !== $pos = strpos($string, "'")) { | |
| $parts[] = sprintf("'%s'", substr($string, 0, $pos)); | |
| $parts[] = "\"'\""; | |
| $string = substr($string, $pos + 1); | |
| } else { | |
| $parts[] = "'$string'"; | |
| break; | |
| } | |
| } | |
| return sprintf('concat(%s)', implode($parts, ', ')); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::') | |
| { | |
| $selectors = $this->parseSelectors($cssExpr); | |
| /** @var SelectorNode $selector */ | |
| foreach ($selectors as $index => $selector) { | |
| if (null !== $selector->getPseudoElement()) { | |
| throw new ExpressionErrorException('Pseudo-elements are not supported.'); | |
| } | |
| $selectors[$index] = $this->selectorToXPath($selector, $prefix); | |
| } | |
| return implode(' | ', $selectors); | |
| } | |
| /** | |
| * {@inheritdoc} | |
| */ | |
| public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::') | |
| { | |
| return ($prefix ?: '').$this->nodeToXPath($selector); | |
| } | |
| /** | |
| * Registers an extension. | |
| * | |
| * @param Extension\ExtensionInterface $extension | |
| * | |
| * @return $this | |
| */ | |
| public function registerExtension(Extension\ExtensionInterface $extension) | |
| { | |
| $this->extensions[$extension->getName()] = $extension; | |
| $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators()); | |
| $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators()); | |
| $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators()); | |
| $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators()); | |
| $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators()); | |
| return $this; | |
| } | |
| /** | |
| * @param string $name | |
| * | |
| * @return Extension\ExtensionInterface | |
| * | |
| * @throws ExpressionErrorException | |
| */ | |
| public function getExtension($name) | |
| { | |
| if (!isset($this->extensions[$name])) { | |
| throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name)); | |
| } | |
| return $this->extensions[$name]; | |
| } | |
| /** | |
| * Registers a shortcut parser. | |
| * | |
| * @param ParserInterface $shortcut | |
| * | |
| * @return $this | |
| */ | |
| public function registerParserShortcut(ParserInterface $shortcut) | |
| { | |
| $this->shortcutParsers[] = $shortcut; | |
| return $this; | |
| } | |
| /** | |
| * @param NodeInterface $node | |
| * | |
| * @return XPathExpr | |
| * | |
| * @throws ExpressionErrorException | |
| */ | |
| public function nodeToXPath(NodeInterface $node) | |
| { | |
| if (!isset($this->nodeTranslators[$node->getNodeName()])) { | |
| throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName())); | |
| } | |
| return call_user_func($this->nodeTranslators[$node->getNodeName()], $node, $this); | |
| } | |
| /** | |
| * @param string $combiner | |
| * @param NodeInterface $xpath | |
| * @param NodeInterface $combinedXpath | |
| * | |
| * @return XPathExpr | |
| * | |
| * @throws ExpressionErrorException | |
| */ | |
| public function addCombination($combiner, NodeInterface $xpath, NodeInterface $combinedXpath) | |
| { | |
| if (!isset($this->combinationTranslators[$combiner])) { | |
| throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner)); | |
| } | |
| return call_user_func($this->combinationTranslators[$combiner], $this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath)); | |
| } | |
| /** | |
| * @param XPathExpr $xpath | |
| * @param FunctionNode $function | |
| * | |
| * @return XPathExpr | |
| * | |
| * @throws ExpressionErrorException | |
| */ | |
| public function addFunction(XPathExpr $xpath, FunctionNode $function) | |
| { | |
| if (!isset($this->functionTranslators[$function->getName()])) { | |
| throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName())); | |
| } | |
| return call_user_func($this->functionTranslators[$function->getName()], $xpath, $function); | |
| } | |
| /** | |
| * @param XPathExpr $xpath | |
| * @param string $pseudoClass | |
| * | |
| * @return XPathExpr | |
| * | |
| * @throws ExpressionErrorException | |
| */ | |
| public function addPseudoClass(XPathExpr $xpath, $pseudoClass) | |
| { | |
| if (!isset($this->pseudoClassTranslators[$pseudoClass])) { | |
| throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass)); | |
| } | |
| return call_user_func($this->pseudoClassTranslators[$pseudoClass], $xpath); | |
| } | |
| /** | |
| * @param XPathExpr $xpath | |
| * @param string $operator | |
| * @param string $attribute | |
| * @param string $value | |
| * | |
| * @return XPathExpr | |
| * | |
| * @throws ExpressionErrorException | |
| */ | |
| public function addAttributeMatching(XPathExpr $xpath, $operator, $attribute, $value) | |
| { | |
| if (!isset($this->attributeMatchingTranslators[$operator])) { | |
| throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator)); | |
| } | |
| return call_user_func($this->attributeMatchingTranslators[$operator], $xpath, $attribute, $value); | |
| } | |
| /** | |
| * @param string $css | |
| * | |
| * @return SelectorNode[] | |
| */ | |
| private function parseSelectors($css) | |
| { | |
| foreach ($this->shortcutParsers as $shortcut) { | |
| $tokens = $shortcut->parse($css); | |
| if (!empty($tokens)) { | |
| return $tokens; | |
| } | |
| } | |
| return $this->mainParser->parse($css); | |
| } | |
| } | |