| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165 | <?php/* * 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\Extension;use Symfony\Component\CssSelector\Exception\ExpressionErrorException;use Symfony\Component\CssSelector\Exception\SyntaxErrorException;use Symfony\Component\CssSelector\Node\FunctionNode;use Symfony\Component\CssSelector\Parser\Parser;use Symfony\Component\CssSelector\XPath\Translator;use Symfony\Component\CssSelector\XPath\XPathExpr;/** * XPath expression translator function extension. * * 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 FunctionExtension extends AbstractExtension{    public function getFunctionTranslators(): array    {        return [            'nth-child' => $this->translateNthChild(...),            'nth-last-child' => $this->translateNthLastChild(...),            'nth-of-type' => $this->translateNthOfType(...),            'nth-last-of-type' => $this->translateNthLastOfType(...),            'contains' => $this->translateContains(...),            'lang' => $this->translateLang(...),        ];    }    /**     * @throws ExpressionErrorException     */    public function translateNthChild(XPathExpr $xpath, FunctionNode $function, bool $last = false, bool $addNameTest = true): XPathExpr    {        try {            [$a, $b] = Parser::parseSeries($function->getArguments());        } catch (SyntaxErrorException $e) {            throw new ExpressionErrorException(sprintf('Invalid series: "%s".', implode('", "', $function->getArguments())), 0, $e);        }        $xpath->addStarPrefix();        if ($addNameTest) {            $xpath->addNameTest();        }        if (0 === $a) {            return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b));        }        if ($a < 0) {            if ($b < 1) {                return $xpath->addCondition('false()');            }            $sign = '<=';        } else {            $sign = '>=';        }        $expr = 'position()';        if ($last) {            $expr = 'last() - '.$expr;            --$b;        }        if (0 !== $b) {            $expr .= ' - '.$b;        }        $conditions = [sprintf('%s %s 0', $expr, $sign)];        if (1 !== $a && -1 !== $a) {            $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a);        }        return $xpath->addCondition(implode(' and ', $conditions));        // todo: handle an+b, odd, even        // an+b means every-a, plus b, e.g., 2n+1 means odd        // 0n+b means b        // n+0 means a=1, i.e., all elements        // an means every a elements, i.e., 2n means even        // -n means -1n        // -1n+6 means elements 6 and previous    }    public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function): XPathExpr    {        return $this->translateNthChild($xpath, $function, true);    }    public function translateNthOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr    {        return $this->translateNthChild($xpath, $function, false, false);    }    /**     * @throws ExpressionErrorException     */    public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr    {        if ('*' === $xpath->getElement()) {            throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.');        }        return $this->translateNthChild($xpath, $function, true, false);    }    /**     * @throws ExpressionErrorException     */    public function translateContains(XPathExpr $xpath, FunctionNode $function): XPathExpr    {        $arguments = $function->getArguments();        foreach ($arguments as $token) {            if (!($token->isString() || $token->isIdentifier())) {                throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments));            }        }        return $xpath->addCondition(sprintf(            'contains(string(.), %s)',            Translator::getXpathLiteral($arguments[0]->getValue())        ));    }    /**     * @throws ExpressionErrorException     */    public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr    {        $arguments = $function->getArguments();        foreach ($arguments as $token) {            if (!($token->isString() || $token->isIdentifier())) {                throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));            }        }        return $xpath->addCondition(sprintf(            'lang(%s)',            Translator::getXpathLiteral($arguments[0]->getValue())        ));    }    public function getName(): string    {        return 'function';    }}
 |