comparison vendor/symfony/css-selector/XPath/Extension/FunctionExtension.php @ 0:4c8ae668cc8c

Initial import (non-working)
author Chris Cannam
date Wed, 29 Nov 2017 16:09:58 +0000
parents
children 1fec387a4317
comparison
equal deleted inserted replaced
-1:000000000000 0:4c8ae668cc8c
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()
37 {
38 return array(
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'),
45 );
46 }
47
48 /**
49 * @param XPathExpr $xpath
50 * @param FunctionNode $function
51 * @param bool $last
52 * @param bool $addNameTest
53 *
54 * @return XPathExpr
55 *
56 * @throws ExpressionErrorException
57 */
58 public function translateNthChild(XPathExpr $xpath, FunctionNode $function, $last = false, $addNameTest = true)
59 {
60 try {
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);
64 }
65
66 $xpath->addStarPrefix();
67 if ($addNameTest) {
68 $xpath->addNameTest();
69 }
70
71 if (0 === $a) {
72 return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b));
73 }
74
75 if ($a < 0) {
76 if ($b < 1) {
77 return $xpath->addCondition('false()');
78 }
79
80 $sign = '<=';
81 } else {
82 $sign = '>=';
83 }
84
85 $expr = 'position()';
86
87 if ($last) {
88 $expr = 'last() - '.$expr;
89 --$b;
90 }
91
92 if (0 !== $b) {
93 $expr .= ' - '.$b;
94 }
95
96 $conditions = array(sprintf('%s %s 0', $expr, $sign));
97
98 if (1 !== $a && -1 !== $a) {
99 $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a);
100 }
101
102 return $xpath->addCondition(implode(' and ', $conditions));
103
104 // todo: handle an+b, odd, even
105 // an+b means every-a, plus b, e.g., 2n+1 means odd
106 // 0n+b means b
107 // n+0 means a=1, i.e., all elements
108 // an means every a elements, i.e., 2n means even
109 // -n means -1n
110 // -1n+6 means elements 6 and previous
111 }
112
113 /**
114 * @param XPathExpr $xpath
115 * @param FunctionNode $function
116 *
117 * @return XPathExpr
118 */
119 public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function)
120 {
121 return $this->translateNthChild($xpath, $function, true);
122 }
123
124 /**
125 * @param XPathExpr $xpath
126 * @param FunctionNode $function
127 *
128 * @return XPathExpr
129 */
130 public function translateNthOfType(XPathExpr $xpath, FunctionNode $function)
131 {
132 return $this->translateNthChild($xpath, $function, false, false);
133 }
134
135 /**
136 * @param XPathExpr $xpath
137 * @param FunctionNode $function
138 *
139 * @return XPathExpr
140 *
141 * @throws ExpressionErrorException
142 */
143 public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function)
144 {
145 if ('*' === $xpath->getElement()) {
146 throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.');
147 }
148
149 return $this->translateNthChild($xpath, $function, true, false);
150 }
151
152 /**
153 * @param XPathExpr $xpath
154 * @param FunctionNode $function
155 *
156 * @return XPathExpr
157 *
158 * @throws ExpressionErrorException
159 */
160 public function translateContains(XPathExpr $xpath, FunctionNode $function)
161 {
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)
168 );
169 }
170 }
171
172 return $xpath->addCondition(sprintf(
173 'contains(string(.), %s)',
174 Translator::getXpathLiteral($arguments[0]->getValue())
175 ));
176 }
177
178 /**
179 * @param XPathExpr $xpath
180 * @param FunctionNode $function
181 *
182 * @return XPathExpr
183 *
184 * @throws ExpressionErrorException
185 */
186 public function translateLang(XPathExpr $xpath, FunctionNode $function)
187 {
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)
194 );
195 }
196 }
197
198 return $xpath->addCondition(sprintf(
199 'lang(%s)',
200 Translator::getXpathLiteral($arguments[0]->getValue())
201 ));
202 }
203
204 /**
205 * {@inheritdoc}
206 */
207 public function getName()
208 {
209 return 'function';
210 }
211 }