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