2b01fc8e52526e2733ff3d04999ba87753d95f10
[yaffs-website] / vendor / symfony / css-selector / XPath / Translator.php
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 }