HtmlExtension.php 6.5 KB

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