HtmlExtension.php 6.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  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\Node\FunctionNode;
  13. use Symfony\Component\CssSelector\XPath\Translator;
  14. use Symfony\Component\CssSelector\XPath\XPathExpr;
  15. /**
  16. * XPath expression translator HTML extension.
  17. *
  18. * This component is a port of the Python cssselect library,
  19. * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
  20. *
  21. * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  22. *
  23. * @internal
  24. */
  25. class HtmlExtension extends AbstractExtension
  26. {
  27. public function __construct(Translator $translator)
  28. {
  29. $translator
  30. ->getExtension('node')
  31. ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true)
  32. ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true);
  33. }
  34. /**
  35. * {@inheritdoc}
  36. */
  37. public function getPseudoClassTranslators()
  38. {
  39. return [
  40. 'checked' => [$this, 'translateChecked'],
  41. 'link' => [$this, 'translateLink'],
  42. 'disabled' => [$this, 'translateDisabled'],
  43. 'enabled' => [$this, 'translateEnabled'],
  44. 'selected' => [$this, 'translateSelected'],
  45. 'invalid' => [$this, 'translateInvalid'],
  46. 'hover' => [$this, 'translateHover'],
  47. 'visited' => [$this, 'translateVisited'],
  48. ];
  49. }
  50. /**
  51. * {@inheritdoc}
  52. */
  53. public function getFunctionTranslators()
  54. {
  55. return [
  56. 'lang' => [$this, 'translateLang'],
  57. ];
  58. }
  59. /**
  60. * @return XPathExpr
  61. */
  62. public function translateChecked(XPathExpr $xpath)
  63. {
  64. return $xpath->addCondition(
  65. '(@checked '
  66. ."and (name(.) = 'input' or name(.) = 'command')"
  67. ."and (@type = 'checkbox' or @type = 'radio'))"
  68. );
  69. }
  70. /**
  71. * @return XPathExpr
  72. */
  73. public function translateLink(XPathExpr $xpath)
  74. {
  75. return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')");
  76. }
  77. /**
  78. * @return XPathExpr
  79. */
  80. public function translateDisabled(XPathExpr $xpath)
  81. {
  82. return $xpath->addCondition(
  83. '('
  84. .'@disabled and'
  85. .'('
  86. ."(name(.) = 'input' and @type != 'hidden')"
  87. ." or name(.) = 'button'"
  88. ." or name(.) = 'select'"
  89. ." or name(.) = 'textarea'"
  90. ." or name(.) = 'command'"
  91. ." or name(.) = 'fieldset'"
  92. ." or name(.) = 'optgroup'"
  93. ." or name(.) = 'option'"
  94. .')'
  95. .') or ('
  96. ."(name(.) = 'input' and @type != 'hidden')"
  97. ." or name(.) = 'button'"
  98. ." or name(.) = 'select'"
  99. ." or name(.) = 'textarea'"
  100. .')'
  101. .' and ancestor::fieldset[@disabled]'
  102. );
  103. // todo: in the second half, add "and is not a descendant of that fieldset element's first legend element child, if any."
  104. }
  105. /**
  106. * @return XPathExpr
  107. */
  108. public function translateEnabled(XPathExpr $xpath)
  109. {
  110. return $xpath->addCondition(
  111. '('
  112. .'@href and ('
  113. ."name(.) = 'a'"
  114. ." or name(.) = 'link'"
  115. ." or name(.) = 'area'"
  116. .')'
  117. .') or ('
  118. .'('
  119. ."name(.) = 'command'"
  120. ." or name(.) = 'fieldset'"
  121. ." or name(.) = 'optgroup'"
  122. .')'
  123. .' and not(@disabled)'
  124. .') or ('
  125. .'('
  126. ."(name(.) = 'input' and @type != 'hidden')"
  127. ." or name(.) = 'button'"
  128. ." or name(.) = 'select'"
  129. ." or name(.) = 'textarea'"
  130. ." or name(.) = 'keygen'"
  131. .')'
  132. .' and not (@disabled or ancestor::fieldset[@disabled])'
  133. .') or ('
  134. ."name(.) = 'option' and not("
  135. .'@disabled or ancestor::optgroup[@disabled]'
  136. .')'
  137. .')'
  138. );
  139. }
  140. /**
  141. * @return XPathExpr
  142. *
  143. * @throws ExpressionErrorException
  144. */
  145. public function translateLang(XPathExpr $xpath, FunctionNode $function)
  146. {
  147. $arguments = $function->getArguments();
  148. foreach ($arguments as $token) {
  149. if (!($token->isString() || $token->isIdentifier())) {
  150. throw new ExpressionErrorException('Expected a single string or identifier for :lang(), got '.implode(', ', $arguments));
  151. }
  152. }
  153. return $xpath->addCondition(sprintf(
  154. 'ancestor-or-self::*[@lang][1][starts-with(concat('
  155. ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')"
  156. .', %s)]',
  157. 'lang',
  158. Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-')
  159. ));
  160. }
  161. /**
  162. * @return XPathExpr
  163. */
  164. public function translateSelected(XPathExpr $xpath)
  165. {
  166. return $xpath->addCondition("(@selected and name(.) = 'option')");
  167. }
  168. /**
  169. * @return XPathExpr
  170. */
  171. public function translateInvalid(XPathExpr $xpath)
  172. {
  173. return $xpath->addCondition('0');
  174. }
  175. /**
  176. * @return XPathExpr
  177. */
  178. public function translateHover(XPathExpr $xpath)
  179. {
  180. return $xpath->addCondition('0');
  181. }
  182. /**
  183. * @return XPathExpr
  184. */
  185. public function translateVisited(XPathExpr $xpath)
  186. {
  187. return $xpath->addCondition('0');
  188. }
  189. /**
  190. * {@inheritdoc}
  191. */
  192. public function getName()
  193. {
  194. return 'html';
  195. }
  196. }