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
114 * @param XPathExpr $xpath
115 * @param FunctionNode $function
119 public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function)
121 return $this->translateNthChild($xpath, $function, true);
125 * @param XPathExpr $xpath
126 * @param FunctionNode $function
130 public function translateNthOfType(XPathExpr $xpath, FunctionNode $function)
132 return $this->translateNthChild($xpath, $function, false, false);
136 * @param XPathExpr $xpath
137 * @param FunctionNode $function
141 * @throws ExpressionErrorException
143 public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function)
145 if ('*' === $xpath->getElement()) {
146 throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.');
149 return $this->translateNthChild($xpath, $function, true, false);
153 * @param XPathExpr $xpath
154 * @param FunctionNode $function
158 * @throws ExpressionErrorException
160 public function translateContains(XPathExpr $xpath, FunctionNode $function)
162 $arguments = $function->getArguments();
163 foreach ($arguments as $token) {
164 if (!($token->isString() || $token->isIdentifier())) {
165 throw new ExpressionErrorException(
166 'Expected a single string or identifier for :contains(), got '
167 .implode(', ', $arguments)
172 return $xpath->addCondition(sprintf(
173 'contains(string(.), %s)',
174 Translator::getXpathLiteral($arguments[0]->getValue())
179 * @param XPathExpr $xpath
180 * @param FunctionNode $function
184 * @throws ExpressionErrorException
186 public function translateLang(XPathExpr $xpath, FunctionNode $function)
188 $arguments = $function->getArguments();
189 foreach ($arguments as $token) {
190 if (!($token->isString() || $token->isIdentifier())) {
191 throw new ExpressionErrorException(
192 'Expected a single string or identifier for :lang(), got '
193 .implode(', ', $arguments)
198 return $xpath->addCondition(sprintf(
200 Translator::getXpathLiteral($arguments[0]->getValue())
207 public function getName()