annotate vendor/symfony/css-selector/XPath/Translator.php @ 19:fa3358dc1485 tip

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