PseudoClassExtension.php 3.3 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. /**
  26. * {@inheritdoc}
  27. */
  28. public function getPseudoClassTranslators(): array
  29. {
  30. return [
  31. 'root' => [$this, 'translateRoot'],
  32. 'first-child' => [$this, 'translateFirstChild'],
  33. 'last-child' => [$this, 'translateLastChild'],
  34. 'first-of-type' => [$this, 'translateFirstOfType'],
  35. 'last-of-type' => [$this, 'translateLastOfType'],
  36. 'only-child' => [$this, 'translateOnlyChild'],
  37. 'only-of-type' => [$this, 'translateOnlyOfType'],
  38. 'empty' => [$this, 'translateEmpty'],
  39. ];
  40. }
  41. public function translateRoot(XPathExpr $xpath): XPathExpr
  42. {
  43. return $xpath->addCondition('not(parent::*)');
  44. }
  45. public function translateFirstChild(XPathExpr $xpath): XPathExpr
  46. {
  47. return $xpath
  48. ->addStarPrefix()
  49. ->addNameTest()
  50. ->addCondition('position() = 1');
  51. }
  52. public function translateLastChild(XPathExpr $xpath): XPathExpr
  53. {
  54. return $xpath
  55. ->addStarPrefix()
  56. ->addNameTest()
  57. ->addCondition('position() = last()');
  58. }
  59. /**
  60. * @throws ExpressionErrorException
  61. */
  62. public function translateFirstOfType(XPathExpr $xpath): XPathExpr
  63. {
  64. if ('*' === $xpath->getElement()) {
  65. throw new ExpressionErrorException('"*:first-of-type" is not implemented.');
  66. }
  67. return $xpath
  68. ->addStarPrefix()
  69. ->addCondition('position() = 1');
  70. }
  71. /**
  72. * @throws ExpressionErrorException
  73. */
  74. public function translateLastOfType(XPathExpr $xpath): XPathExpr
  75. {
  76. if ('*' === $xpath->getElement()) {
  77. throw new ExpressionErrorException('"*:last-of-type" is not implemented.');
  78. }
  79. return $xpath
  80. ->addStarPrefix()
  81. ->addCondition('position() = last()');
  82. }
  83. public function translateOnlyChild(XPathExpr $xpath): XPathExpr
  84. {
  85. return $xpath
  86. ->addStarPrefix()
  87. ->addNameTest()
  88. ->addCondition('last() = 1');
  89. }
  90. public function translateOnlyOfType(XPathExpr $xpath): XPathExpr
  91. {
  92. $element = $xpath->getElement();
  93. return $xpath->addCondition(sprintf('count(preceding-sibling::%s)=0 and count(following-sibling::%s)=0', $element, $element));
  94. }
  95. public function translateEmpty(XPathExpr $xpath): XPathExpr
  96. {
  97. return $xpath->addCondition('not(*) and not(string-length())');
  98. }
  99. /**
  100. * {@inheritdoc}
  101. */
  102. public function getName(): string
  103. {
  104. return 'pseudo-class';
  105. }
  106. }