菜谱项目

NodeExtension.php 6.2KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243
  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\Node;
  12. use Symfony\Component\CssSelector\XPath\Translator;
  13. use Symfony\Component\CssSelector\XPath\XPathExpr;
  14. /**
  15. * XPath expression translator node extension.
  16. *
  17. * This component is a port of the Python cssselect library,
  18. * which is copyright Ian Bicking, @see https://github.com/SimonSapin/cssselect.
  19. *
  20. * @author Jean-François Simon <jeanfrancois.simon@sensiolabs.com>
  21. *
  22. * @internal
  23. */
  24. class NodeExtension extends AbstractExtension
  25. {
  26. const ELEMENT_NAME_IN_LOWER_CASE = 1;
  27. const ATTRIBUTE_NAME_IN_LOWER_CASE = 2;
  28. const ATTRIBUTE_VALUE_IN_LOWER_CASE = 4;
  29. private $flags;
  30. /**
  31. * @param int $flags
  32. */
  33. public function __construct($flags = 0)
  34. {
  35. $this->flags = $flags;
  36. }
  37. /**
  38. * @param int $flag
  39. * @param bool $on
  40. *
  41. * @return $this
  42. */
  43. public function setFlag($flag, $on)
  44. {
  45. if ($on && !$this->hasFlag($flag)) {
  46. $this->flags += $flag;
  47. }
  48. if (!$on && $this->hasFlag($flag)) {
  49. $this->flags -= $flag;
  50. }
  51. return $this;
  52. }
  53. /**
  54. * @param int $flag
  55. *
  56. * @return bool
  57. */
  58. public function hasFlag($flag)
  59. {
  60. return (bool) ($this->flags & $flag);
  61. }
  62. /**
  63. * {@inheritdoc}
  64. */
  65. public function getNodeTranslators()
  66. {
  67. return array(
  68. 'Selector' => array($this, 'translateSelector'),
  69. 'CombinedSelector' => array($this, 'translateCombinedSelector'),
  70. 'Negation' => array($this, 'translateNegation'),
  71. 'Function' => array($this, 'translateFunction'),
  72. 'Pseudo' => array($this, 'translatePseudo'),
  73. 'Attribute' => array($this, 'translateAttribute'),
  74. 'Class' => array($this, 'translateClass'),
  75. 'Hash' => array($this, 'translateHash'),
  76. 'Element' => array($this, 'translateElement'),
  77. );
  78. }
  79. /**
  80. * @return XPathExpr
  81. */
  82. public function translateSelector(Node\SelectorNode $node, Translator $translator)
  83. {
  84. return $translator->nodeToXPath($node->getTree());
  85. }
  86. /**
  87. * @return XPathExpr
  88. */
  89. public function translateCombinedSelector(Node\CombinedSelectorNode $node, Translator $translator)
  90. {
  91. return $translator->addCombination($node->getCombinator(), $node->getSelector(), $node->getSubSelector());
  92. }
  93. /**
  94. * @return XPathExpr
  95. */
  96. public function translateNegation(Node\NegationNode $node, Translator $translator)
  97. {
  98. $xpath = $translator->nodeToXPath($node->getSelector());
  99. $subXpath = $translator->nodeToXPath($node->getSubSelector());
  100. $subXpath->addNameTest();
  101. if ($subXpath->getCondition()) {
  102. return $xpath->addCondition(sprintf('not(%s)', $subXpath->getCondition()));
  103. }
  104. return $xpath->addCondition('0');
  105. }
  106. /**
  107. * @return XPathExpr
  108. */
  109. public function translateFunction(Node\FunctionNode $node, Translator $translator)
  110. {
  111. $xpath = $translator->nodeToXPath($node->getSelector());
  112. return $translator->addFunction($xpath, $node);
  113. }
  114. /**
  115. * @return XPathExpr
  116. */
  117. public function translatePseudo(Node\PseudoNode $node, Translator $translator)
  118. {
  119. $xpath = $translator->nodeToXPath($node->getSelector());
  120. return $translator->addPseudoClass($xpath, $node->getIdentifier());
  121. }
  122. /**
  123. * @return XPathExpr
  124. */
  125. public function translateAttribute(Node\AttributeNode $node, Translator $translator)
  126. {
  127. $name = $node->getAttribute();
  128. $safe = $this->isSafeName($name);
  129. if ($this->hasFlag(self::ATTRIBUTE_NAME_IN_LOWER_CASE)) {
  130. $name = strtolower($name);
  131. }
  132. if ($node->getNamespace()) {
  133. $name = sprintf('%s:%s', $node->getNamespace(), $name);
  134. $safe = $safe && $this->isSafeName($node->getNamespace());
  135. }
  136. $attribute = $safe ? '@'.$name : sprintf('attribute::*[name() = %s]', Translator::getXpathLiteral($name));
  137. $value = $node->getValue();
  138. $xpath = $translator->nodeToXPath($node->getSelector());
  139. if ($this->hasFlag(self::ATTRIBUTE_VALUE_IN_LOWER_CASE)) {
  140. $value = strtolower($value);
  141. }
  142. return $translator->addAttributeMatching($xpath, $node->getOperator(), $attribute, $value);
  143. }
  144. /**
  145. * @return XPathExpr
  146. */
  147. public function translateClass(Node\ClassNode $node, Translator $translator)
  148. {
  149. $xpath = $translator->nodeToXPath($node->getSelector());
  150. return $translator->addAttributeMatching($xpath, '~=', '@class', $node->getName());
  151. }
  152. /**
  153. * @return XPathExpr
  154. */
  155. public function translateHash(Node\HashNode $node, Translator $translator)
  156. {
  157. $xpath = $translator->nodeToXPath($node->getSelector());
  158. return $translator->addAttributeMatching($xpath, '=', '@id', $node->getId());
  159. }
  160. /**
  161. * @return XPathExpr
  162. */
  163. public function translateElement(Node\ElementNode $node)
  164. {
  165. $element = $node->getElement();
  166. if ($this->hasFlag(self::ELEMENT_NAME_IN_LOWER_CASE)) {
  167. $element = strtolower($element);
  168. }
  169. if ($element) {
  170. $safe = $this->isSafeName($element);
  171. } else {
  172. $element = '*';
  173. $safe = true;
  174. }
  175. if ($node->getNamespace()) {
  176. $element = sprintf('%s:%s', $node->getNamespace(), $element);
  177. $safe = $safe && $this->isSafeName($node->getNamespace());
  178. }
  179. $xpath = new XPathExpr('', $element);
  180. if (!$safe) {
  181. $xpath->addNameTest();
  182. }
  183. return $xpath;
  184. }
  185. /**
  186. * {@inheritdoc}
  187. */
  188. public function getName()
  189. {
  190. return 'node';
  191. }
  192. /**
  193. * Tests if given name is safe.
  194. *
  195. * @param string $name
  196. *
  197. * @return bool
  198. */
  199. private function isSafeName($name)
  200. {
  201. return 0 < preg_match('~^[a-zA-Z_][a-zA-Z0-9_.-]*$~', $name);
  202. }
  203. }