PseudoClassExtension.php 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122
  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\XPath\XPathExpr;
  13. /**
  14. * XPath expression translator pseudo-class extension.
  15. *
  16. * This component is a port of the Python cssselect library,
  17. * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
  18. *
  19. * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  20. *
  21. * @internal
  22. */
  23. class PseudoClassExtension extends AbstractExtension
  24. {
  25. public function getPseudoClassTranslators(): array
  26. {
  27. return [
  28. 'root' => $this->translateRoot(...),
  29. 'scope' => $this->translateScopePseudo(...),
  30. 'first-child' => $this->translateFirstChild(...),
  31. 'last-child' => $this->translateLastChild(...),
  32. 'first-of-type' => $this->translateFirstOfType(...),
  33. 'last-of-type' => $this->translateLastOfType(...),
  34. 'only-child' => $this->translateOnlyChild(...),
  35. 'only-of-type' => $this->translateOnlyOfType(...),
  36. 'empty' => $this->translateEmpty(...),
  37. ];
  38. }
  39. public function translateRoot(XPathExpr $xpath): XPathExpr
  40. {
  41. return $xpath->addCondition('not(parent::*)');
  42. }
  43. public function translateScopePseudo(XPathExpr $xpath): XPathExpr
  44. {
  45. return $xpath->addCondition('1');
  46. }
  47. public function translateFirstChild(XPathExpr $xpath): XPathExpr
  48. {
  49. return $xpath
  50. ->addStarPrefix()
  51. ->addNameTest()
  52. ->addCondition('position() = 1');
  53. }
  54. public function translateLastChild(XPathExpr $xpath): XPathExpr
  55. {
  56. return $xpath
  57. ->addStarPrefix()
  58. ->addNameTest()
  59. ->addCondition('position() = last()');
  60. }
  61. /**
  62. * @throws ExpressionErrorException
  63. */
  64. public function translateFirstOfType(XPathExpr $xpath): XPathExpr
  65. {
  66. if ('*' === $xpath->getElement()) {
  67. throw new ExpressionErrorException('"*:first-of-type" is not implemented.');
  68. }
  69. return $xpath
  70. ->addStarPrefix()
  71. ->addCondition('position() = 1');
  72. }
  73. /**
  74. * @throws ExpressionErrorException
  75. */
  76. public function translateLastOfType(XPathExpr $xpath): XPathExpr
  77. {
  78. if ('*' === $xpath->getElement()) {
  79. throw new ExpressionErrorException('"*:last-of-type" is not implemented.');
  80. }
  81. return $xpath
  82. ->addStarPrefix()
  83. ->addCondition('position() = last()');
  84. }
  85. public function translateOnlyChild(XPathExpr $xpath): XPathExpr
  86. {
  87. return $xpath
  88. ->addStarPrefix()
  89. ->addNameTest()
  90. ->addCondition('last() = 1');
  91. }
  92. public function translateOnlyOfType(XPathExpr $xpath): XPathExpr
  93. {
  94. $element = $xpath->getElement();
  95. return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));
  96. }
  97. public function translateEmpty(XPathExpr $xpath): XPathExpr
  98. {
  99. return $xpath->addCondition('not(*) and not(string-length())');
  100. }
  101. public function getName(): string
  102. {
  103. return 'pseudo-class';
  104. }
  105. }