diff --git a/PHP/CodeCoverage/Autoload.php b/PHP/CodeCoverage/Autoload.php
index dfc810774..6c3b8abc4 100644
--- a/PHP/CodeCoverage/Autoload.php
+++ b/PHP/CodeCoverage/Autoload.php
@@ -80,6 +80,17 @@ function ($class)
'php_codecoverage_report_node_iterator' => '/CodeCoverage/Report/Node/Iterator.php',
'php_codecoverage_report_php' => '/CodeCoverage/Report/PHP.php',
'php_codecoverage_report_text' => '/CodeCoverage/Report/Text.php',
+ 'php_codecoverage_report_xml' => '/CodeCoverage/Report/XML.php',
+ 'php_codecoverage_report_xml_directory' => '/CodeCoverage/Report/XML/Directory.php',
+ 'php_codecoverage_report_xml_file' => '/CodeCoverage/Report/XML/File.php',
+ 'php_codecoverage_report_xml_file_coverage' => '/CodeCoverage/Report/XML/File/Coverage.php',
+ 'php_codecoverage_report_xml_file_method' => '/CodeCoverage/Report/XML/File/Method.php',
+ 'php_codecoverage_report_xml_file_report' => '/CodeCoverage/Report/XML/File/Report.php',
+ 'php_codecoverage_report_xml_file_unit' => '/CodeCoverage/Report/XML/File/Unit.php',
+ 'php_codecoverage_report_xml_node' => '/CodeCoverage/Report/XML/Node.php',
+ 'php_codecoverage_report_xml_project' => '/CodeCoverage/Report/XML/Project.php',
+ 'php_codecoverage_report_xml_tests' => '/CodeCoverage/Report/XML/Tests.php',
+ 'php_codecoverage_report_xml_totals' => '/CodeCoverage/Report/XML/Totals.php',
'php_codecoverage_util' => '/CodeCoverage/Util.php',
'php_codecoverage_util_invalidargumenthelper' => '/CodeCoverage/Util/InvalidArgumentHelper.php'
);
diff --git a/PHP/CodeCoverage/Report/XML.php b/PHP/CodeCoverage/Report/XML.php
new file mode 100644
index 000000000..3174e7630
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML.php
@@ -0,0 +1,189 @@
+target = $target;
+ $this->initTargetDirectory($target);
+ $report = $coverage->getReport();
+
+ $this->project = new PHP_CodeCoverage_Report_XML_Project(
+ $coverage->getReport()->getName()
+ );
+ $this->processTests($coverage->getTests());
+
+ $this->processDirectory($report, $this->project);
+
+ $index = $this->project->asDom();
+ $index->formatOutput = TRUE;
+ $index->preserveWhiteSpace = FALSE;
+ $index->save($target . '/index.xml');
+ }
+
+ private function initTargetDirectory($dir)
+ {
+ if (file_exists($dir)) {
+
+ if (!is_dir($dir)) {
+ throw new PHP_CodeCoverage_Exception("'$dir' exists but is not a directory.");
+ }
+
+ if (!is_writable($dir)) {
+ throw new PHP_CodeCoverage_Exception("'$dir' exists but is not writable.");
+ }
+ }
+ else if (!@mkdir($dir, 0777, TRUE)) {
+ throw new PHP_CodeCoverage_Exception("'$dir' could not be created.");
+ }
+ }
+
+ private function processDirectory(PHP_CodeCoverage_Report_Node_Directory $directory, PHP_CodeCoverage_Report_XML_Node $context)
+ {
+ $dirObject = $context->addDirectory($directory->getName());
+ $this->setTotals($directory, $dirObject->getTotals());
+ foreach ($directory as $node) {
+ if ($node instanceof PHP_CodeCoverage_Report_Node_Directory) {
+ $this->processDirectory($node, $dirObject);
+ continue;
+ }
+ if ($node instanceof PHP_CodeCoverage_Report_Node_File) {
+ $this->processFile($node, $dirObject);
+ continue;
+ }
+ throw new PHP_CodeCoverage_Exception('Unknown node type for XML Report');
+ }
+ }
+
+ private function processFile(PHP_CodeCoverage_Report_Node_File $file, PHP_CodeCoverage_Report_XML_Directory $context)
+ {
+ $fileObject = $context->addFile($file->getName(), $file->getId() . '.xml');
+ $this->setTotals($file, $fileObject->getTotals());
+
+ $fileReport = new PHP_CodeCoverage_Report_XML_File_Report($file->getName());
+ $this->setTotals($file, $fileReport->getTotals());
+
+ foreach ($file->getClassesAndTraits() as $unit) {
+ $this->processUnit($unit, $fileReport);
+ }
+
+ foreach ($file->getFunctions() as $function) {
+ $this->processFunction($function, $fileReport);
+ }
+
+ foreach ($file->getCoverageData() as $line => $tests) {
+ if (!is_array($tests) || count($tests) == 0) {
+ continue;
+ }
+ $coverage = $fileReport->getLineCoverage($line);
+ foreach ($tests as $test) {
+ $coverage->addTest($test);
+ }
+ }
+
+ $this->initTargetDirectory($this->target . dirname($file->getId()) . '/');
+ $fileDom = $fileReport->asDom();
+ $fileDom->formatOutput = TRUE;
+ $fileDom->preserveWhiteSpace = FALSE;
+ $fileDom->save($this->target . $file->getId() . '.xml');
+ }
+
+ private function processUnit($unit, PHP_CodeCoverage_Report_XML_File_Report $report)
+ {
+ if (isset($unit['className'])) {
+ $unitObject = $report->getClassObject($unit['className']);
+ }
+ else {
+ $unitObject = $report->getTraitObject($unit['traitName']);
+ }
+
+ $unitObject->setLines(
+ $unit['startLine'],
+ $unit['executableLines'],
+ $unit['executedLines']
+ );
+ $unitObject->setCrap($unit['crap']);
+
+ $unitObject->setPackage(
+ $unit['package']['fullPackage'],
+ $unit['package']['package'],
+ $unit['package']['subpackage'],
+ $unit['package']['category']
+ );
+
+ $unitObject->setNamespace($unit['package']['namespace']);
+
+ foreach ($unit['methods'] as $method) {
+ $methodObject = $unitObject->addMethod($method['methodName']);
+ $methodObject->setSignature($method['signature']);
+ $methodObject->setLines($method['startLine'], $method['endLine']);
+ $methodObject->setCrap($method['crap']);
+ $methodObject->setTotals($method['executableLines'], $method['executedLines'], $method['coverage']);
+ }
+ }
+
+ private function processFunction($function, PHP_CodeCoverage_Report_XML_File_Report $report)
+ {
+ $functionObject = $report->getFunctionObject($function['functionName']);
+ $functionObject->setSignature($function['signature']);
+ $functionObject->setLines($function['startLine']);
+ $functionObject->setCrap($function['crap']);
+ $functionObject->setTotals($function['executableLines'], $function['executedLines'], $function['coverage']);
+ }
+
+ private function processTests(array $tests)
+ {
+ $testsObject = $this->project->getTests();
+ foreach ($tests as $test => $result) {
+ if ($test == 'UNCOVERED_FILES_FROM_WHITELIST') {
+ continue;
+ }
+ $testsObject->addTest($test, $result);
+ }
+ }
+
+ private function setTotals(PHP_CodeCoverage_Report_Node $node, PHP_CodeCoverage_Report_XML_Totals $totals)
+ {
+ $loc = $node->getLinesOfCode();
+ $totals->setNumLines(
+ $loc['loc'],
+ $loc['cloc'],
+ $loc['ncloc'],
+ $node->getNumExecutableLines(),
+ $node->getNumExecutedLines()
+ );
+
+ $totals->setNumClasses(
+ $node->getNumClasses(),
+ $node->getNumTestedClasses()
+ );
+
+ $totals->setNumTraits(
+ $node->getNumTraits(),
+ $node->getNumTestedTraits()
+ );
+
+ $totals->setNumMethods(
+ $node->getNumMethods(),
+ $node->getNumTestedMethods()
+ );
+
+ $totals->setNumFunctions(
+ $node->getNumFunctions(),
+ $node->getNumTestedFunctions()
+ );
+ }
+
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/Directory.php b/PHP/CodeCoverage/Report/XML/Directory.php
new file mode 100644
index 000000000..bad00ed51
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/Directory.php
@@ -0,0 +1,3 @@
+contextNode = $context;
+ $this->dom = $context->ownerDocument;
+ }
+
+ public function getTotals()
+ {
+ $totalsContainer = $this->contextNode->firstChild;
+ if (!$totalsContainer) {
+ $totalsContainer = $this->contextNode->appendChild(
+ $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'totals')
+ );
+ }
+ return new PHP_CodeCoverage_Report_XML_Totals($totalsContainer);
+ }
+
+ public function getLineCoverage($line)
+ {
+ $coverage = $this->contextNode->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'coverage')->item(0);
+ if (!$coverage) {
+ $coverage = $this->contextNode->appendChild(
+ $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'coverage')
+ );
+ }
+ $lineNode = $coverage->appendChild(
+ $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'line')
+ );
+ return new PHP_CodeCoverage_Report_XML_File_Coverage($lineNode, $line);
+ }
+
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/File/Coverage.php b/PHP/CodeCoverage/Report/XML/File/Coverage.php
new file mode 100644
index 000000000..3528c5a6d
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/File/Coverage.php
@@ -0,0 +1,25 @@
+contextNode = $context;
+ $this->setLine($line);
+ }
+
+ private function setLine($line)
+ {
+ $this->contextNode->setAttribute('nr', $line);
+ }
+
+ public function addTest($test)
+ {
+ $covered = $this->contextNode->appendChild(
+ $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'covered')
+ );
+ $covered->setAttribute('by', $test);
+ }
+
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/File/Method.php b/PHP/CodeCoverage/Report/XML/File/Method.php
new file mode 100644
index 000000000..c6dd897c0
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/File/Method.php
@@ -0,0 +1,42 @@
+contextNode = $context;
+ $this->setName($name);
+ }
+
+ private function setName($name)
+ {
+ $this->contextNode->setAttribute('name', $name);
+ }
+
+ public function setSignature($signature)
+ {
+ $this->contextNode->setAttribute('signature', $signature);
+ }
+
+ public function setLines($start, $end = NULL)
+ {
+ $this->contextNode->setAttribute('start', $start);
+ if ($end !== NULL) {
+ $this->contextNode->setAttribute('end', $end);
+ }
+ }
+
+ public function setTotals($executable, $executed, $coverage)
+ {
+ $this->contextNode->setAttribute('executable', $executable);
+ $this->contextNode->setAttribute('executed', $executed);
+ $this->contextNode->setAttribute('coverage', $coverage);
+ }
+
+ public function setCrap($crap)
+ {
+ $this->contextNode->setAttribute('crap', $crap);
+ }
+
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/File/Report.php b/PHP/CodeCoverage/Report/XML/File/Report.php
new file mode 100644
index 000000000..32a16cb6e
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/File/Report.php
@@ -0,0 +1,48 @@
+dom = new \DOMDocument();
+ $this->dom->loadXML('');
+ $this->contextNode = $this->dom->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'file')->item(0);
+ $this->setName($name);
+ }
+
+ private function setName($name)
+ {
+ $this->contextNode->setAttribute('name', $name);
+ }
+
+ public function asDom()
+ {
+ return $this->dom;
+ }
+
+ public function getFunctionObject($name)
+ {
+ $node = $this->contextNode->appendChild(
+ $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'function')
+ );
+ return new PHP_CodeCoverage_Report_XML_File_Method($node, $name);
+ }
+
+ public function getClassObject($name)
+ {
+ return $this->getUnitObject('class', $name);
+ }
+
+ public function getTraitObject($name)
+ {
+ return $this->getUnitObject('trait', $name);
+ }
+
+ private function getUnitObject($tagName, $name)
+ {
+ $node = $this->contextNode->appendChild(
+ $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', $tagName)
+ );
+ return new PHP_CodeCoverage_Report_XML_File_Unit($node, $name);
+ }
+
+}
diff --git a/PHP/CodeCoverage/Report/XML/File/Unit.php b/PHP/CodeCoverage/Report/XML/File/Unit.php
new file mode 100644
index 000000000..6ef09987e
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/File/Unit.php
@@ -0,0 +1,62 @@
+contextNode = $context;
+ $this->setName($name);
+ }
+
+ private function setName($name)
+ {
+ $this->contextNode->setAttribute('name', $name);
+ }
+
+ public function setLines($start, $executable, $executed)
+ {
+ $this->contextNode->setAttribute('start', $start);
+ $this->contextNode->setAttribute('executable', $executable);
+ $this->contextNode->setAttribute('executed', $executed);
+ }
+
+ public function setCrap($crap)
+ {
+ $this->contextNode->setAttribute('crap', $crap);
+ }
+
+ public function setPackage($full, $package, $sub, $category)
+ {
+ $node = $this->contextNode->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'package')->item(0);
+ if (!$node) {
+ $node = $this->contextNode->appendChild(
+ $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'package')
+ );
+ }
+ $node->setAttribute('full', $full);
+ $node->setAttribute('name', $package);
+ $node->setAttribute('sub', $sub);
+ $node->setAttribute('category', $category);
+ }
+
+ public function setNamespace($namespace)
+ {
+ $node = $this->contextNode->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'namespace')->item(0);
+ if (!$node) {
+ $node = $this->contextNode->appendChild(
+ $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'namespace')
+ );
+ }
+ $node->setAttribute('name', $namespace);
+ }
+
+ public function addMethod($name)
+ {
+ $node = $this->contextNode->appendChild(
+ $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'method')
+ );
+ return new PHP_CodeCoverage_Report_XML_File_Method($node, $name);
+ }
+
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/Node.php b/PHP/CodeCoverage/Report/XML/Node.php
new file mode 100644
index 000000000..a802d89c8
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/Node.php
@@ -0,0 +1,63 @@
+setContextNode($context);
+ }
+
+ protected function setContextNode(\DOMElement $context)
+ {
+ $this->contextNode = $context;
+ $this->dom = $context->ownerDocument;
+ }
+
+ public function getDom()
+ {
+ return $this->dom;
+ }
+
+ protected function getContextNode()
+ {
+ return $this->contextNode;
+ }
+
+ public function getTotals()
+ {
+ $totalsContainer = $this->getContextNode()->firstChild;
+ if (!$totalsContainer) {
+ $totalsContainer = $this->getContextNode()->appendChild(
+ $this->dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'totals')
+ );
+ }
+ return new PHP_CodeCoverage_Report_XML_Totals($totalsContainer);
+ }
+
+ public function addDirectory($name)
+ {
+ $dirNode = $this->getDom()->createElementNS('http://xml.phpunit.de/coverage/1.0', 'directory');
+ $dirNode->setAttribute('name', $name);
+ $this->getContextNode()->appendChild($dirNode);
+ return new PHP_CodeCoverage_Report_XML_Directory($dirNode);
+ }
+
+ public function addFile($name, $href)
+ {
+ $fileNode = $this->getDom()->createElementNS('http://xml.phpunit.de/coverage/1.0', 'file');
+ $fileNode->setAttribute('name', $name);
+ $fileNode->setAttribute('href', $href);
+ $this->getContextNode()->appendChild($fileNode);
+ return new PHP_CodeCoverage_Report_XML_File($fileNode);
+ }
+
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/Project.php b/PHP/CodeCoverage/Report/XML/Project.php
new file mode 100644
index 000000000..27581df72
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/Project.php
@@ -0,0 +1,37 @@
+init();
+ $this->setProjectName($name);
+ }
+
+ private function init()
+ {
+ $dom = new DOMDocument();
+ $dom->loadXML('');
+ $this->setContextNode($dom->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'project')->item(0));
+ }
+
+ private function setProjectName($name)
+ {
+ $this->getContextNode()->setAttribute('name', $name);
+ }
+
+ public function getTests()
+ {
+ $testsNode = $this->getContextNode()->getElementsByTagNameNS('http://xml.phpunit.de/coverage/1.0', 'tests')->item(0);
+ if (!$testsNode) {
+ $testsNode = $this->getContextNode()->appendChild(
+ $this->getDom()->createElementNS('http://xml.phpunit.de/coverage/1.0', 'tests')
+ );
+ }
+ return new PHP_CodeCoverage_Report_XML_Tests($testsNode);
+ }
+
+ public function asDom()
+ {
+ return $this->getDom();
+ }
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/Tests.php b/PHP/CodeCoverage/Report/XML/Tests.php
new file mode 100644
index 000000000..fe16d5b31
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/Tests.php
@@ -0,0 +1,20 @@
+contextNode = $context;
+ }
+
+ public function addTest($test, $result)
+ {
+ $node = $this->contextNode->appendChild(
+ $this->contextNode->ownerDocument->createElementNS('http://xml.phpunit.de/coverage/1.0', 'test')
+ );
+ $node->setAttribute('name', $test);
+ $node->setAttribute('result', $result);
+ }
+
+}
\ No newline at end of file
diff --git a/PHP/CodeCoverage/Report/XML/Totals.php b/PHP/CodeCoverage/Report/XML/Totals.php
new file mode 100644
index 000000000..466f64c64
--- /dev/null
+++ b/PHP/CodeCoverage/Report/XML/Totals.php
@@ -0,0 +1,94 @@
+container = $container;
+ $dom = $container->ownerDocument;
+ $this->linesNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'lines');
+ $this->methodsNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'methods');
+ $this->functionsNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'functions');
+ $this->classesNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'classes');
+ $this->traitsNode = $dom->createElementNS('http://xml.phpunit.de/coverage/1.0', 'traits');
+
+ $container->appendChild($this->linesNode);
+ $container->appendChild($this->methodsNode);
+ $container->appendChild($this->functionsNode);
+ $container->appendChild($this->classesNode);
+ $container->appendChild($this->traitsNode);
+ }
+
+ public function getContainer()
+ {
+ return $this->container;
+ }
+
+ public function setNumLines($loc, $cloc, $ncloc, $executable, $executed)
+ {
+ $this->linesNode->setAttribute('total', $loc);
+ $this->linesNode->setAttribute('comments', $cloc);
+ $this->linesNode->setAttribute('code', $ncloc);
+ $this->linesNode->setAttribute('executable', $executable);
+ $this->linesNode->setAttribute('executed', $executed);
+ $this->linesNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($executed,$executable, TRUE));
+ }
+
+ public function setNumClasses($count, $tested)
+ {
+ $this->classesNode->setAttribute('count', $count);
+ $this->classesNode->setAttribute('tested', $tested);
+ $this->classesNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE));
+ }
+
+ public function setNumTraits($count, $tested)
+ {
+ $this->traitsNode->setAttribute('count', $count);
+ $this->traitsNode->setAttribute('tested', $tested);
+ $this->traitsNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE));
+ }
+
+ public function setNumMethods($count, $tested)
+ {
+ $this->methodsNode->setAttribute('count', $count);
+ $this->methodsNode->setAttribute('tested', $tested);
+ $this->methodsNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE));
+ }
+
+ public function setNumFunctions($count, $tested)
+ {
+ $this->functionsNode->setAttribute('count', $count);
+ $this->functionsNode->setAttribute('tested', $tested);
+ $this->functionsNode->setAttribute('percent', PHP_CodeCoverage_Util::percent($tested,$count, TRUE));
+ }
+
+}