comparison vendor/symfony/css-selector/XPath/Translator.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;
13
14 use Symfony\Component\CssSelector\Exception\ExpressionErrorException;
15 use Symfony\Component\CssSelector\Node\FunctionNode;
16 use Symfony\Component\CssSelector\Node\NodeInterface;
17 use Symfony\Component\CssSelector\Node\SelectorNode;
18 use Symfony\Component\CssSelector\Parser\Parser;
19 use Symfony\Component\CssSelector\Parser\ParserInterface;
20
21 /**
22 * XPath expression translator interface.
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 Translator implements TranslatorInterface
32 {
33 /**
34 * @var ParserInterface
35 */
36 private $mainParser;
37
38 /**
39 * @var ParserInterface[]
40 */
41 private $shortcutParsers = array();
42
43 /**
44 * @var Extension\ExtensionInterface
45 */
46 private $extensions = array();
47
48 /**
49 * @var array
50 */
51 private $nodeTranslators = array();
52
53 /**
54 * @var array
55 */
56 private $combinationTranslators = array();
57
58 /**
59 * @var array
60 */
61 private $functionTranslators = array();
62
63 /**
64 * @var array
65 */
66 private $pseudoClassTranslators = array();
67
68 /**
69 * @var array
70 */
71 private $attributeMatchingTranslators = array();
72
73 public function __construct(ParserInterface $parser = null)
74 {
75 $this->mainParser = $parser ?: new Parser();
76
77 $this
78 ->registerExtension(new Extension\NodeExtension())
79 ->registerExtension(new Extension\CombinationExtension())
80 ->registerExtension(new Extension\FunctionExtension())
81 ->registerExtension(new Extension\PseudoClassExtension())
82 ->registerExtension(new Extension\AttributeMatchingExtension())
83 ;
84 }
85
86 /**
87 * @param string $element
88 *
89 * @return string
90 */
91 public static function getXpathLiteral($element)
92 {
93 if (false === strpos($element, "'")) {
94 return "'".$element."'";
95 }
96
97 if (false === strpos($element, '"')) {
98 return '"'.$element.'"';
99 }
100
101 $string = $element;
102 $parts = array();
103 while (true) {
104 if (false !== $pos = strpos($string, "'")) {
105 $parts[] = sprintf("'%s'", substr($string, 0, $pos));
106 $parts[] = "\"'\"";
107 $string = substr($string, $pos + 1);
108 } else {
109 $parts[] = "'$string'";
110 break;
111 }
112 }
113
114 return sprintf('concat(%s)', implode($parts, ', '));
115 }
116
117 /**
118 * {@inheritdoc}
119 */
120 public function cssToXPath($cssExpr, $prefix = 'descendant-or-self::')
121 {
122 $selectors = $this->parseSelectors($cssExpr);
123
124 /** @var SelectorNode $selector */
125 foreach ($selectors as $index => $selector) {
126 if (null !== $selector->getPseudoElement()) {
127 throw new ExpressionErrorException('Pseudo-elements are not supported.');
128 }
129
130 $selectors[$index] = $this->selectorToXPath($selector, $prefix);
131 }
132
133 return implode(' | ', $selectors);
134 }
135
136 /**
137 * {@inheritdoc}
138 */
139 public function selectorToXPath(SelectorNode $selector, $prefix = 'descendant-or-self::')
140 {
141 return ($prefix ?: '').$this->nodeToXPath($selector);
142 }
143
144 /**
145 * Registers an extension.
146 *
147 * @param Extension\ExtensionInterface $extension
148 *
149 * @return $this
150 */
151 public function registerExtension(Extension\ExtensionInterface $extension)
152 {
153 $this->extensions[$extension->getName()] = $extension;
154
155 $this->nodeTranslators = array_merge($this->nodeTranslators, $extension->getNodeTranslators());
156 $this->combinationTranslators = array_merge($this->combinationTranslators, $extension->getCombinationTranslators());
157 $this->functionTranslators = array_merge($this->functionTranslators, $extension->getFunctionTranslators());
158 $this->pseudoClassTranslators = array_merge($this->pseudoClassTranslators, $extension->getPseudoClassTranslators());
159 $this->attributeMatchingTranslators = array_merge($this->attributeMatchingTranslators, $extension->getAttributeMatchingTranslators());
160
161 return $this;
162 }
163
164 /**
165 * @param string $name
166 *
167 * @return Extension\ExtensionInterface
168 *
169 * @throws ExpressionErrorException
170 */
171 public function getExtension($name)
172 {
173 if (!isset($this->extensions[$name])) {
174 throw new ExpressionErrorException(sprintf('Extension "%s" not registered.', $name));
175 }
176
177 return $this->extensions[$name];
178 }
179
180 /**
181 * Registers a shortcut parser.
182 *
183 * @param ParserInterface $shortcut
184 *
185 * @return $this
186 */
187 public function registerParserShortcut(ParserInterface $shortcut)
188 {
189 $this->shortcutParsers[] = $shortcut;
190
191 return $this;
192 }
193
194 /**
195 * @param NodeInterface $node
196 *
197 * @return XPathExpr
198 *
199 * @throws ExpressionErrorException
200 */
201 public function nodeToXPath(NodeInterface $node)
202 {
203 if (!isset($this->nodeTranslators[$node->getNodeName()])) {
204 throw new ExpressionErrorException(sprintf('Node "%s" not supported.', $node->getNodeName()));
205 }
206
207 return call_user_func($this->nodeTranslators[$node->getNodeName()], $node, $this);
208 }
209
210 /**
211 * @param string $combiner
212 * @param NodeInterface $xpath
213 * @param NodeInterface $combinedXpath
214 *
215 * @return XPathExpr
216 *
217 * @throws ExpressionErrorException
218 */
219 public function addCombination($combiner, NodeInterface $xpath, NodeInterface $combinedXpath)
220 {
221 if (!isset($this->combinationTranslators[$combiner])) {
222 throw new ExpressionErrorException(sprintf('Combiner "%s" not supported.', $combiner));
223 }
224
225 return call_user_func($this->combinationTranslators[$combiner], $this->nodeToXPath($xpath), $this->nodeToXPath($combinedXpath));
226 }
227
228 /**
229 * @param XPathExpr $xpath
230 * @param FunctionNode $function
231 *
232 * @return XPathExpr
233 *
234 * @throws ExpressionErrorException
235 */
236 public function addFunction(XPathExpr $xpath, FunctionNode $function)
237 {
238 if (!isset($this->functionTranslators[$function->getName()])) {
239 throw new ExpressionErrorException(sprintf('Function "%s" not supported.', $function->getName()));
240 }
241
242 return call_user_func($this->functionTranslators[$function->getName()], $xpath, $function);
243 }
244
245 /**
246 * @param XPathExpr $xpath
247 * @param string $pseudoClass
248 *
249 * @return XPathExpr
250 *
251 * @throws ExpressionErrorException
252 */
253 public function addPseudoClass(XPathExpr $xpath, $pseudoClass)
254 {
255 if (!isset($this->pseudoClassTranslators[$pseudoClass])) {
256 throw new ExpressionErrorException(sprintf('Pseudo-class "%s" not supported.', $pseudoClass));
257 }
258
259 return call_user_func($this->pseudoClassTranslators[$pseudoClass], $xpath);
260 }
261
262 /**
263 * @param XPathExpr $xpath
264 * @param string $operator
265 * @param string $attribute
266 * @param string $value
267 *
268 * @return XPathExpr
269 *
270 * @throws ExpressionErrorException
271 */
272 public function addAttributeMatching(XPathExpr $xpath, $operator, $attribute, $value)
273 {
274 if (!isset($this->attributeMatchingTranslators[$operator])) {
275 throw new ExpressionErrorException(sprintf('Attribute matcher operator "%s" not supported.', $operator));
276 }
277
278 return call_user_func($this->attributeMatchingTranslators[$operator], $xpath, $attribute, $value);
279 }
280
281 /**
282 * @param string $css
283 *
284 * @return SelectorNode[]
285 */
286 private function parseSelectors($css)
287 {
288 foreach ($this->shortcutParsers as $shortcut) {
289 $tokens = $shortcut->parse($css);
290
291 if (!empty($tokens)) {
292 return $tokens;
293 }
294 }
295
296 return $this->mainParser->parse($css);
297 }
298 }