[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/_test/vendor/symfony/css-selector/XPath/ -> Translator.php (source)

   1  <?php
   2  
   3  /*
   4   * This file is part of the Symfony package.
   5   *
   6   * (c) Fabien Potencier <fabien@symfony.com>
   7   *
   8   * For the full copyright and license information, please view the LICENSE
   9   * file that was distributed with this source code.
  10   */
  11  
  12  namespace Symfony\Component\CssSelector\XPath;
  13  
  14  use Symfony\Component\CssSelector\Exception\ExpressionErrorException;
  15  use Symfony\Component\CssSelector\Node\FunctionNode;
  16  use Symfony\Component\CssSelector\Node\NodeInterface;
  17  use Symfony\Component\CssSelector\Node\SelectorNode;
  18  use Symfony\Component\CssSelector\Parser\Parser;
  19  use Symfony\Component\CssSelector\Parser\ParserInterface;
  20  
  21  /**
  22   * XPath expression translator interface.
  23   *
  24   * This component is a port of the Python cssselect library,
  25   * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
  26   *
  27   * @author Jean-Fran├žois Simon <jeanfrancois.simon@sensiolabs.com>
  28   *
  29   * @internal
  30   */
  31  class Translator implements TranslatorInterface
  32  {
  33      private $mainParser;
  34  
  35      /**
  36       * @var ParserInterface[]
  37       */
  38      private $shortcutParsers = [];
  39  
  40      /**
  41       * @var Extension\ExtensionInterface[]
  42       */
  43      private $extensions = [];
  44  
  45      private $nodeTranslators = [];
  46      private $combinationTranslators = [];
  47      private $functionTranslators = [];
  48      private $pseudoClassTranslators = [];
  49      private $attributeMatchingTranslators = [];
  50  
  51      public function __construct(ParserInterface $parser = null)
  52      {
  53          $this->mainParser = $parser ?? new Parser();
  54  
  55          $this
  56              ->registerExtension(new Extension\NodeExtension())
  57              ->registerExtension(new Extension\CombinationExtension())
  58              ->registerExtension(new Extension\FunctionExtension())
  59              ->registerExtension(new Extension\PseudoClassExtension())
  60              ->registerExtension(new Extension\AttributeMatchingExtension())
  61          ;
  62      }
  63  
  64      public static function getXpathLiteral(string $element): string
  65      {
  66          if (!str_contains($element, "'")) {
  67              return "'".$element."'";
  68          }
  69  
  70          if (!str_contains($element, '"')) {
  71              return '"'.$element.'"';
  72          }
  73  
  74          $string = $element;
  75          $parts = [];
  76          while (true) {
  77              if (false !== $pos = strpos($string, "'")) {
  78                  $parts[] = sprintf("'%s'", substr($string, 0, $pos));
  79                  $parts[] = "\"'\"";
  80                  $string = substr($string, $pos + 1);
  81              } else {
  82                  $parts[] = "'$string'";
  83                  break;
  84              }
  85          }
  86  
  87          return sprintf('concat(%s)', implode(', ', $parts));
  88      }
  89  
  90      /**
  91       * {@inheritdoc}
  92       */
  93      public function cssToXPath(string $cssExpr, string $prefix = 'descendant-or-self::'): string
  94      {
  95          $selectors = $this->parseSelectors($cssExpr);
  96  
  97          /** @var SelectorNode $selector */
  98          foreach ($selectors as $index => $selector) {
  99              if (null !== $selector->getPseudoElement()) {
 100                  throw new ExpressionErrorException('Pseudo-elements are not supported.');
 101              }
 102  
 103              $selectors[$index] = $this->selectorToXPath($selector, $prefix);
 104          }
 105  
 106          return implode(' | ', $selectors);
 107      }
 108  
 109      /**
 110       * {@inheritdoc}
 111       */
 112      public function selectorToXPath(SelectorNode $selector, string $prefix = 'descendant-or-self::'): string
 113      {
 114          return ($prefix ?: '').$this->nodeToXPath($selector);
 115      }
 116  
 117      /**
 118       * @return $this
 119       */
 120      public function registerExtension(Extension\ExtensionInterface $extension): self
 121      {
 122          $this->extensions[$extension->getName()] = $extension;
 123  
 124          $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators());
 125          $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators());
 126          $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators());
 127          $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators());
 128          $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators());
 129  
 130          return $this;
 131      }
 132  
 133      /**
 134       * @throws ExpressionErrorException
 135       */
 136      public function getExtension(string $name): Extension\ExtensionInterface
 137      {
 138          if (!isset($this->extensions[$name])) {
 139              throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name));
 140          }
 141  
 142          return $this->extensions[$name];
 143      }
 144  
 145      /**
 146       * @return $this
 147       */
 148      public function registerParserShortcut(ParserInterface $shortcut): self
 149      {
 150          $this->shortcutParsers[] = $shortcut;
 151  
 152          return $this;
 153      }
 154  
 155      /**
 156       * @throws ExpressionErrorException
 157       */
 158      public function nodeToXPath(NodeInterface $node): XPathExpr
 159      {
 160          if (!isset($this->nodeTranslators[$node->getNodeName()])) {
 161              throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName()));
 162          }
 163  
 164          return $this->nodeTranslators[$node->getNodeName()]($node, $this);
 165      }
 166  
 167      /**
 168       * @throws ExpressionErrorException
 169       */
 170      public function addCombination(string $combiner, NodeInterface $xpath, NodeInterface $combinedXpath): XPathExpr
 171      {
 172          if (!isset($this->combinationTranslators[$combiner])) {
 173              throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner));
 174          }
 175  
 176          return $this->combinationTranslators[$combiner]($this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath));
 177      }
 178  
 179      /**
 180       * @throws ExpressionErrorException
 181       */
 182      public function addFunction(XPathExpr $xpath, FunctionNode $function): XPathExpr
 183      {
 184          if (!isset($this->functionTranslators[$function->getName()])) {
 185              throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName()));
 186          }
 187  
 188          return $this->functionTranslators[$function->getName()]($xpath, $function);
 189      }
 190  
 191      /**
 192       * @throws ExpressionErrorException
 193       */
 194      public function addPseudoClass(XPathExpr $xpath, string $pseudoClass): XPathExpr
 195      {
 196          if (!isset($this->pseudoClassTranslators[$pseudoClass])) {
 197              throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass));
 198          }
 199  
 200          return $this->pseudoClassTranslators[$pseudoClass]($xpath);
 201      }
 202  
 203      /**
 204       * @throws ExpressionErrorException
 205       */
 206      public function addAttributeMatching(XPathExpr $xpath, string $operator, string $attribute, $value): XPathExpr
 207      {
 208          if (!isset($this->attributeMatchingTranslators[$operator])) {
 209              throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator));
 210          }
 211  
 212          return $this->attributeMatchingTranslators[$operator]($xpath, $attribute, $value);
 213      }
 214  
 215      /**
 216       * @return SelectorNode[]
 217       */
 218      private function parseSelectors(string $css): array
 219      {
 220          foreach ($this->shortcutParsers as $shortcut) {
 221              $tokens = $shortcut->parse($css);
 222  
 223              if (!empty($tokens)) {
 224                  return $tokens;
 225              }
 226          }
 227  
 228          return $this->mainParser->parse($css);
 229      }
 230  }