4 * This file is part of the Symfony package.
6 * (c) Fabien Potencier <fabien@symfony.com>
8 * For the full copyright and license information, please view the LICENSE
9 * file that was distributed with this source code.
12 namespace Symfony\Component\CssSelector\XPath\Extension;
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;
22 * XPath expression translator function extension.
24 * This component is a port of the Python cssselect library,
25 * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
27 * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
31 class FunctionExtension extends AbstractExtension
36 public function getFunctionTranslators()
39 'nth-child' => array($this, 'translateNthChild'),
40 'nth-last-child' => array($this, 'translateNthLastChild'),
41 'nth-of-type' => array($this, 'translateNthOfType'),
42 'nth-last-of-type' => array($this, 'translateNthLastOfType'),
43 'contains' => array($this, 'translateContains'),
44 'lang' => array($this, 'translateLang'),
49 * @param XPathExpr $xpath
50 * @param FunctionNode $function
52 * @param bool $addNameTest
56 * @throws ExpressionErrorException
58 public function translateNthChild(XPathExpr $xpath, FunctionNode $function, $last = false, $addNameTest = true)
61 list($a, $b) = Parser::parseSeries($function->getArguments());
62 } catch (SyntaxErrorException $e) {
63 throw new ExpressionErrorException(sprintf('Invalid series: %s', implode(', ', $function->getArguments())), 0, $e);
66 $xpath->addStarPrefix();
68 $xpath->addNameTest();
72 return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b));
77 return $xpath->addCondition('false()');
88 $expr = 'last() - '.$expr;
96 $conditions = array(sprintf('%s %s 0', $expr, $sign));
98 if (1 !== $a && -1 !== $a) {
99 $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a);
102 return $xpath->addCondition(implode(' and ', $conditions));
104 // todo: handle an+b, odd, even
105 // an+b means every-a, plus b, e.g., 2n+1 means odd
107 // n+0 means a=1, i.e., all elements
108 // an means every a elements, i.e., 2n means even
110 // -1n+6 means elements 6 and previous
116 public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function)
118 return $this->translateNthChild($xpath, $function, true);
124 public function translateNthOfType(XPathExpr $xpath, FunctionNode $function)
126 return $this->translateNthChild($xpath, $function, false, false);
132 * @throws ExpressionErrorException
134 public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function)
136 if ('*' === $xpath->getElement()) {
137 throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.');
140 return $this->translateNthChild($xpath, $function, true, false);
146 * @throws ExpressionErrorException
148 public function translateContains(XPathExpr $xpath, FunctionNode $function)
150 $arguments = $function->getArguments();
151 foreach ($arguments as $token) {
152 if (!($token->isString() || $token->isIdentifier())) {
153 throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments));
157 return $xpath->addCondition(sprintf(
158 'contains(string(.), %s)',
159 Translator::getXpathLiteral($arguments[0]->getValue())
166 * @throws ExpressionErrorException
168 public function translateLang(XPathExpr $xpath, FunctionNode $function)
170 $arguments = $function->getArguments();
171 foreach ($arguments as $token) {
172 if (!($token->isString() || $token->isIdentifier())) {
173 throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));
177 return $xpath->addCondition(sprintf(
179 Translator::getXpathLiteral($arguments[0]->getValue())
186 public function getName()