friendship ended with social-app. php is my new best friend
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 12namespace Symfony\Component\CssSelector\XPath\Extension; 13 14use Symfony\Component\CssSelector\Exception\ExpressionErrorException; 15use Symfony\Component\CssSelector\Exception\SyntaxErrorException; 16use Symfony\Component\CssSelector\Node\FunctionNode; 17use Symfony\Component\CssSelector\Parser\Parser; 18use Symfony\Component\CssSelector\XPath\Translator; 19use 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 */ 31class FunctionExtension extends AbstractExtension 32{ 33 public function getFunctionTranslators(): array 34 { 35 return [ 36 'nth-child' => $this->translateNthChild(...), 37 'nth-last-child' => $this->translateNthLastChild(...), 38 'nth-of-type' => $this->translateNthOfType(...), 39 'nth-last-of-type' => $this->translateNthLastOfType(...), 40 'contains' => $this->translateContains(...), 41 'lang' => $this->translateLang(...), 42 ]; 43 } 44 45 /** 46 * @throws ExpressionErrorException 47 */ 48 public function translateNthChild(XPathExpr $xpath, FunctionNode $function, bool $last = false, bool $addNameTest = true): XPathExpr 49 { 50 try { 51 [$a, $b] = Parser::parseSeries($function->getArguments()); 52 } catch (SyntaxErrorException $e) { 53 throw new ExpressionErrorException(\sprintf('Invalid series: "%s".', implode('", "', $function->getArguments())), 0, $e); 54 } 55 56 $xpath->addStarPrefix(); 57 if ($addNameTest) { 58 $xpath->addNameTest(); 59 } 60 61 if (0 === $a) { 62 return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b)); 63 } 64 65 if ($a < 0) { 66 if ($b < 1) { 67 return $xpath->addCondition('false()'); 68 } 69 70 $sign = '<='; 71 } else { 72 $sign = '>='; 73 } 74 75 $expr = 'position()'; 76 77 if ($last) { 78 $expr = 'last() - '.$expr; 79 --$b; 80 } 81 82 if (0 !== $b) { 83 $expr .= ' - '.$b; 84 } 85 86 $conditions = [\sprintf('%s %s 0', $expr, $sign)]; 87 88 if (1 !== $a && -1 !== $a) { 89 $conditions[] = \sprintf('(%s) mod %d = 0', $expr, $a); 90 } 91 92 return $xpath->addCondition(implode(' and ', $conditions)); 93 94 // todo: handle an+b, odd, even 95 // an+b means every-a, plus b, e.g., 2n+1 means odd 96 // 0n+b means b 97 // n+0 means a=1, i.e., all elements 98 // an means every a elements, i.e., 2n means even 99 // -n means -1n 100 // -1n+6 means elements 6 and previous 101 } 102 103 public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function): XPathExpr 104 { 105 return $this->translateNthChild($xpath, $function, true); 106 } 107 108 public function translateNthOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr 109 { 110 return $this->translateNthChild($xpath, $function, false, false); 111 } 112 113 /** 114 * @throws ExpressionErrorException 115 */ 116 public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function): XPathExpr 117 { 118 if ('*' === $xpath->getElement()) { 119 throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.'); 120 } 121 122 return $this->translateNthChild($xpath, $function, true, false); 123 } 124 125 /** 126 * @throws ExpressionErrorException 127 */ 128 public function translateContains(XPathExpr $xpath, FunctionNode $function): XPathExpr 129 { 130 $arguments = $function->getArguments(); 131 foreach ($arguments as $token) { 132 if (!($token->isString() || $token->isIdentifier())) { 133 throw new ExpressionErrorException('Expected a single string or identifier for :contains(), got '.implode(', ', $arguments)); 134 } 135 } 136 137 return $xpath->addCondition(\sprintf( 138 'contains(string(.), %s)', 139 Translator::getXpathLiteral($arguments[0]->getValue()) 140 )); 141 } 142 143 /** 144 * @throws ExpressionErrorException 145 */ 146 public function translateLang(XPathExpr $xpath, FunctionNode $function): XPathExpr 147 { 148 $arguments = $function->getArguments(); 149 foreach ($arguments as $token) { 150 if (!($token->isString() || $token->isIdentifier())) { 151 throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments)); 152 } 153 } 154 155 return $xpath->addCondition(\sprintf( 156 'lang(%s)', 157 Translator::getXpathLiteral($arguments[0]->getValue()) 158 )); 159 } 160 161 public function getName(): string 162 { 163 return 'function'; 164 } 165}