HtmlExtension.php 6.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241
  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. /**
  28. * Constructor.
  29. *
  30. * @param Translator $translator
  31. */
  32. public function __construct(Translator $translator)
  33. {
  34. $translator
  35. ->getExtension('node')
  36. ->setFlag(NodeExtension::ELEMENT_NAME_IN_LOWER_CASE, true)
  37. ->setFlag(NodeExtension::ATTRIBUTE_NAME_IN_LOWER_CASE, true);
  38. }
  39. /**
  40. * {@inheritdoc}
  41. */
  42. public function getPseudoClassTranslators()
  43. {
  44. return array(
  45. 'checked' => array($this, 'translateChecked'),
  46. 'link' => array($this, 'translateLink'),
  47. 'disabled' => array($this, 'translateDisabled'),
  48. 'enabled' => array($this, 'translateEnabled'),
  49. 'selected' => array($this, 'translateSelected'),
  50. 'invalid' => array($this, 'translateInvalid'),
  51. 'hover' => array($this, 'translateHover'),
  52. 'visited' => array($this, 'translateVisited'),
  53. );
  54. }
  55. /**
  56. * {@inheritdoc}
  57. */
  58. public function getFunctionTranslators()
  59. {
  60. return array(
  61. 'lang' => array($this, 'translateLang'),
  62. );
  63. }
  64. /**
  65. * @param XPathExpr $xpath
  66. *
  67. * @return XPathExpr
  68. */
  69. public function translateChecked(XPathExpr $xpath)
  70. {
  71. return $xpath->addCondition(
  72. '(@checked '
  73. ."and (name(.) = 'input' or name(.) = 'command')"
  74. ."and (@type = 'checkbox' or @type = 'radio'))"
  75. );
  76. }
  77. /**
  78. * @param XPathExpr $xpath
  79. *
  80. * @return XPathExpr
  81. */
  82. public function translateLink(XPathExpr $xpath)
  83. {
  84. return $xpath->addCondition("@href and (name(.) = 'a' or name(.) = 'link' or name(.) = 'area')");
  85. }
  86. /**
  87. * @param XPathExpr $xpath
  88. *
  89. * @return XPathExpr
  90. */
  91. public function translateDisabled(XPathExpr $xpath)
  92. {
  93. return $xpath->addCondition(
  94. '('
  95. .'@disabled and'
  96. .'('
  97. ."(name(.) = 'input' and @type != 'hidden')"
  98. ." or name(.) = 'button'"
  99. ." or name(.) = 'select'"
  100. ." or name(.) = 'textarea'"
  101. ." or name(.) = 'command'"
  102. ." or name(.) = 'fieldset'"
  103. ." or name(.) = 'optgroup'"
  104. ." or name(.) = 'option'"
  105. .')'
  106. .') or ('
  107. ."(name(.) = 'input' and @type != 'hidden')"
  108. ." or name(.) = 'button'"
  109. ." or name(.) = 'select'"
  110. ." or name(.) = 'textarea'"
  111. .')'
  112. .' and ancestor::fieldset[@disabled]'
  113. );
  114. // todo: in the second half, add "and is not a descendant of that fieldset element's first legend element child, if any."
  115. }
  116. /**
  117. * @param XPathExpr $xpath
  118. *
  119. * @return XPathExpr
  120. */
  121. public function translateEnabled(XPathExpr $xpath)
  122. {
  123. return $xpath->addCondition(
  124. '('
  125. .'@href and ('
  126. ."name(.) = 'a'"
  127. ." or name(.) = 'link'"
  128. ." or name(.) = 'area'"
  129. .')'
  130. .') or ('
  131. .'('
  132. ."name(.) = 'command'"
  133. ." or name(.) = 'fieldset'"
  134. ." or name(.) = 'optgroup'"
  135. .')'
  136. .' and not(@disabled)'
  137. .') or ('
  138. .'('
  139. ."(name(.) = 'input' and @type != 'hidden')"
  140. ." or name(.) = 'button'"
  141. ." or name(.) = 'select'"
  142. ." or name(.) = 'textarea'"
  143. ." or name(.) = 'keygen'"
  144. .')'
  145. .' and not (@disabled or ancestor::fieldset[@disabled])'
  146. .') or ('
  147. ."name(.) = 'option' and not("
  148. .'@disabled or ancestor::optgroup[@disabled]'
  149. .')'
  150. .')'
  151. );
  152. }
  153. /**
  154. * @param XPathExpr $xpath
  155. * @param FunctionNode $function
  156. *
  157. * @return XPathExpr
  158. *
  159. * @throws ExpressionErrorException
  160. */
  161. public function translateLang(XPathExpr $xpath, FunctionNode $function)
  162. {
  163. $arguments = $function->getArguments();
  164. foreach ($arguments as $token) {
  165. if (!($token->isString() || $token->isIdentifier())) {
  166. throw new ExpressionErrorException(
  167. 'Expected a single string or identifier for :lang(), got '
  168. .implode(', ', $arguments)
  169. );
  170. }
  171. }
  172. return $xpath->addCondition(sprintf(
  173. 'ancestor-or-self::*[@lang][1][starts-with(concat('
  174. ."translate(@%s, 'ABCDEFGHIJKLMNOPQRSTUVWXYZ', 'abcdefghijklmnopqrstuvwxyz'), '-')"
  175. .', %s)]',
  176. 'lang',
  177. Translator::getXpathLiteral(strtolower($arguments[0]->getValue()).'-')
  178. ));
  179. }
  180. /**
  181. * @param XPathExpr $xpath
  182. *
  183. * @return XPathExpr
  184. */
  185. public function translateSelected(XPathExpr $xpath)
  186. {
  187. return $xpath->addCondition("(@selected and name(.) = 'option')");
  188. }
  189. /**
  190. * @param XPathExpr $xpath
  191. *
  192. * @return XPathExpr
  193. */
  194. public function translateInvalid(XPathExpr $xpath)
  195. {
  196. return $xpath->addCondition('0');
  197. }
  198. /**
  199. * @param XPathExpr $xpath
  200. *
  201. * @return XPathExpr
  202. */
  203. public function translateHover(XPathExpr $xpath)
  204. {
  205. return $xpath->addCondition('0');
  206. }
  207. /**
  208. * @param XPathExpr $xpath
  209. *
  210. * @return XPathExpr
  211. */
  212. public function translateVisited(XPathExpr $xpath)
  213. {
  214. return $xpath->addCondition('0');
  215. }
  216. /**
  217. * {@inheritdoc}
  218. */
  219. public function getName()
  220. {
  221. return 'html';
  222. }
  223. }