菜谱项目

FunctionExtension.php 5.5KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien@symfony.com>
  6. *
  7. * For the full copyright and license information, please view the LICENSE
  8. * file that was distributed with this source code.
  9. */
  10. namespace Symfony\Component\CssSelector\XPath\Extension;
  11. use Symfony\Component\CssSelector\Exception\ExpressionErrorException;
  12. use Symfony\Component\CssSelector\Exception\SyntaxErrorException;
  13. use Symfony\Component\CssSelector\Node\FunctionNode;
  14. use Symfony\Component\CssSelector\Parser\Parser;
  15. use Symfony\Component\CssSelector\XPath\Translator;
  16. use Symfony\Component\CssSelector\XPath\XPathExpr;
  17. /**
  18. * XPath expression translator function extension.
  19. *
  20. * This component is a port of the Python cssselect library,
  21. * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
  22. *
  23. * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  24. *
  25. * @internal
  26. */
  27. class FunctionExtension extends AbstractExtension
  28. {
  29. /**
  30. * {@inheritdoc}
  31. */
  32. public function getFunctionTranslators()
  33. {
  34. return array(
  35. 'nth-child' => array($this, 'translateNthChild'),
  36. 'nth-last-child' => array($this, 'translateNthLastChild'),
  37. 'nth-of-type' => array($this, 'translateNthOfType'),
  38. 'nth-last-of-type' => array($this, 'translateNthLastOfType'),
  39. 'contains' => array($this, 'translateContains'),
  40. 'lang' => array($this, 'translateLang'),
  41. );
  42. }
  43. /**
  44. * @param XPathExpr $xpath
  45. * @param FunctionNode $function
  46. * @param bool $last
  47. * @param bool $addNameTest
  48. *
  49. * @return XPathExpr
  50. *
  51. * @throws ExpressionErrorException
  52. */
  53. public function translateNthChild(XPathExpr $xpath, FunctionNode $function, $last = false, $addNameTest = true)
  54. {
  55. try {
  56. list($a, $b) = Parser::parseSeries($function->getArguments());
  57. } catch (SyntaxErrorException $e) {
  58. throw new ExpressionErrorException(sprintf('Invalid series: %s', implode(', ', $function->getArguments())), 0, $e);
  59. }
  60. $xpath->addStarPrefix();
  61. if ($addNameTest) {
  62. $xpath->addNameTest();
  63. }
  64. if (0 === $a) {
  65. return $xpath->addCondition('position() = '.($last ? 'last() - '.($b - 1) : $b));
  66. }
  67. if ($a < 0) {
  68. if ($b < 1) {
  69. return $xpath->addCondition('false()');
  70. }
  71. $sign = '<=';
  72. } else {
  73. $sign = '>=';
  74. }
  75. $expr = 'position()';
  76. if ($last) {
  77. $expr = 'last() - '.$expr;
  78. --$b;
  79. }
  80. if (0 !== $b) {
  81. $expr .= ' - '.$b;
  82. }
  83. $conditions = array(sprintf('%s %s 0', $expr, $sign));
  84. if (1 !== $a && -1 !== $a) {
  85. $conditions[] = sprintf('(%s) mod %d = 0', $expr, $a);
  86. }
  87. return $xpath->addCondition(implode(' and ', $conditions));
  88. // todo: handle an+b, odd, even
  89. // an+b means every-a, plus b, e.g., 2n+1 means odd
  90. // 0n+b means b
  91. // n+0 means a=1, i.e., all elements
  92. // an means every a elements, i.e., 2n means even
  93. // -n means -1n
  94. // -1n+6 means elements 6 and previous
  95. }
  96. /**
  97. * @return XPathExpr
  98. */
  99. public function translateNthLastChild(XPathExpr $xpath, FunctionNode $function)
  100. {
  101. return $this->translateNthChild($xpath, $function, true);
  102. }
  103. /**
  104. * @return XPathExpr
  105. */
  106. public function translateNthOfType(XPathExpr $xpath, FunctionNode $function)
  107. {
  108. return $this->translateNthChild($xpath, $function, false, false);
  109. }
  110. /**
  111. * @return XPathExpr
  112. *
  113. * @throws ExpressionErrorException
  114. */
  115. public function translateNthLastOfType(XPathExpr $xpath, FunctionNode $function)
  116. {
  117. if ('*' === $xpath->getElement()) {
  118. throw new ExpressionErrorException('"*:nth-of-type()" is not implemented.');
  119. }
  120. return $this->translateNthChild($xpath, $function, true, false);
  121. }
  122. /**
  123. * @return XPathExpr
  124. *
  125. * @throws ExpressionErrorException
  126. */
  127. public function translateContains(XPathExpr $xpath, FunctionNode $function)
  128. {
  129. $arguments = $function->getArguments();
  130. foreach ($arguments as $token) {
  131. if (!($token->isString() || $token->isIdentifier())) {
  132. throw new ExpressionErrorException(
  133. 'Expected a single string or identifier for :contains(), got '
  134. .implode(', ', $arguments)
  135. );
  136. }
  137. }
  138. return $xpath->addCondition(sprintf(
  139. 'contains(string(.), %s)',
  140. Translator::getXpathLiteral($arguments[0]->getValue())
  141. ));
  142. }
  143. /**
  144. * @return XPathExpr
  145. *
  146. * @throws ExpressionErrorException
  147. */
  148. public function translateLang(XPathExpr $xpath, FunctionNode $function)
  149. {
  150. $arguments = $function->getArguments();
  151. foreach ($arguments as $token) {
  152. if (!($token->isString() || $token->isIdentifier())) {
  153. throw new ExpressionErrorException(
  154. 'Expected a single string or identifier for :lang(), got '
  155. .implode(', ', $arguments)
  156. );
  157. }
  158. }
  159. return $xpath->addCondition(sprintf(
  160. 'lang(%s)',
  161. Translator::getXpathLiteral($arguments[0]->getValue())
  162. ));
  163. }
  164. /**
  165. * {@inheritdoc}
  166. */
  167. public function getName()
  168. {
  169. return 'function';
  170. }
  171. }