[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/_test/vendor/symfony/css-selector/XPath/Extension/ -> FunctionExtension.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\Extension;
  13  
  14  use Symfony\Component\CssSelector\Exception\ExpressionErrorException;
  15  use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
  16  use Symfony\Component\CssSelector\Node\FunctionNode;
  17  use Symfony\Component\CssSelector\Parser\Parser;
  18  use Symfony\Component\CssSelector\XPath\Translator;
  19  use Symfony\Component\CssSelector\XPath\XPathExpr;
  20  
  21  /**
  22   * XPath expression translator function extension.
  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 FunctionExtension extends AbstractExtension
  32  {
  33      /**
  34       * {@inheritdoc}
  35       */
  36      public function getFunctionTranslators(): array
  37      {
  38          return [
  39              'nth-child' => [$this, 'translateNthChild'],
  40              'nth-last-child' => [$this, 'translateNthLastChild'],
  41              'nth-of-type' => [$this, 'translateNthOfType'],
  42              'nth-last-of-type' => [$this, 'translateNthLastOfType'],
  43              'contains' => [$this, 'translateContains'],
  44              'lang' => [$this, 'translateLang'],
  45          ];
  46      }
  47  
  48      /**
  49       * @throws ExpressionErrorException
  50       */
  51      public function translateNthChild(XPathExpr $xpath, FunctionNode $function, bool $last = false, bool $addNameTest = true): XPathExpr
  52      {
  53          try {
  54              [$a, $b] = Parser::parseSeries($function->getArguments());
  55          } catch (SyntaxErrorException $e) {
  56              throw new ExpressionErrorException(sprintf('Invalid series: "%s".', implode('", "', $function->getArguments())), 0, $e);
  57          }
  58  
  59          $xpath->addStarPrefix();
  60          if ($addNameTest) {
  61              $xpath->addNameTest();
  62          }
  63  
  64          if (0 === $a) {
  65              return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b));
  66          }
  67  
  68          if ($a < 0) {
  69              if ($b < 1) {
  70                  return $xpath->addCondition('false()');
  71              }
  72  
  73              $sign = '<=';
  74          } else {
  75              $sign = '>=';
  76          }
  77  
  78          $expr = 'position()';
  79  
  80          if ($last) {
  81              $expr = 'last() - '.$expr;
  82              --$b;
  83          }
  84  
  85          if (0 !== $b) {
  86              $expr .= ' - '.$b;
  87          }
  88  
  89          $conditions = [sprintf('%s %s 0', $expr, $sign)];
  90  
  91          if (1 !== $a && -1 !== $a) {
  92              $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a);
  93          }
  94  
  95          return $xpath->addCondition(implode(' and ', $conditions));
  96  
  97          // todo: handle an+b, odd, even
  98          // an+b means every-a, plus b, e.g., 2n+1 means odd
  99          // 0n+b means b
 100          // n+0 means a=1, i.e., all elements
 101          // an means every a elements, i.e., 2n means even
 102          // -n means -1n
 103          // -1n+6 means elements 6 and previous
 104      }
 105  
 106      public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function): XPathExpr
 107      {
 108          return $this->translateNthChild($xpath, $function, true);
 109      }
 110  
 111      public function translateNthOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr
 112      {
 113          return $this->translateNthChild($xpath, $function, false, false);
 114      }
 115  
 116      /**
 117       * @throws ExpressionErrorException
 118       */
 119      public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr
 120      {
 121          if ('*' === $xpath->getElement()) {
 122              throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.');
 123          }
 124  
 125          return $this->translateNthChild($xpath, $function, true, false);
 126      }
 127  
 128      /**
 129       * @throws ExpressionErrorException
 130       */
 131      public function translateContains(XPathExpr $xpath, FunctionNode $function): XPathExpr
 132      {
 133          $arguments = $function->getArguments();
 134          foreach ($arguments as $token) {
 135              if (!($token->isString() || $token->isIdentifier())) {
 136                  throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments));
 137              }
 138          }
 139  
 140          return $xpath->addCondition(sprintf(
 141              'contains(string(.), %s)',
 142              Translator::getXpathLiteral($arguments[0]->getValue())
 143          ));
 144      }
 145  
 146      /**
 147       * @throws ExpressionErrorException
 148       */
 149      public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr
 150      {
 151          $arguments = $function->getArguments();
 152          foreach ($arguments as $token) {
 153              if (!($token->isString() || $token->isIdentifier())) {
 154                  throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));
 155              }
 156          }
 157  
 158          return $xpath->addCondition(sprintf(
 159              'lang(%s)',
 160              Translator::getXpathLiteral($arguments[0]->getValue())
 161          ));
 162      }
 163  
 164      /**
 165       * {@inheritdoc}
 166       */
 167      public function getName(): string
 168      {
 169          return 'function';
 170      }
 171  }