DomQuery37.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Test
  17. * @subpackage PHPUnit
  18. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  19. * @license http://framework.zend.com/license/new-bsd New BSD License
  20. * @version $Id$
  21. */
  22. if (version_compare(PHPUnit_Runner_Version::id(), '4.0.0', '<')) {
  23. /** @see PHPUnit_Framework_Constraint */
  24. require_once 'PHPUnit/Framework/Constraint.php';
  25. }
  26. /** @see Zend_Dom_Query */
  27. require_once 'Zend/Dom/Query.php';
  28. /**
  29. * Zend_Dom_Query-based PHPUnit Constraint
  30. *
  31. * @uses PHPUnit_Framework_Constraint
  32. * @category Zend
  33. * @package Zend_Test
  34. * @subpackage PHPUnit
  35. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  36. * @license http://framework.zend.com/license/new-bsd New BSD License
  37. */
  38. class Zend_Test_PHPUnit_Constraint_DomQuery extends PHPUnit_Framework_Constraint
  39. {
  40. /**#@+
  41. * Assertion type constants
  42. */
  43. const ASSERT_QUERY = 'assertQuery';
  44. const ASSERT_CONTENT_CONTAINS = 'assertQueryContentContains';
  45. const ASSERT_CONTENT_REGEX = 'assertQueryContentRegex';
  46. const ASSERT_CONTENT_COUNT = 'assertQueryCount';
  47. const ASSERT_CONTENT_COUNT_MIN= 'assertQueryCountMin';
  48. const ASSERT_CONTENT_COUNT_MAX= 'assertQueryCountMax';
  49. /**#@-*/
  50. /**
  51. * Current assertion type
  52. * @var string
  53. */
  54. protected $_assertType = null;
  55. /**
  56. * Available assertion types
  57. * @var array
  58. */
  59. protected $_assertTypes = array(
  60. self::ASSERT_QUERY,
  61. self::ASSERT_CONTENT_CONTAINS,
  62. self::ASSERT_CONTENT_REGEX,
  63. self::ASSERT_CONTENT_COUNT,
  64. self::ASSERT_CONTENT_COUNT_MIN,
  65. self::ASSERT_CONTENT_COUNT_MAX,
  66. );
  67. /**
  68. * Content being matched
  69. * @var string
  70. */
  71. protected $_content = null;
  72. /**
  73. * Whether or not assertion is negated
  74. * @var bool
  75. */
  76. protected $_negate = false;
  77. /**
  78. * CSS selector or XPath path to select against
  79. * @var string
  80. */
  81. protected $_path = null;
  82. /**
  83. * Whether or not to use XPath when querying
  84. * @var bool
  85. */
  86. protected $_useXpath = false;
  87. /**
  88. * XPath namespaces
  89. * @var array
  90. */
  91. protected $_xpathNamespaces = array();
  92. /**
  93. * Constructor; setup constraint state
  94. *
  95. * @param string $path CSS selector path
  96. * @return void
  97. */
  98. public function __construct($path)
  99. {
  100. $this->_path = $path;
  101. }
  102. /**
  103. * Indicate negative match
  104. *
  105. * @param bool $flag
  106. * @return void
  107. */
  108. public function setNegate($flag = true)
  109. {
  110. $this->_negate = $flag;
  111. }
  112. /**
  113. * Whether or not path is a straight XPath expression
  114. *
  115. * @param bool $flag
  116. * @return Zend_Test_PHPUnit_Constraint_DomQuery
  117. */
  118. public function setUseXpath($flag = true)
  119. {
  120. $this->_useXpath = (bool) $flag;
  121. return $this;
  122. }
  123. /**
  124. * Evaluate an object to see if it fits the constraints
  125. *
  126. * @param string Response content to be matched against (haystack)
  127. * @param null|string Assertion type
  128. * @param string (optional) String to match (needle), may be required depending on assertion type
  129. * @return bool
  130. *
  131. * NOTE:
  132. * Drastic changes up to PHPUnit 3.5.15 this was:
  133. * public function evaluate($other, $assertType = null)
  134. * In PHPUnit 3.6.0 they changed the interface into this:
  135. * public function evaluate($other, $description = '', $returnResult = FALSE)
  136. * We use the new interface for PHP-strict checking, but emulate the old one
  137. */
  138. public function evaluate($content, $assertType = '', $match = FALSE)
  139. {
  140. if (strstr($assertType, 'Not')) {
  141. $this->setNegate(true);
  142. $assertType = str_replace('Not', '', $assertType);
  143. }
  144. if (strstr($assertType, 'Xpath')) {
  145. $this->setUseXpath(true);
  146. $assertType = str_replace('Xpath', 'Query', $assertType);
  147. }
  148. if (!in_array($assertType, $this->_assertTypes)) {
  149. require_once 'Zend/Test/PHPUnit/Constraint/Exception.php';
  150. throw new Zend_Test_PHPUnit_Constraint_Exception(sprintf('Invalid assertion type "%s" provided to %s constraint', $assertType, __CLASS__));
  151. }
  152. $this->_assertType = $assertType;
  153. $method = $this->_useXpath ? 'queryXpath' : 'query';
  154. $domQuery = new Zend_Dom_Query($content);
  155. $domQuery->registerXpathNamespaces($this->_xpathNamespaces);
  156. $result = $domQuery->$method($this->_path);
  157. switch ($assertType) {
  158. case self::ASSERT_CONTENT_CONTAINS:
  159. if (!$match) {
  160. require_once 'Zend/Test/PHPUnit/Constraint/Exception.php';
  161. throw new Zend_Test_PHPUnit_Constraint_Exception('No content provided against which to match');
  162. }
  163. $this->_content = $match;
  164. return ($this->_negate)
  165. ? $this->_notMatchContent($result, $match)
  166. : $this->_matchContent($result, $match);
  167. case self::ASSERT_CONTENT_REGEX:
  168. if (!$match) {
  169. require_once 'Zend/Test/PHPUnit/Constraint/Exception.php';
  170. throw new Zend_Test_PHPUnit_Constraint_Exception('No pattern provided against which to match');
  171. }
  172. $this->_content = $match;
  173. return ($this->_negate)
  174. ? $this->_notRegexContent($result, $match)
  175. : $this->_regexContent($result, $match);
  176. case self::ASSERT_CONTENT_COUNT:
  177. case self::ASSERT_CONTENT_COUNT_MIN:
  178. case self::ASSERT_CONTENT_COUNT_MAX:
  179. if ($match === false) {
  180. require_once 'Zend/Test/PHPUnit/Constraint/Exception.php';
  181. throw new Zend_Test_PHPUnit_Constraint_Exception('No count provided against which to compare');
  182. }
  183. $this->_content = $match;
  184. return $this->_countContent($result, $match, $assertType);
  185. case self::ASSERT_QUERY:
  186. default:
  187. if ($this->_negate) {
  188. return (0 == count($result));
  189. } else {
  190. return (0 != count($result));
  191. }
  192. }
  193. }
  194. /**
  195. * Report Failure
  196. *
  197. * @see PHPUnit_Framework_Constraint for implementation details
  198. * @param mixed CSS selector path
  199. * @param string Failure description
  200. * @param object Cannot be used, null
  201. * @return void
  202. * @throws PHPUnit_Framework_ExpectationFailedException
  203. * NOTE:
  204. * Drastic changes up to PHPUnit 3.5.15 this was:
  205. * public function fail($other, $description, $not = false)
  206. * In PHPUnit 3.6.0 they changed the interface into this:
  207. * protected function fail($other, $description, PHPUnit_Framework_ComparisonFailure $comparisonFailure = NULL)
  208. * We use the new interface for PHP-strict checking
  209. */
  210. public function fail($other, $description, PHPUnit_Framework_ComparisonFailure $cannot_be_used = NULL)
  211. {
  212. require_once 'Zend/Test/PHPUnit/Constraint/Exception.php';
  213. switch ($this->_assertType) {
  214. case self::ASSERT_CONTENT_CONTAINS:
  215. $failure = 'Failed asserting node denoted by %s CONTAINS content "%s"';
  216. if ($this->_negate) {
  217. $failure = 'Failed asserting node DENOTED BY %s DOES NOT CONTAIN content "%s"';
  218. }
  219. $failure = sprintf($failure, $other, $this->_content);
  220. break;
  221. case self::ASSERT_CONTENT_REGEX:
  222. $failure = 'Failed asserting node denoted by %s CONTAINS content MATCHING "%s"';
  223. if ($this->_negate) {
  224. $failure = 'Failed asserting node DENOTED BY %s DOES NOT CONTAIN content MATCHING "%s"';
  225. }
  226. $failure = sprintf($failure, $other, $this->_content);
  227. break;
  228. case self::ASSERT_CONTENT_COUNT:
  229. $failure = 'Failed asserting node DENOTED BY %s OCCURS EXACTLY %d times';
  230. if ($this->_negate) {
  231. $failure = 'Failed asserting node DENOTED BY %s DOES NOT OCCUR EXACTLY %d times';
  232. }
  233. $failure = sprintf($failure, $other, $this->_content);
  234. break;
  235. case self::ASSERT_CONTENT_COUNT_MIN:
  236. $failure = 'Failed asserting node DENOTED BY %s OCCURS AT LEAST %d times';
  237. $failure = sprintf($failure, $other, $this->_content);
  238. break;
  239. case self::ASSERT_CONTENT_COUNT_MAX:
  240. $failure = 'Failed asserting node DENOTED BY %s OCCURS AT MOST %d times';
  241. $failure = sprintf($failure, $other, $this->_content);
  242. break;
  243. case self::ASSERT_QUERY:
  244. default:
  245. $failure = 'Failed asserting node DENOTED BY %s EXISTS';
  246. if ($this->_negate) {
  247. $failure = 'Failed asserting node DENOTED BY %s DOES NOT EXIST';
  248. }
  249. $failure = sprintf($failure, $other);
  250. break;
  251. }
  252. if (!empty($description)) {
  253. $failure = $description . "\n" . $failure;
  254. }
  255. throw new Zend_Test_PHPUnit_Constraint_Exception($failure);
  256. }
  257. /**
  258. * Complete implementation
  259. *
  260. * @return string
  261. */
  262. public function toString()
  263. {
  264. return '';
  265. }
  266. /**
  267. * Register XPath namespaces
  268. *
  269. * @param array $xpathNamespaces
  270. * @return void
  271. */
  272. public function registerXpathNamespaces($xpathNamespaces)
  273. {
  274. $this->_xpathNamespaces = $xpathNamespaces;
  275. }
  276. /**
  277. * Check to see if content is matched in selected nodes
  278. *
  279. * @param Zend_Dom_Query_Result $result
  280. * @param string $match Content to match
  281. * @return bool
  282. */
  283. protected function _matchContent($result, $match)
  284. {
  285. $match = (string) $match;
  286. if (0 == count($result)) {
  287. return false;
  288. }
  289. foreach ($result as $node) {
  290. $content = $this->_getNodeContent($node);
  291. if (strstr($content, $match)) {
  292. return true;
  293. }
  294. }
  295. return false;
  296. }
  297. /**
  298. * Check to see if content is NOT matched in selected nodes
  299. *
  300. * @param Zend_Dom_Query_Result $result
  301. * @param string $match
  302. * @return bool
  303. */
  304. protected function _notMatchContent($result, $match)
  305. {
  306. if (0 == count($result)) {
  307. return true;
  308. }
  309. foreach ($result as $node) {
  310. $content = $this->_getNodeContent($node);
  311. if (strstr($content, $match)) {
  312. return false;
  313. }
  314. }
  315. return true;
  316. }
  317. /**
  318. * Check to see if content is matched by regex in selected nodes
  319. *
  320. * @param Zend_Dom_Query_Result $result
  321. * @param string $pattern
  322. * @return bool
  323. */
  324. protected function _regexContent($result, $pattern)
  325. {
  326. if (0 == count($result)) {
  327. return false;
  328. }
  329. foreach ($result as $node) {
  330. $content = $this->_getNodeContent($node);
  331. if (preg_match($pattern, $content)) {
  332. return true;
  333. }
  334. }
  335. return false;
  336. }
  337. /**
  338. * Check to see if content is NOT matched by regex in selected nodes
  339. *
  340. * @param Zend_Dom_Query_Result $result
  341. * @param string $pattern
  342. * @return bool
  343. */
  344. protected function _notRegexContent($result, $pattern)
  345. {
  346. if (0 == count($result)) {
  347. return true;
  348. }
  349. foreach ($result as $node) {
  350. $content = $this->_getNodeContent($node);
  351. if (preg_match($pattern, $content)) {
  352. return false;
  353. }
  354. }
  355. return true;
  356. }
  357. /**
  358. * Determine if content count matches criteria
  359. *
  360. * @param Zend_Dom_Query_Result $result
  361. * @param int $test Value against which to test
  362. * @param string $type assertion type
  363. * @return boolean
  364. */
  365. protected function _countContent($result, $test, $type)
  366. {
  367. $count = count($result);
  368. switch ($type) {
  369. case self::ASSERT_CONTENT_COUNT:
  370. return ($this->_negate)
  371. ? ($test != $count)
  372. : ($test == $count);
  373. case self::ASSERT_CONTENT_COUNT_MIN:
  374. return ($count >= $test);
  375. case self::ASSERT_CONTENT_COUNT_MAX:
  376. return ($count <= $test);
  377. default:
  378. return false;
  379. }
  380. }
  381. /**
  382. * Get node content, minus node markup tags
  383. *
  384. * @param DOMNode $node
  385. * @return string
  386. */
  387. protected function _getNodeContent(DOMNode $node)
  388. {
  389. if ($node instanceof DOMAttr) {
  390. return $node->value;
  391. } else {
  392. $doc = $node->ownerDocument;
  393. $content = $doc->saveXML($node);
  394. $tag = $node->nodeName;
  395. $regex = '|</?' . $tag . '[^>]*>|';
  396. return preg_replace($regex, '', $content);
  397. }
  398. }
  399. }