Track #76: XML and CSV report output now also works for structures without groups.
diff --git a/org.jacoco.doc/docroot/doc/changes.html b/org.jacoco.doc/docroot/doc/changes.html
index c035df3..50516c1 100644
--- a/org.jacoco.doc/docroot/doc/changes.html
+++ b/org.jacoco.doc/docroot/doc/changes.html
@@ -24,8 +24,14 @@
   <li>New HTML report option to directly create a zip file containing the report

     (Trac #12).</li>

   <li>Code coverage for static initializers in interfaces (Trac #21).</li>

-  <li>Better error handling for <code>report</code> Ant task. (Trac #71).</li>

-  <li>Classes without instructions are excluded from reports. (Trac #73).</li>

+  <li>Better error handling for <code>report</code> Ant task (Trac #71).</li>

+  <li>Classes without instructions are excluded from reports (Trac #73).</li>

+</ul>

+

+<h3>Fixed Bugs</h3>

+<ul>

+  <li>XML and CSV report output now also works for structures without groups

+  (Track #76).</li>

 </ul>

 

 <h3>API Changes</h3>

diff --git a/org.jacoco.report.test/src/org/jacoco/report/xml/XMLElementTest.java b/org.jacoco.report.test/src/org/jacoco/report/xml/XMLElementTest.java
index db1c648..fa4d050 100644
--- a/org.jacoco.report.test/src/org/jacoco/report/xml/XMLElementTest.java
+++ b/org.jacoco.report.test/src/org/jacoco/report/xml/XMLElementTest.java
@@ -105,13 +105,20 @@
 	}

 

 	@Test

-	public void testAttributes() throws IOException {

+	public void testStringAttributes() throws IOException {

 		root.attr("id", "12345").attr("quote", "<\">");

 		root.close();

 		assertEquals("<root id=\"12345\" quote=\"&lt;&quot;&gt;\"/>", buffer

 				.toString());

 	}

 

+	@Test

+	public void testIntAttributes() throws IOException {

+		root.attr("missed", 0).attr("total", 123);

+		root.close();

+		assertEquals("<root missed=\"0\" total=\"123\"/>", buffer.toString());

+	}

+

 	@Test(expected = IOException.class)

 	public void testInvalidAttributeOutput1() throws IOException {

 		root.text("text");

diff --git a/org.jacoco.report.test/src/org/jacoco/report/xml/XMLFormatterTest.java b/org.jacoco.report.test/src/org/jacoco/report/xml/XMLFormatterTest.java
index 5c2228b..8e0ca96 100644
--- a/org.jacoco.report.test/src/org/jacoco/report/xml/XMLFormatterTest.java
+++ b/org.jacoco.report.test/src/org/jacoco/report/xml/XMLFormatterTest.java
@@ -18,7 +18,6 @@
 import org.jacoco.report.ReportStructureTestDriver;
 import org.junit.After;
 import org.junit.Before;
-import org.junit.Ignore;
 import org.junit.Test;
 import org.w3c.dom.Document;
 
@@ -52,6 +51,7 @@
 	@Test
 	public void testStructureWithGroup() throws Exception {
 		driver.sendGroup(formatter);
+		assertPathMatches("group", "/report/@name");
 		assertPathMatches("bundle", "/report/group/@name");
 		assertPathMatches("org/jacoco/example", "/report/group/package/@name");
 		assertPathMatches("org/jacoco/example/FooClass",
@@ -60,12 +60,10 @@
 				"/report/group/package/class/method/@name");
 	}
 
-	// TODO: Trac #67
-	@Ignore
 	@Test
 	public void testStructureWithBundleOnly() throws Exception {
 		driver.sendBundle(formatter);
-		assertPathMatches("bundle", "/report/group/@name");
+		assertPathMatches("bundle", "/report/@name");
 		assertPathMatches("org/jacoco/example", "/report/package/@name");
 		assertPathMatches("org/jacoco/example/FooClass",
 				"/report/package/class/@name");
@@ -74,7 +72,7 @@
 
 	private void assertPathMatches(String expected, String path)
 			throws Exception {
-		XMLSupport support = new XMLSupport(XMLReportFile.class);
+		XMLSupport support = new XMLSupport(XMLFormatter.class);
 		Document document = support.parse(output.getFile());
 		assertEquals(expected, support.findStr(document, path));
 	}
diff --git a/org.jacoco.report.test/src/org/jacoco/report/xml/XMLReportFileTest.java b/org.jacoco.report.test/src/org/jacoco/report/xml/XMLReportFileTest.java
deleted file mode 100644
index f5ff259..0000000
--- a/org.jacoco.report.test/src/org/jacoco/report/xml/XMLReportFileTest.java
+++ /dev/null
@@ -1,113 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * All rights reserved. This program and the accompanying materials

- * are made available under the terms of the Eclipse Public License v1.0

- * which accompanies this distribution, and is available at

- * http://www.eclipse.org/legal/epl-v10.html

- *

- * Contributors:

- *    Brock Janiczak - initial API and implementation

- *    

- * $Id: $

- *******************************************************************************/

-package org.jacoco.report.xml;

-

-import static org.junit.Assert.assertEquals;

-

-import java.io.IOException;

-import java.io.Reader;

-import java.util.Arrays;

-import java.util.Collections;

-

-import org.jacoco.core.analysis.BundleCoverage;

-import org.jacoco.core.analysis.ClassCoverage;

-import org.jacoco.core.analysis.MethodCoverage;

-import org.jacoco.core.analysis.PackageCoverage;

-import org.jacoco.core.analysis.SourceFileCoverage;

-import org.jacoco.report.IReportVisitor;

-import org.jacoco.report.ISourceFileLocator;

-import org.jacoco.report.MemorySingleReportOutput;

-import org.junit.After;

-import org.junit.Before;

-import org.junit.Test;

-import org.w3c.dom.Document;

-

-/**

- * Unit tests for {@link XMLReportFile}

- * 

- * @author Brock Janiczak

- * @version $Revision: $

- * 

- */

-public class XMLReportFileTest {

-

-	private MemorySingleReportOutput output;

-

-	private XMLReportFile report;

-

-	private final ISourceFileLocator nullSourceLocator = new ISourceFileLocator() {

-

-		public Reader getSourceFile(String packageName, String fileName)

-				throws IOException {

-			return null;

-		}

-	};

-

-	@Before

-	public void setUp() throws Exception {

-		output = new MemorySingleReportOutput();

-		report = new XMLReportFile("UTF-8", output.createFile());

-	}

-

-	@After

-	public void teardown() {

-		output.assertClosed();

-	}

-

-	@Test

-	public void testEmptyReport() throws Exception {

-		IReportVisitor groupElement = report.visitChild(new BundleCoverage(

-				"test", Arrays.<PackageCoverage> asList()));

-		groupElement.visitEnd(nullSourceLocator);

-		report.visitEnd(nullSourceLocator);

-

-		assertPathMatches("test", "/report/group/@name");

-	}

-

-	@Test

-	public void testReportWithNoGroupsOrBundles() throws Exception {

-

-		MethodCoverage methodCoverage = new MethodCoverage("foo", "V", "V");

-		ClassCoverage classCoverage = new ClassCoverage("org/jacoco/test/Test",

-				null, "java/lang/Object", new String[0], "Test.java", Arrays

-						.asList(methodCoverage));

-		PackageCoverage packageCoverage = new PackageCoverage(

-				"org/jacoco/test", Arrays.asList(classCoverage), Collections

-						.<SourceFileCoverage> emptyList());

-		BundleCoverage bundleCoverage = new BundleCoverage("test", Arrays

-				.asList(packageCoverage));

-

-		IReportVisitor sessionElement = report.visitChild(bundleCoverage);

-		IReportVisitor packageElement = sessionElement

-				.visitChild(packageCoverage);

-		IReportVisitor classElement = packageElement.visitChild(classCoverage);

-		IReportVisitor methodElement = classElement.visitChild(methodCoverage);

-

-		methodElement.visitEnd(nullSourceLocator);

-		classElement.visitEnd(nullSourceLocator);

-		packageElement.visitEnd(nullSourceLocator);

-		sessionElement.visitEnd(nullSourceLocator);

-		report.visitEnd(nullSourceLocator);

-

-		assertPathMatches("BLOCK",

-				"/report/group/package/class/method[@name='foo']/counter/@type");

-	}

-

-	private void assertPathMatches(String expected, String path)

-			throws Exception {

-		XMLSupport support = new XMLSupport(XMLReportFile.class);

-		Document document = support.parse(output.getFile());

-		assertEquals(expected, support.findStr(document, path));

-

-	}

-}

diff --git a/org.jacoco.report/src/org/jacoco/report/html/HTMLElement.java b/org.jacoco.report/src/org/jacoco/report/html/HTMLElement.java
index bdc3b68..d0f247b 100644
--- a/org.jacoco.report/src/org/jacoco/report/html/HTMLElement.java
+++ b/org.jacoco.report/src/org/jacoco/report/html/HTMLElement.java
@@ -331,7 +331,7 @@
 			td.attr("class", classattr);

 		}

 		if (colspanattr > 1) {

-			td.attr("colspan", String.valueOf(colspanattr));

+			td.attr("colspan", colspanattr);

 		}

 		return td;

 	}

@@ -354,8 +354,8 @@
 			final int heightattr, final String titleattr) throws IOException {

 		final HTMLElement img = element("img");

 		img.attr("src", srcattr);

-		img.attr("width", String.valueOf(widthattr));

-		img.attr("height", String.valueOf(heightattr));

+		img.attr("width", widthattr);

+		img.attr("height", heightattr);

 		img.attr("title", titleattr);

 		img.attr("alt", titleattr);

 		img.close();

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/ClassNode.java b/org.jacoco.report/src/org/jacoco/report/xml/ClassNode.java
deleted file mode 100644
index 2d43e8c..0000000
--- a/org.jacoco.report/src/org/jacoco/report/xml/ClassNode.java
+++ /dev/null
@@ -1,79 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * All rights reserved. This program and the accompanying materials

- * are made available under the terms of the Eclipse Public License v1.0

- * which accompanies this distribution, and is available at

- * http://www.eclipse.org/legal/epl-v10.html

- *

- * Contributors:

- *    Brock Janiczak -initial API and implementation

- *    

- * $Id: $

- *******************************************************************************/

-package org.jacoco.report.xml;

-

-import java.io.IOException;

-

-import org.jacoco.core.analysis.ClassCoverage;

-import org.jacoco.core.analysis.ICoverageNode;

-import org.jacoco.core.analysis.MethodCoverage;

-import org.jacoco.core.analysis.ICoverageNode.CounterEntity;

-import org.jacoco.report.IReportVisitor;

-

-/**

- * Wrapper for an {@link XMLElement} that contains class coverage data

- * 

- * @author Brock Janiczak

- * @version $Revision: $

- */

-public class ClassNode extends NodeWithCoverage {

-	private static final CounterEntity[] CLASS_COUNTERS = {

-			CounterEntity.METHOD, CounterEntity.BLOCK, CounterEntity.LINE,

-			CounterEntity.INSTRUCTION, };

-

-	/**

-	 * Creates a new Class coverage element for the supplied package and class

-	 * coverage node

-	 * 

-	 * @param parent

-	 *            Parent element that will own this class element

-	 * @param classNode

-	 *            Class coverage node

-	 * @throws IOException

-	 *             IO Error creating the element

-	 */

-	public ClassNode(final PackageNode parent, final ClassCoverage classNode)

-			throws IOException {

-		super(parent, "class", classNode);

-		if (classNode.getSignature() != null) {

-			this.attr("signature", classNode.getSignature());

-		}

-		if (classNode.getSuperName() != null) {

-			this.attr("superclass", classNode.getSuperName());

-		}

-		if (classNode.getInterfaceNames() != null) {

-			boolean first = true;

-			final StringBuilder builder = new StringBuilder();

-			for (final String iface : classNode.getInterfaceNames()) {

-				if (first) {

-					first = false;

-				} else {

-					builder.append(' ');

-				}

-				builder.append(iface);

-			}

-			this.attr("interfaces", builder.toString());

-		}

-	}

-

-	public IReportVisitor visitChild(final ICoverageNode node)

-			throws IOException {

-		return new MethodNode(this, (MethodCoverage) node);

-	}

-

-	@Override

-	protected CounterEntity[] getCounterEntities() {

-		return CLASS_COUNTERS;

-	}

-

-}

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/GroupNode.java b/org.jacoco.report/src/org/jacoco/report/xml/GroupNode.java
deleted file mode 100644
index d33beed..0000000
--- a/org.jacoco.report/src/org/jacoco/report/xml/GroupNode.java
+++ /dev/null
@@ -1,78 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * All rights reserved. This program and the accompanying materials

- * are made available under the terms of the Eclipse Public License v1.0

- * which accompanies this distribution, and is available at

- * http://www.eclipse.org/legal/epl-v10.html

- *

- * Contributors:

- *    Brock Janiczak -initial API and implementation

- *    

- * $Id: $

- *******************************************************************************/

-package org.jacoco.report.xml;

-

-import java.io.IOException;

-

-import org.jacoco.core.analysis.ICoverageNode;

-import org.jacoco.core.analysis.ICoverageNode.ElementType;

-import org.jacoco.report.IReportVisitor;

-

-/**

- * Wrapper for an {@link XMLElement} that contains 'groups' of coverage data.

- * Group Nodes can represent either be Bundle or Group coverage data

- * 

- * @author Brock Janiczak

- * @version $Revision: $

- */

-public class GroupNode extends NodeWithCoverage {

-

-	/**

-	 * Creates a new top level Group coverage element for the supplied session

-	 * coverage node

-	 * 

-	 * @param file

-	 *            Root element to attach to

-	 * @param coverageNode

-	 *            Coverage node

-	 * @throws IOException

-	 *             IO Error creating the element

-	 */

-	public GroupNode(final XMLReportFile file, final ICoverageNode coverageNode)

-			throws IOException {

-		this((XMLElement) file, coverageNode);

-	}

-

-	/**

-	 * Creates a new Group coverage element under an existing group element for

-	 * the supplied coverage node

-	 * 

-	 * @param parent

-	 *            Element to attach to

-	 * @param node

-	 *            Coverage node

-	 * @throws IOException

-	 *             IO Error creating the element

-	 */

-	public GroupNode(final GroupNode parent, final ICoverageNode node)

-			throws IOException {

-		this((XMLElement) parent, node);

-	}

-

-	private GroupNode(final XMLElement parent, final ICoverageNode node)

-			throws IOException {

-		super(parent, "group", node);

-	}

-

-	public IReportVisitor visitChild(final ICoverageNode node)

-			throws IOException {

-

-		if (node.getElementType() == ElementType.GROUP

-				|| node.getElementType() == ElementType.BUNDLE) {

-			return new GroupNode(this, node);

-		}

-

-		return new PackageNode(this, node);

-	}

-

-}

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/MethodNode.java b/org.jacoco.report/src/org/jacoco/report/xml/MethodNode.java
deleted file mode 100644
index 05fe829..0000000
--- a/org.jacoco.report/src/org/jacoco/report/xml/MethodNode.java
+++ /dev/null
@@ -1,64 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * All rights reserved. This program and the accompanying materials

- * are made available under the terms of the Eclipse Public License v1.0

- * which accompanies this distribution, and is available at

- * http://www.eclipse.org/legal/epl-v10.html

- *

- * Contributors:

- *    Brock Janiczak -initial API and implementation

- *    

- * $Id: $

- *******************************************************************************/

-package org.jacoco.report.xml;

-

-import java.io.IOException;

-

-import org.jacoco.core.analysis.ICoverageNode;

-import org.jacoco.core.analysis.MethodCoverage;

-import org.jacoco.core.analysis.ICoverageNode.CounterEntity;

-import org.jacoco.report.IReportVisitor;

-

-/**

- * Wrapper for an {@link XMLElement} that contains method coverage data

- * 

- * @author Brock Janiczak

- * @version $Revision: $

- */

-public class MethodNode extends NodeWithCoverage {

-

-	private static final CounterEntity[] METHOD_COUNTERS = {

-			CounterEntity.BLOCK, CounterEntity.LINE, CounterEntity.INSTRUCTION };

-

-	/**

-	 * Creates a new Method coverage element for the supplied package and class

-	 * coverage node

-	 * 

-	 * @param parent

-	 *            Parent element that will own this class element

-	 * @param methodNode

-	 *            Method coverage node

-	 * @throws IOException

-	 *             IO Error creating the element

-	 */

-	public MethodNode(final ClassNode parent, final MethodCoverage methodNode)

-			throws IOException {

-		super(parent, "method", methodNode);

-		this.attr("desc", methodNode.getDesc());

-		final String signature = methodNode.getSignature();

-		if (signature != null) {

-			this.attr("signature", signature);

-		}

-	}

-

-	public IReportVisitor visitChild(final ICoverageNode node)

-			throws IOException {

-		throw new IllegalStateException("Methods must not have child nodes.");

-	}

-

-	@Override

-	protected CounterEntity[] getCounterEntities() {

-		return METHOD_COUNTERS;

-	}

-

-}

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/NodeWithCoverage.java b/org.jacoco.report/src/org/jacoco/report/xml/NodeWithCoverage.java
deleted file mode 100644
index 4ec8c5f..0000000
--- a/org.jacoco.report/src/org/jacoco/report/xml/NodeWithCoverage.java
+++ /dev/null
@@ -1,89 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * All rights reserved. This program and the accompanying materials

- * are made available under the terms of the Eclipse Public License v1.0

- * which accompanies this distribution, and is available at

- * http://www.eclipse.org/legal/epl-v10.html

- *

- * Contributors:

- *    Brock Janiczak -initial API and implementation

- *    

- * $Id: $

- *******************************************************************************/

-package org.jacoco.report.xml;

-

-import java.io.IOException;

-

-import org.jacoco.core.analysis.ICounter;

-import org.jacoco.core.analysis.ICoverageNode;

-import org.jacoco.core.analysis.ICoverageNode.CounterEntity;

-import org.jacoco.report.IReportVisitor;

-import org.jacoco.report.ISourceFileLocator;

-

-/**

- * Base class for implementing XML Elements that contain coverage elements

- * 

- * @author Brock Janiczak

- * @version $Revision: $

- */

-public abstract class NodeWithCoverage extends XMLElement implements

-		IReportVisitor {

-	private static final CounterEntity[] DEFAULT_COUNTERS = {

-			CounterEntity.CLASS, CounterEntity.METHOD, CounterEntity.BLOCK,

-			CounterEntity.LINE, CounterEntity.INSTRUCTION, };

-

-	private final ICoverageNode node;

-

-	/**

-	 * Creates a new Coverage node under the supplied parent

-	 * 

-	 * @param parent

-	 *            Parent element

-	 * @param elementName

-	 *            Name of this element

-	 * @param node

-	 *            Coverage node

-	 * @throws IOException

-	 *             IO Error creating this element

-	 */

-	public NodeWithCoverage(final XMLElement parent, final String elementName,

-			final ICoverageNode node) throws IOException {

-		super(parent.writer, elementName);

-		parent.addChildElement(this);

-		this.node = node;

-		this.attr("name", node.getName());

-	}

-

-	public final void visitEnd(final ISourceFileLocator sourceFileLocator)

-			throws IOException {

-

-		for (final CounterEntity counterEntity : getCounterEntities()) {

-			createCounterElement(counterEntity);

-		}

-

-		this.close();

-	}

-

-	/**

-	 * Retrieves the list of counters supported by this element

-	 * 

-	 * @return Counters supported by this element

-	 */

-	protected CounterEntity[] getCounterEntities() {

-		return DEFAULT_COUNTERS;

-	}

-

-	private void createCounterElement(final CounterEntity counterEntity)

-			throws IOException {

-		final ICounter counter = node.getCounter(counterEntity);

-

-		final XMLElement counterNode = this.element("counter");

-		counterNode.attr("type", counterEntity.name());

-		counterNode

-				.attr("covered", Integer.toString(counter.getCoveredCount()));

-		counterNode.attr("missed", Integer.toString(counter.getMissedCount()));

-

-		counterNode.close();

-	}

-

-}

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/PackageNode.java b/org.jacoco.report/src/org/jacoco/report/xml/PackageNode.java
deleted file mode 100644
index c6e5008..0000000
--- a/org.jacoco.report/src/org/jacoco/report/xml/PackageNode.java
+++ /dev/null
@@ -1,59 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * All rights reserved. This program and the accompanying materials

- * are made available under the terms of the Eclipse Public License v1.0

- * which accompanies this distribution, and is available at

- * http://www.eclipse.org/legal/epl-v10.html

- *

- * Contributors:

- *    Brock Janiczak -initial API and implementation

- *    

- * $Id: $

- *******************************************************************************/

-package org.jacoco.report.xml;

-

-import static java.lang.String.format;

-

-import java.io.IOException;

-

-import org.jacoco.core.analysis.ClassCoverage;

-import org.jacoco.core.analysis.ICoverageNode;

-import org.jacoco.core.analysis.ICoverageNode.ElementType;

-import org.jacoco.report.IReportVisitor;

-

-/**

- * Wrapper for an {@link XMLElement} that contains package coverage data

- * 

- * @author Brock Janiczak

- * @version $Revision: $

- */

-public class PackageNode extends NodeWithCoverage {

-

-	/**

-	 * Creates a new Package coverage element under the supplied group element

-	 * 

-	 * @param parent

-	 *            Parent element that will own this class element

-	 * @param packageNode

-	 *            Package coverage node

-	 * @throws IOException

-	 *             IO Error creating the element

-	 */

-	public PackageNode(final GroupNode parent, final ICoverageNode packageNode)

-			throws IOException {

-		super(parent, "package", packageNode);

-	}

-

-	public IReportVisitor visitChild(final ICoverageNode node)

-			throws IOException {

-		final ElementType type = node.getElementType();

-		switch (type) {

-		case CLASS:

-			return new ClassNode(this, (ClassCoverage) node);

-		case SOURCEFILE:

-			return IReportVisitor.NOP;

-		}

-		throw new IllegalStateException(format("Unexpected child node %s.",

-				type));

-	}

-}

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/XMLElement.java b/org.jacoco.report/src/org/jacoco/report/xml/XMLElement.java
index 1468441..5e55bc1 100644
--- a/org.jacoco.report/src/org/jacoco/report/xml/XMLElement.java
+++ b/org.jacoco.report/src/org/jacoco/report/xml/XMLElement.java
@@ -161,6 +161,25 @@
 	}

 

 	/**

+	 * Adds an attribute to this element. May only be called before an child

+	 * element is added or this element has been closed. The attribute value is

+	 * the decimal representation of the given int value.

+	 * 

+	 * @param name

+	 *            attribute name

+	 * @param value

+	 *            attribute value

+	 * 

+	 * @return this element

+	 * @throws IOException

+	 *             in case of problems with the writer

+	 */

+	public XMLElement attr(final String name, final int value)

+			throws IOException {

+		return attr(name, String.valueOf(value));

+	}

+

+	/**

 	 * Adds the given text as a child to this node. The text will be quoted.

 	 * 

 	 * @param text

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/XMLFormatter.java b/org.jacoco.report/src/org/jacoco/report/xml/XMLFormatter.java
index 10056f9..4fa71af 100644
--- a/org.jacoco.report/src/org/jacoco/report/xml/XMLFormatter.java
+++ b/org.jacoco.report/src/org/jacoco/report/xml/XMLFormatter.java
@@ -27,13 +27,19 @@
  */

 public class XMLFormatter implements IReportFormatter {

 

+	private static final String PUBID = "-//JACOCO//DTD Report 1.0//EN";

+

+	private static final String SYSTEM = "report.dtd";

+

 	private ISingleReportOutput output;

 

 	private String outputEncoding = "UTF-8";

 

 	public IReportVisitor createReportVisitor(final ICoverageNode session)

 			throws IOException {

-		return new XMLReportFile(outputEncoding, output.createFile());

+		final XMLElement root = new XMLDocument("report", PUBID, SYSTEM,

+				outputEncoding, true, output.createFile());

+		return new XMLReportNodeHandler(root, session);

 	}

 

 	/**

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/XMLReportFile.java b/org.jacoco.report/src/org/jacoco/report/xml/XMLReportFile.java
deleted file mode 100644
index 4ca31bf..0000000
--- a/org.jacoco.report/src/org/jacoco/report/xml/XMLReportFile.java
+++ /dev/null
@@ -1,61 +0,0 @@
-/*******************************************************************************

- * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

- * All rights reserved. This program and the accompanying materials

- * are made available under the terms of the Eclipse Public License v1.0

- * which accompanies this distribution, and is available at

- * http://www.eclipse.org/legal/epl-v10.html

- *

- * Contributors:

- *    Brock Janiczak -initial API and implementation

- *    

- * $Id: $

- *******************************************************************************/

-package org.jacoco.report.xml;

-

-import java.io.IOException;

-import java.io.OutputStream;

-

-import org.jacoco.core.analysis.ICoverageNode;

-import org.jacoco.report.IReportVisitor;

-import org.jacoco.report.ISourceFileLocator;

-

-/**

- * Report visitor that will generate an XML report of the coverage data

- * 

- * @author Brock Janiczak

- * @version $Revision: $

- */

-public class XMLReportFile extends XMLDocument implements IReportVisitor {

-

-	private static final String ROOT = "report";

-

-	private static final String PUBID = "-//JACOCO//DTD Report 1.0//EN";

-

-	private static final String SYSTEM = "report.dtd";

-

-	/**

-	 * Creates a new Report file

-	 * 

-	 * @param output

-	 *            Report output

-	 * @param encoding

-	 *            Encoding of the XML file

-	 * @throws IOException

-	 *             IO Error creating report file

-	 */

-	public XMLReportFile(final String encoding, final OutputStream output)

-			throws IOException {

-		super(ROOT, PUBID, SYSTEM, encoding, true, output);

-	}

-

-	public IReportVisitor visitChild(final ICoverageNode node)

-			throws IOException {

-		return new GroupNode(this, node);

-	}

-

-	public void visitEnd(final ISourceFileLocator sourceFileLocator)

-			throws IOException {

-		this.close();

-	}

-

-}

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/XMLReportNodeHandler.java b/org.jacoco.report/src/org/jacoco/report/xml/XMLReportNodeHandler.java
new file mode 100644
index 0000000..5dc7cd8
--- /dev/null
+++ b/org.jacoco.report/src/org/jacoco/report/xml/XMLReportNodeHandler.java
@@ -0,0 +1,130 @@
+/*******************************************************************************

+ * Copyright (c) 2009, 2010 Mountainminds GmbH & Co. KG and Contributors

+ * All rights reserved. This program and the accompanying materials

+ * are made available under the terms of the Eclipse Public License v1.0

+ * which accompanies this distribution, and is available at

+ * http://www.eclipse.org/legal/epl-v10.html

+ *

+ * Contributors:

+ *    Brock Janiczak - initial API and implementation

+ *    Marc R. Hoffmann - generalized structure 

+ *    

+ * $Id: $

+ *******************************************************************************/

+package org.jacoco.report.xml;

+

+import java.io.IOException;

+

+import org.jacoco.core.analysis.ClassCoverage;

+import org.jacoco.core.analysis.ICounter;

+import org.jacoco.core.analysis.ICoverageNode;

+import org.jacoco.core.analysis.MethodCoverage;

+import org.jacoco.core.analysis.ICoverageNode.CounterEntity;

+import org.jacoco.core.analysis.ICoverageNode.ElementType;

+import org.jacoco.report.IReportVisitor;

+import org.jacoco.report.ISourceFileLocator;

+

+/**

+ * Report visitor that transforms the report structure into XML elements.

+ * 

+ * @author Brock Janiczak

+ * @version $Revision: $

+ */

+class XMLReportNodeHandler implements IReportVisitor {

+

+	protected final XMLElement element;

+

+	protected final ICoverageNode node;

+

+	/**

+	 * New handler for the given coverage node.

+	 * 

+	 * @param element

+	 *            XML-Element representing this coverage node. The start tag

+	 *            must not be closed yet to allow adding additional attributes.

+	 * @param node

+	 *            corresponding coverage node

+	 * @throws IOException

+	 *             in case of problems with the underlying writer

+	 */

+	public XMLReportNodeHandler(final XMLElement element,

+			final ICoverageNode node) throws IOException {

+		this.element = element;

+		this.node = node;

+		element.attr("name", node.getName());

+	}

+

+	public IReportVisitor visitChild(final ICoverageNode node)

+			throws IOException {

+		final ElementType type = node.getElementType();

+		switch (type) {

+		case GROUP:

+		case BUNDLE:

+			return new XMLReportNodeHandler(element.element("group"), node);

+		case PACKAGE:

+			return new XMLReportNodeHandler(element.element("package"), node);

+		case CLASS:

+			final XMLElement classChild = element.element("class");

+			addClassAttributes(classChild, (ClassCoverage) node);

+			return new XMLReportNodeHandler(classChild, node);

+		case METHOD:

+			final XMLElement methodChild = element.element("method");

+			addMethodAttributes(methodChild, (MethodCoverage) node);

+			return new XMLReportNodeHandler(methodChild, node);

+		}

+		return IReportVisitor.NOP;

+	}

+

+	public void visitEnd(final ISourceFileLocator sourceFileLocator)

+			throws IOException {

+		for (final CounterEntity counterEntity : CounterEntity.values()) {

+			createCounterElement(counterEntity);

+		}

+		this.element.close();

+	}

+

+	private void createCounterElement(final CounterEntity counterEntity)

+			throws IOException {

+		final ICounter counter = node.getCounter(counterEntity);

+		if (counter.getTotalCount() > 0) {

+			final XMLElement counterNode = this.element.element("counter");

+			counterNode.attr("type", counterEntity.name());

+			counterNode.attr("covered", counter.getCoveredCount());

+			counterNode.attr("missed", counter.getMissedCount());

+			counterNode.close();

+		}

+	}

+

+	private static void addClassAttributes(final XMLElement element,

+			final ClassCoverage node) throws IOException {

+		if (node.getSignature() != null) {

+			element.attr("signature", node.getSignature());

+		}

+		if (node.getSuperName() != null) {

+			element.attr("superclass", node.getSuperName());

+		}

+		if (node.getInterfaceNames() != null) {

+			boolean first = true;

+			final StringBuilder builder = new StringBuilder();

+			for (final String iface : node.getInterfaceNames()) {

+				if (first) {

+					first = false;

+				} else {

+					builder.append(' ');

+				}

+				builder.append(iface);

+			}

+			element.attr("interfaces", builder.toString());

+		}

+	}

+

+	private static void addMethodAttributes(final XMLElement element,

+			final MethodCoverage node) throws IOException {

+		element.attr("desc", node.getDesc());

+		final String signature = node.getSignature();

+		if (signature != null) {

+			element.attr("signature", signature);

+		}

+	}

+

+}

diff --git a/org.jacoco.report/src/org/jacoco/report/xml/report.dtd b/org.jacoco.report/src/org/jacoco/report/xml/report.dtd
index 4d6b7d8..11164d9 100644
--- a/org.jacoco.report/src/org/jacoco/report/xml/report.dtd
+++ b/org.jacoco.report/src/org/jacoco/report/xml/report.dtd
@@ -7,28 +7,31 @@
   

    Contributors:

       Brock Janiczak - initial API and implementation

+      Marc R. Hoffmann - generalized report structure

       

    $Id: $

 -->

 

-<!ELEMENT report (group)>

+<!ELEMENT report ((group* , package*) , counter*)>

+<!ATTLIST report

+  name CDATA #REQUIRED>

 

-<!ELEMENT group ((group* , package*) , counter+)>

+<!ELEMENT group ((group* , package*) , counter*)>

 <!ATTLIST group

   name CDATA #REQUIRED>

 

-<!ELEMENT package ((class*) , counter+)>

+<!ELEMENT package ((class*) , counter*)>

 <!ATTLIST package

   name CDATA #REQUIRED>

 

-<!ELEMENT class ((method*), counter+)>

+<!ELEMENT class ((method*), counter*)>

 <!ATTLIST class

   name CDATA #REQUIRED

   signature CDATA #IMPLIED

   superclass CDATA #IMPLIED

   interfaces CDATA #IMPLIED>

 

-<!ELEMENT method (counter+)>

+<!ELEMENT method (counter*)>

 <!ATTLIST method

   name CDATA #REQUIRED

   desc CDATA #REQUIRED