XPathExpr.php 6.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254
  1. <?php
  2. /*
  3. * This file is part of the Symfony package.
  4. *
  5. * (c) Fabien Potencier <fabien.potencier@symfony-project.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;
  11. /**
  12. * XPathExpr represents an XPath expression.
  13. *
  14. * This component is a port of the Python lxml library,
  15. * which is copyright Infrae and distributed under the BSD license.
  16. *
  17. * @author Fabien Potencier <fabien.potencier@symfony-project.com>
  18. */
  19. class XPathExpr
  20. {
  21. protected $prefix;
  22. protected $path;
  23. protected $element;
  24. protected $condition;
  25. protected $starPrefix;
  26. /**
  27. * Constructor.
  28. *
  29. * @param string $prefix Prefix for the XPath expression.
  30. * @param string $path Actual path of the expression.
  31. * @param string $element The element in the expression.
  32. * @param string $condition A condition for the expression.
  33. * @param Boolean $starPrefix Indicates whether to use a star prefix.
  34. */
  35. public function __construct($prefix = null, $path = null, $element = '*', $condition = null, $starPrefix = false)
  36. {
  37. $this->prefix = $prefix;
  38. $this->path = $path;
  39. $this->element = $element;
  40. $this->condition = $condition;
  41. $this->starPrefix = $starPrefix;
  42. }
  43. /**
  44. * Gets the prefix of this XPath expression.
  45. *
  46. * @return string
  47. */
  48. public function getPrefix()
  49. {
  50. return $this->prefix;
  51. }
  52. /**
  53. * Gets the path of this XPath expression.
  54. *
  55. * @return string
  56. */
  57. public function getPath()
  58. {
  59. return $this->path;
  60. }
  61. /**
  62. * Answers whether this XPath expression has a star prefix.
  63. *
  64. * @return Boolean
  65. */
  66. public function hasStarPrefix()
  67. {
  68. return $this->starPrefix;
  69. }
  70. /**
  71. * Gets the element of this XPath expression.
  72. *
  73. * @return string
  74. */
  75. public function getElement()
  76. {
  77. return $this->element;
  78. }
  79. /**
  80. * Gets the condition of this XPath expression.
  81. *
  82. * @return string
  83. */
  84. public function getCondition()
  85. {
  86. return $this->condition;
  87. }
  88. /**
  89. * Gets a string representation for this XPath expression.
  90. *
  91. * @return string
  92. */
  93. public function __toString()
  94. {
  95. $path = '';
  96. if (null !== $this->prefix) {
  97. $path .= $this->prefix;
  98. }
  99. if (null !== $this->path) {
  100. $path .= $this->path;
  101. }
  102. $path .= $this->element;
  103. if ($this->condition) {
  104. $path .= sprintf('[%s]', $this->condition);
  105. }
  106. return $path;
  107. }
  108. /**
  109. * Adds a condition to this XPath expression.
  110. * Any pre-existent condition will be ANDed to it.
  111. *
  112. * @param string $condition The condition to add.
  113. */
  114. public function addCondition($condition)
  115. {
  116. if ($this->condition) {
  117. $this->condition = sprintf('%s and (%s)', $this->condition, $condition);
  118. } else {
  119. $this->condition = $condition;
  120. }
  121. }
  122. /**
  123. * Adds a prefix to this XPath expression.
  124. * It will be prepended to any pre-existent prefixes.
  125. *
  126. * @param string $prefix The prefix to add.
  127. */
  128. public function addPrefix($prefix)
  129. {
  130. if ($this->prefix) {
  131. $this->prefix = $prefix.$this->prefix;
  132. } else {
  133. $this->prefix = $prefix;
  134. }
  135. }
  136. /**
  137. * Adds a condition to this XPath expression using the name of the element
  138. * as the desired value.
  139. * This method resets the element to '*'.
  140. */
  141. public function addNameTest()
  142. {
  143. if ($this->element == '*') {
  144. // We weren't doing a test anyway
  145. return;
  146. }
  147. $this->addCondition(sprintf('name() = %s', XPathExpr::xpathLiteral($this->element)));
  148. $this->element = '*';
  149. }
  150. /**
  151. * Adds a star prefix to this XPath expression.
  152. * This method will prepend a '*' to the path and set the star prefix flag
  153. * to true.
  154. */
  155. public function addStarPrefix()
  156. {
  157. /*
  158. Adds a /* prefix if there is no prefix. This is when you need
  159. to keep context's constrained to a single parent.
  160. */
  161. if ($this->path) {
  162. $this->path .= '*/';
  163. } else {
  164. $this->path = '*/';
  165. }
  166. $this->starPrefix = true;
  167. }
  168. /**
  169. * Joins this XPath expression with $other (another XPath expression) using
  170. * $combiner to join them.
  171. *
  172. * @param string $combiner The combiner string.
  173. * @param XPathExpr $other The other XPath expression to combine with
  174. * this one.
  175. */
  176. public function join($combiner, $other)
  177. {
  178. $prefix = (string) $this;
  179. $prefix .= $combiner;
  180. $path = $other->getPrefix().$other->getPath();
  181. /* We don't need a star prefix if we are joining to this other
  182. prefix; so we'll get rid of it */
  183. if ($other->hasStarPrefix() && '*/' == $path) {
  184. $path = '';
  185. }
  186. $this->prefix = $prefix;
  187. $this->path = $path;
  188. $this->element = $other->getElement();
  189. $this->condition = $other->GetCondition();
  190. }
  191. /**
  192. * Gets an XPath literal for $s.
  193. *
  194. * @param mixed $s Can either be a Node\ElementNode or a string.
  195. *
  196. * @return string
  197. */
  198. static public function xpathLiteral($s)
  199. {
  200. if ($s instanceof Node\ElementNode) {
  201. // This is probably a symbol that looks like an expression...
  202. $s = $s->formatElement();
  203. } else {
  204. $s = (string) $s;
  205. }
  206. if (false === strpos($s, "'")) {
  207. return sprintf("'%s'", $s);
  208. }
  209. if (false === strpos($s, '"')) {
  210. return sprintf('"%s"', $s);
  211. }
  212. $string = $s;
  213. $parts = array();
  214. while (true) {
  215. if (false !== $pos = strpos($string, "'")) {
  216. $parts[] = sprintf("'%s'", substr($string, 0, $pos));
  217. $parts[] = "\"'\"";
  218. $string = substr($string, $pos + 1);
  219. } else {
  220. $parts[] = "'$string'";
  221. break;
  222. }
  223. }
  224. return sprintf('concat(%s)', implode($parts, ', '));
  225. }
  226. }