xml.php 19KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648
  1. <?php
  2. /**
  3. * base include file for SimpleTest
  4. * @package SimpleTest
  5. * @subpackage UnitTester
  6. * @version $Id: xml.php 1787 2008-04-26 20:35:39Z pp11 $
  7. */
  8. /**#@+
  9. * include other SimpleTest class files
  10. */
  11. require_once(dirname(__FILE__) . '/scorer.php');
  12. /**#@-*/
  13. /**
  14. * Creates the XML needed for remote communication
  15. * by SimpleTest.
  16. * @package SimpleTest
  17. * @subpackage UnitTester
  18. */
  19. class XmlReporter extends SimpleReporter {
  20. private $indent;
  21. private $namespace;
  22. /**
  23. * Sets up indentation and namespace.
  24. * @param string $namespace Namespace to add to each tag.
  25. * @param string $indent Indenting to add on each nesting.
  26. * @access public
  27. */
  28. function __construct($namespace = false, $indent = ' ') {
  29. parent::__construct();
  30. $this->namespace = ($namespace ? $namespace . ':' : '');
  31. $this->indent = $indent;
  32. }
  33. /**
  34. * Calculates the pretty printing indent level
  35. * from the current level of nesting.
  36. * @param integer $offset Extra indenting level.
  37. * @return string Leading space.
  38. * @access protected
  39. */
  40. protected function getIndent($offset = 0) {
  41. return str_repeat(
  42. $this->indent,
  43. count($this->getTestList()) + $offset);
  44. }
  45. /**
  46. * Converts character string to parsed XML
  47. * entities string.
  48. * @param string text Unparsed character data.
  49. * @return string Parsed character data.
  50. * @access public
  51. */
  52. function toParsedXml($text) {
  53. return str_replace(
  54. array('&', '<', '>', '"', '\''),
  55. array('&amp;', '&lt;', '&gt;', '&quot;', '&apos;'),
  56. $text);
  57. }
  58. /**
  59. * Paints the start of a group test.
  60. * @param string $test_name Name of test that is starting.
  61. * @param integer $size Number of test cases starting.
  62. * @access public
  63. */
  64. function paintGroupStart($test_name, $size) {
  65. parent::paintGroupStart($test_name, $size);
  66. print $this->getIndent();
  67. print "<" . $this->namespace . "group size=\"$size\">\n";
  68. print $this->getIndent(1);
  69. print "<" . $this->namespace . "name>" .
  70. $this->toParsedXml($test_name) .
  71. "</" . $this->namespace . "name>\n";
  72. }
  73. /**
  74. * Paints the end of a group test.
  75. * @param string $test_name Name of test that is ending.
  76. * @access public
  77. */
  78. function paintGroupEnd($test_name) {
  79. print $this->getIndent();
  80. print "</" . $this->namespace . "group>\n";
  81. parent::paintGroupEnd($test_name);
  82. }
  83. /**
  84. * Paints the start of a test case.
  85. * @param string $test_name Name of test that is starting.
  86. * @access public
  87. */
  88. function paintCaseStart($test_name) {
  89. parent::paintCaseStart($test_name);
  90. print $this->getIndent();
  91. print "<" . $this->namespace . "case>\n";
  92. print $this->getIndent(1);
  93. print "<" . $this->namespace . "name>" .
  94. $this->toParsedXml($test_name) .
  95. "</" . $this->namespace . "name>\n";
  96. }
  97. /**
  98. * Paints the end of a test case.
  99. * @param string $test_name Name of test that is ending.
  100. * @access public
  101. */
  102. function paintCaseEnd($test_name) {
  103. print $this->getIndent();
  104. print "</" . $this->namespace . "case>\n";
  105. parent::paintCaseEnd($test_name);
  106. }
  107. /**
  108. * Paints the start of a test method.
  109. * @param string $test_name Name of test that is starting.
  110. * @access public
  111. */
  112. function paintMethodStart($test_name) {
  113. parent::paintMethodStart($test_name);
  114. print $this->getIndent();
  115. print "<" . $this->namespace . "test>\n";
  116. print $this->getIndent(1);
  117. print "<" . $this->namespace . "name>" .
  118. $this->toParsedXml($test_name) .
  119. "</" . $this->namespace . "name>\n";
  120. }
  121. /**
  122. * Paints the end of a test method.
  123. * @param string $test_name Name of test that is ending.
  124. * @param integer $progress Number of test cases ending.
  125. * @access public
  126. */
  127. function paintMethodEnd($test_name) {
  128. print $this->getIndent();
  129. print "</" . $this->namespace . "test>\n";
  130. parent::paintMethodEnd($test_name);
  131. }
  132. /**
  133. * Paints pass as XML.
  134. * @param string $message Message to encode.
  135. * @access public
  136. */
  137. function paintPass($message) {
  138. parent::paintPass($message);
  139. print $this->getIndent(1);
  140. print "<" . $this->namespace . "pass>";
  141. print $this->toParsedXml($message);
  142. print "</" . $this->namespace . "pass>\n";
  143. }
  144. /**
  145. * Paints failure as XML.
  146. * @param string $message Message to encode.
  147. * @access public
  148. */
  149. function paintFail($message) {
  150. parent::paintFail($message);
  151. print $this->getIndent(1);
  152. print "<" . $this->namespace . "fail>";
  153. print $this->toParsedXml($message);
  154. print "</" . $this->namespace . "fail>\n";
  155. }
  156. /**
  157. * Paints error as XML.
  158. * @param string $message Message to encode.
  159. * @access public
  160. */
  161. function paintError($message) {
  162. parent::paintError($message);
  163. print $this->getIndent(1);
  164. print "<" . $this->namespace . "exception>";
  165. print $this->toParsedXml($message);
  166. print "</" . $this->namespace . "exception>\n";
  167. }
  168. /**
  169. * Paints exception as XML.
  170. * @param Exception $exception Exception to encode.
  171. * @access public
  172. */
  173. function paintException($exception) {
  174. parent::paintException($exception);
  175. print $this->getIndent(1);
  176. print "<" . $this->namespace . "exception>";
  177. $message = 'Unexpected exception of type [' . get_class($exception) .
  178. '] with message ['. $exception->getMessage() .
  179. '] in ['. $exception->getFile() .
  180. ' line ' . $exception->getLine() . ']';
  181. print $this->toParsedXml($message);
  182. print "</" . $this->namespace . "exception>\n";
  183. }
  184. /**
  185. * Paints the skipping message and tag.
  186. * @param string $message Text to display in skip tag.
  187. * @access public
  188. */
  189. function paintSkip($message) {
  190. parent::paintSkip($message);
  191. print $this->getIndent(1);
  192. print "<" . $this->namespace . "skip>";
  193. print $this->toParsedXml($message);
  194. print "</" . $this->namespace . "skip>\n";
  195. }
  196. /**
  197. * Paints a simple supplementary message.
  198. * @param string $message Text to display.
  199. * @access public
  200. */
  201. function paintMessage($message) {
  202. parent::paintMessage($message);
  203. print $this->getIndent(1);
  204. print "<" . $this->namespace . "message>";
  205. print $this->toParsedXml($message);
  206. print "</" . $this->namespace . "message>\n";
  207. }
  208. /**
  209. * Paints a formatted ASCII message such as a
  210. * privateiable dump.
  211. * @param string $message Text to display.
  212. * @access public
  213. */
  214. function paintFormattedMessage($message) {
  215. parent::paintFormattedMessage($message);
  216. print $this->getIndent(1);
  217. print "<" . $this->namespace . "formatted>";
  218. print "<![CDATA[$message]]>";
  219. print "</" . $this->namespace . "formatted>\n";
  220. }
  221. /**
  222. * Serialises the event object.
  223. * @param string $type Event type as text.
  224. * @param mixed $payload Message or object.
  225. * @access public
  226. */
  227. function paintSignal($type, $payload) {
  228. parent::paintSignal($type, $payload);
  229. print $this->getIndent(1);
  230. print "<" . $this->namespace . "signal type=\"$type\">";
  231. print "<![CDATA[" . serialize($payload) . "]]>";
  232. print "</" . $this->namespace . "signal>\n";
  233. }
  234. /**
  235. * Paints the test document header.
  236. * @param string $test_name First test top level
  237. * to start.
  238. * @access public
  239. * @abstract
  240. */
  241. function paintHeader($test_name) {
  242. if (! SimpleReporter::inCli()) {
  243. header('Content-type: text/xml');
  244. }
  245. print "<?xml version=\"1.0\"";
  246. if ($this->namespace) {
  247. print " xmlns:" . $this->namespace .
  248. "=\"www.lastcraft.com/SimpleTest/Beta3/Report\"";
  249. }
  250. print "?>\n";
  251. print "<" . $this->namespace . "run>\n";
  252. }
  253. /**
  254. * Paints the test document footer.
  255. * @param string $test_name The top level test.
  256. * @access public
  257. * @abstract
  258. */
  259. function paintFooter($test_name) {
  260. print "</" . $this->namespace . "run>\n";
  261. }
  262. }
  263. /**
  264. * Accumulator for incoming tag. Holds the
  265. * incoming test structure information for
  266. * later dispatch to the reporter.
  267. * @package SimpleTest
  268. * @subpackage UnitTester
  269. */
  270. class NestingXmlTag {
  271. private $name;
  272. private $attributes;
  273. /**
  274. * Sets the basic test information except
  275. * the name.
  276. * @param hash $attributes Name value pairs.
  277. * @access public
  278. */
  279. function NestingXmlTag($attributes) {
  280. $this->name = false;
  281. $this->attributes = $attributes;
  282. }
  283. /**
  284. * Sets the test case/method name.
  285. * @param string $name Name of test.
  286. * @access public
  287. */
  288. function setName($name) {
  289. $this->name = $name;
  290. }
  291. /**
  292. * Accessor for name.
  293. * @return string Name of test.
  294. * @access public
  295. */
  296. function getName() {
  297. return $this->name;
  298. }
  299. /**
  300. * Accessor for attributes.
  301. * @return hash All attributes.
  302. * @access protected
  303. */
  304. protected function getAttributes() {
  305. return $this->attributes;
  306. }
  307. }
  308. /**
  309. * Accumulator for incoming method tag. Holds the
  310. * incoming test structure information for
  311. * later dispatch to the reporter.
  312. * @package SimpleTest
  313. * @subpackage UnitTester
  314. */
  315. class NestingMethodTag extends NestingXmlTag {
  316. /**
  317. * Sets the basic test information except
  318. * the name.
  319. * @param hash $attributes Name value pairs.
  320. * @access public
  321. */
  322. function NestingMethodTag($attributes) {
  323. $this->NestingXmlTag($attributes);
  324. }
  325. /**
  326. * Signals the appropriate start event on the
  327. * listener.
  328. * @param SimpleReporter $listener Target for events.
  329. * @access public
  330. */
  331. function paintStart(&$listener) {
  332. $listener->paintMethodStart($this->getName());
  333. }
  334. /**
  335. * Signals the appropriate end event on the
  336. * listener.
  337. * @param SimpleReporter $listener Target for events.
  338. * @access public
  339. */
  340. function paintEnd(&$listener) {
  341. $listener->paintMethodEnd($this->getName());
  342. }
  343. }
  344. /**
  345. * Accumulator for incoming case tag. Holds the
  346. * incoming test structure information for
  347. * later dispatch to the reporter.
  348. * @package SimpleTest
  349. * @subpackage UnitTester
  350. */
  351. class NestingCaseTag extends NestingXmlTag {
  352. /**
  353. * Sets the basic test information except
  354. * the name.
  355. * @param hash $attributes Name value pairs.
  356. * @access public
  357. */
  358. function NestingCaseTag($attributes) {
  359. $this->NestingXmlTag($attributes);
  360. }
  361. /**
  362. * Signals the appropriate start event on the
  363. * listener.
  364. * @param SimpleReporter $listener Target for events.
  365. * @access public
  366. */
  367. function paintStart(&$listener) {
  368. $listener->paintCaseStart($this->getName());
  369. }
  370. /**
  371. * Signals the appropriate end event on the
  372. * listener.
  373. * @param SimpleReporter $listener Target for events.
  374. * @access public
  375. */
  376. function paintEnd(&$listener) {
  377. $listener->paintCaseEnd($this->getName());
  378. }
  379. }
  380. /**
  381. * Accumulator for incoming group tag. Holds the
  382. * incoming test structure information for
  383. * later dispatch to the reporter.
  384. * @package SimpleTest
  385. * @subpackage UnitTester
  386. */
  387. class NestingGroupTag extends NestingXmlTag {
  388. /**
  389. * Sets the basic test information except
  390. * the name.
  391. * @param hash $attributes Name value pairs.
  392. * @access public
  393. */
  394. function NestingGroupTag($attributes) {
  395. $this->NestingXmlTag($attributes);
  396. }
  397. /**
  398. * Signals the appropriate start event on the
  399. * listener.
  400. * @param SimpleReporter $listener Target for events.
  401. * @access public
  402. */
  403. function paintStart(&$listener) {
  404. $listener->paintGroupStart($this->getName(), $this->getSize());
  405. }
  406. /**
  407. * Signals the appropriate end event on the
  408. * listener.
  409. * @param SimpleReporter $listener Target for events.
  410. * @access public
  411. */
  412. function paintEnd(&$listener) {
  413. $listener->paintGroupEnd($this->getName());
  414. }
  415. /**
  416. * The size in the attributes.
  417. * @return integer Value of size attribute or zero.
  418. * @access public
  419. */
  420. function getSize() {
  421. $attributes = $this->getAttributes();
  422. if (isset($attributes['SIZE'])) {
  423. return (integer)$attributes['SIZE'];
  424. }
  425. return 0;
  426. }
  427. }
  428. /**
  429. * Parser for importing the output of the XmlReporter.
  430. * Dispatches that output to another reporter.
  431. * @package SimpleTest
  432. * @subpackage UnitTester
  433. */
  434. class SimpleTestXmlParser {
  435. private $listener;
  436. private $expat;
  437. private $tag_stack;
  438. private $in_content_tag;
  439. private $content;
  440. private $attributes;
  441. /**
  442. * Loads a listener with the SimpleReporter
  443. * interface.
  444. * @param SimpleReporter $listener Listener of tag events.
  445. * @access public
  446. */
  447. function SimpleTestXmlParser(&$listener) {
  448. $this->listener = &$listener;
  449. $this->expat = &$this->createParser();
  450. $this->tag_stack = array();
  451. $this->in_content_tag = false;
  452. $this->content = '';
  453. $this->attributes = array();
  454. }
  455. /**
  456. * Parses a block of XML sending the results to
  457. * the listener.
  458. * @param string $chunk Block of text to read.
  459. * @return boolean True if valid XML.
  460. * @access public
  461. */
  462. function parse($chunk) {
  463. if (! xml_parse($this->expat, $chunk)) {
  464. trigger_error('XML parse error with ' .
  465. xml_error_string(xml_get_error_code($this->expat)));
  466. return false;
  467. }
  468. return true;
  469. }
  470. /**
  471. * Sets up expat as the XML parser.
  472. * @return resource Expat handle.
  473. * @access protected
  474. */
  475. protected function &createParser() {
  476. $expat = xml_parser_create();
  477. xml_set_object($expat, $this);
  478. xml_set_element_handler($expat, 'startElement', 'endElement');
  479. xml_set_character_data_handler($expat, 'addContent');
  480. xml_set_default_handler($expat, 'defaultContent');
  481. return $expat;
  482. }
  483. /**
  484. * Opens a new test nesting level.
  485. * @return NestedXmlTag The group, case or method tag
  486. * to start.
  487. * @access private
  488. */
  489. protected function pushNestingTag($nested) {
  490. array_unshift($this->tag_stack, $nested);
  491. }
  492. /**
  493. * Accessor for current test structure tag.
  494. * @return NestedXmlTag The group, case or method tag
  495. * being parsed.
  496. * @access private
  497. */
  498. protected function &getCurrentNestingTag() {
  499. return $this->tag_stack[0];
  500. }
  501. /**
  502. * Ends a nesting tag.
  503. * @return NestedXmlTag The group, case or method tag
  504. * just finished.
  505. * @access private
  506. */
  507. protected function popNestingTag() {
  508. return array_shift($this->tag_stack);
  509. }
  510. /**
  511. * Test if tag is a leaf node with only text content.
  512. * @param string $tag XML tag name.
  513. * @return @boolean True if leaf, false if nesting.
  514. * @private
  515. */
  516. protected function isLeaf($tag) {
  517. return in_array($tag, array(
  518. 'NAME', 'PASS', 'FAIL', 'EXCEPTION', 'SKIP', 'MESSAGE', 'FORMATTED', 'SIGNAL'));
  519. }
  520. /**
  521. * Handler for start of event element.
  522. * @param resource $expat Parser handle.
  523. * @param string $tag Element name.
  524. * @param hash $attributes Name value pairs.
  525. * Attributes without content
  526. * are marked as true.
  527. * @access protected
  528. */
  529. protected function startElement($expat, $tag, $attributes) {
  530. $this->attributes = $attributes;
  531. if ($tag == 'GROUP') {
  532. $this->pushNestingTag(new NestingGroupTag($attributes));
  533. } elseif ($tag == 'CASE') {
  534. $this->pushNestingTag(new NestingCaseTag($attributes));
  535. } elseif ($tag == 'TEST') {
  536. $this->pushNestingTag(new NestingMethodTag($attributes));
  537. } elseif ($this->isLeaf($tag)) {
  538. $this->in_content_tag = true;
  539. $this->content = '';
  540. }
  541. }
  542. /**
  543. * End of element event.
  544. * @param resource $expat Parser handle.
  545. * @param string $tag Element name.
  546. * @access protected
  547. */
  548. protected function endElement($expat, $tag) {
  549. $this->in_content_tag = false;
  550. if (in_array($tag, array('GROUP', 'CASE', 'TEST'))) {
  551. $nesting_tag = $this->popNestingTag();
  552. $nesting_tag->paintEnd($this->listener);
  553. } elseif ($tag == 'NAME') {
  554. $nesting_tag = &$this->getCurrentNestingTag();
  555. $nesting_tag->setName($this->content);
  556. $nesting_tag->paintStart($this->listener);
  557. } elseif ($tag == 'PASS') {
  558. $this->listener->paintPass($this->content);
  559. } elseif ($tag == 'FAIL') {
  560. $this->listener->paintFail($this->content);
  561. } elseif ($tag == 'EXCEPTION') {
  562. $this->listener->paintError($this->content);
  563. } elseif ($tag == 'SKIP') {
  564. $this->listener->paintSkip($this->content);
  565. } elseif ($tag == 'SIGNAL') {
  566. $this->listener->paintSignal(
  567. $this->attributes['TYPE'],
  568. unserialize($this->content));
  569. } elseif ($tag == 'MESSAGE') {
  570. $this->listener->paintMessage($this->content);
  571. } elseif ($tag == 'FORMATTED') {
  572. $this->listener->paintFormattedMessage($this->content);
  573. }
  574. }
  575. /**
  576. * Content between start and end elements.
  577. * @param resource $expat Parser handle.
  578. * @param string $text Usually output messages.
  579. * @access protected
  580. */
  581. protected function addContent($expat, $text) {
  582. if ($this->in_content_tag) {
  583. $this->content .= $text;
  584. }
  585. return true;
  586. }
  587. /**
  588. * XML and Doctype handler. Discards all such content.
  589. * @param resource $expat Parser handle.
  590. * @param string $default Text of default content.
  591. * @access protected
  592. */
  593. protected function defaultContent($expat, $default) {
  594. }
  595. }
  596. ?>