diff --git a/src/main/org/apache/tools/ant/util/DOMElementWriter.java b/src/main/org/apache/tools/ant/util/DOMElementWriter.java index 69b5798b5..786ac6dd4 100644 --- a/src/main/org/apache/tools/ant/util/DOMElementWriter.java +++ b/src/main/org/apache/tools/ant/util/DOMElementWriter.java @@ -21,6 +21,9 @@ import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.io.Writer; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import org.w3c.dom.Attr; import org.w3c.dom.Element; import org.w3c.dom.NamedNodeMap; @@ -30,7 +33,6 @@ import org.w3c.dom.Text; /** * Writes a DOM tree to a given Writer. - * warning: this utility currently does not declare XML Namespaces. *

Utility class used by {@link org.apache.tools.ant.XmlLogger * XmlLogger} and * org.apache.tools.ant.taskdefs.optional.junit.XMLJUnitResultFormatter @@ -39,24 +41,99 @@ import org.w3c.dom.Text; */ public class DOMElementWriter { + /** prefix for genefrated prefixes */ + private static final String NS = "ns"; + /** xml declaration is on by default */ private boolean xmlDeclaration=true; + /** + * XML Namespaces are ignored by default. + */ + private XmlNamespacePolicy namespacePolicy = XmlNamespacePolicy.IGNORE; + + /** + * Map (URI to prefix) of known namespaces. + */ + private HashMap nsPrefixMap = new HashMap(); + + /** + * Number of generated prefix to use next. + */ + private int nextPrefix = 0; + + /** + * Map (Element to URI) of namespaces defined on a given element. + */ + private HashMap nsURIByElement = new HashMap(); + + /** + * Whether namespaces should be ignored for elements and attributes. + * + * @since Ant 1.7 + */ + public static class XmlNamespacePolicy { + private boolean qualifyElements; + private boolean qualifyAttributes; + + /** + * Ignores namespaces for elements and attributes, the default. + */ + public static final XmlNamespacePolicy IGNORE = + new XmlNamespacePolicy(false, false); + + /** + * Ignores namespaces for attributes. + */ + public static final XmlNamespacePolicy ONLY_QUALIFY_ELEMENTS = + new XmlNamespacePolicy(true, false); + + /** + * Qualifies namespaces for elements and attributes. + */ + public static final XmlNamespacePolicy QUALIFY_ALL = + new XmlNamespacePolicy(true, true); + + /** + * @param qualifyElements whether to qualify elements + * @param qualifyAttributes whether to qualify elements + */ + public XmlNamespacePolicy(boolean qualifyElements, + boolean qualifyAttributes) { + this.qualifyElements = qualifyElements; + this.qualifyAttributes = qualifyAttributes; + } + } + /** * Create an element writer. - * The ?xml? declaration will be included. + * The ?xml? declaration will be included, namespaces ignored. */ public DOMElementWriter() { } /** * Create an element writer + * XML namespaces will be ignored. * @param xmlDeclaration flag to indicate whether the ?xml? declaration * should be included. * @since Ant1.7 */ public DOMElementWriter(boolean xmlDeclaration) { + this(xmlDeclaration, XmlNamespacePolicy.IGNORE); + } + + /** + * Create an element writer + * XML namespaces will be ignored. + * @param xmlDeclaration flag to indicate whether the ?xml? declaration + * should be included. + * @since Ant1.7 + */ + public DOMElementWriter(boolean xmlDeclaration, + XmlNamespacePolicy namespacePolicy) { this.xmlDeclaration = xmlDeclaration; + this.namespacePolicy = namespacePolicy; } private static String lSep = System.getProperty("line.separator"); @@ -200,6 +277,23 @@ public class DOMElementWriter { // Write element out.write("<"); + if (namespacePolicy.qualifyElements) { + String prefix = (String) nsPrefixMap.get(element.getNamespaceURI()); + if (prefix == null) { + if (nsPrefixMap.isEmpty()) { + // steal default namespace + prefix = ""; + } else { + prefix = NS + (nextPrefix++); + } + nsPrefixMap.put(element.getNamespaceURI(), prefix); + addNSDefinition(element, element.getNamespaceURI()); + } + if (!"".equals(prefix)) { + out.write(prefix); + out.write(":"); + } + } out.write(element.getTagName()); // Write attributes @@ -207,14 +301,45 @@ public class DOMElementWriter { for (int i = 0; i < attrs.getLength(); i++) { Attr attr = (Attr) attrs.item(i); out.write(" "); + if (namespacePolicy.qualifyAttributes) { + String prefix = + (String) nsPrefixMap.get(attr.getNamespaceURI()); + if (prefix == null) { + prefix = NS + (nextPrefix++); + nsPrefixMap.put(attr.getNamespaceURI(), prefix); + addNSDefinition(element, attr.getNamespaceURI()); + } + out.write(prefix); + out.write(":"); + } out.write(attr.getName()); out.write("=\""); out.write(encode(attr.getValue())); out.write("\""); } + + // write namespace declarations + ArrayList al = (ArrayList) nsURIByElement.get(element); + if (al != null) { + Iterator iter = al.iterator(); + while (iter.hasNext()) { + String uri = (String) iter.next(); + String prefix = (String) nsPrefixMap.get(uri); + out.write(" xmlns"); + if (!"".equals(prefix)) { + out.write(":"); + out.write(prefix); + } + out.write("=\""); + out.write(uri); + out.write("\""); + } + } + if (hasChildren) { out.write(">"); } else { + removeNSDefinitions(element); out.write(" />"); out.write(lSep); out.flush(); @@ -245,6 +370,16 @@ public class DOMElementWriter { // Write element close out.write(""); out.write(lSep); @@ -393,4 +528,24 @@ public class DOMElementWriter { } return false; } + + private void removeNSDefinitions(Element element) { + ArrayList al = (ArrayList) nsURIByElement.get(element); + if (al != null) { + Iterator iter = al.iterator(); + while (iter.hasNext()) { + nsPrefixMap.remove(iter.next()); + } + nsURIByElement.remove(element); + } + } + + private void addNSDefinition(Element element, String uri) { + ArrayList al = (ArrayList) nsURIByElement.get(element); + if (al == null) { + al = new ArrayList(); + nsURIByElement.put(element, al); + } + al.add(uri); + } } diff --git a/src/testcases/org/apache/tools/ant/util/DOMElementWriterTest.java b/src/testcases/org/apache/tools/ant/util/DOMElementWriterTest.java index d5241bd25..c4cbf031a 100644 --- a/src/testcases/org/apache/tools/ant/util/DOMElementWriterTest.java +++ b/src/testcases/org/apache/tools/ant/util/DOMElementWriterTest.java @@ -143,4 +143,114 @@ public class DOMElementWriterTest extends TestCase { sw.toString()); } + public void testNoNSPrefixByDefault() throws IOException { + Document d = DOMUtils.newDocument(); + Element root = d.createElementNS("urn:foo", "root"); + root.setAttributeNS("urn:foo2", "bar", "baz"); + + StringWriter sw = new StringWriter(); + DOMElementWriter w = new DOMElementWriter(); + w.write(root, sw, 0, " "); + assertEquals("" + + StringUtils.LINE_SEP, sw.toString()); + } + + public void testNSOnElement() throws IOException { + Document d = DOMUtils.newDocument(); + Element root = d.createElementNS("urn:foo", "root"); + root.setAttributeNS("urn:foo2", "bar", "baz"); + + StringWriter sw = new StringWriter(); + DOMElementWriter w = + new DOMElementWriter(false, + DOMElementWriter.XmlNamespacePolicy + .ONLY_QUALIFY_ELEMENTS); + w.write(root, sw, 0, " "); + assertEquals("" + + StringUtils.LINE_SEP, sw.toString()); + } + + public void testNSPrefixOnAttribute() throws IOException { + Document d = DOMUtils.newDocument(); + Element root = d.createElementNS("urn:foo", "root"); + root.setAttributeNS("urn:foo2", "bar", "baz"); + + StringWriter sw = new StringWriter(); + DOMElementWriter w = + new DOMElementWriter(false, + DOMElementWriter.XmlNamespacePolicy + .QUALIFY_ALL); + w.write(root, sw, 0, " "); + assertEquals("" + + StringUtils.LINE_SEP, sw.toString()); + } + + public void testNSPrefixOnAttributeEvenWithoutElement() throws IOException { + Document d = DOMUtils.newDocument(); + Element root = d.createElementNS("urn:foo", "root"); + root.setAttributeNS("urn:foo2", "bar", "baz"); + + StringWriter sw = new StringWriter(); + DOMElementWriter w = + new DOMElementWriter(false, + new DOMElementWriter.XmlNamespacePolicy(false, + true) + ); + w.write(root, sw, 0, " "); + assertEquals("" + + StringUtils.LINE_SEP, sw.toString()); + } + + public void testNSGetsReused() throws IOException { + Document d = DOMUtils.newDocument(); + Element root = d.createElementNS("urn:foo", "root"); + Element child = d.createElementNS("urn:foo", "child"); + root.appendChild(child); + StringWriter sw = new StringWriter(); + DOMElementWriter w = + new DOMElementWriter(false, + DOMElementWriter.XmlNamespacePolicy + .ONLY_QUALIFY_ELEMENTS); + w.write(root, sw, 0, " "); + assertEquals("" + + StringUtils.LINE_SEP + + " " + + StringUtils.LINE_SEP + + "" + + StringUtils.LINE_SEP, sw.toString()); + } + + public void testNSGoesOutOfScope() throws IOException { + Document d = DOMUtils.newDocument(); + Element root = d.createElementNS("urn:foo", "root"); + Element child = d.createElementNS("urn:foo2", "child"); + root.appendChild(child); + Element child2 = d.createElementNS("urn:foo2", "child"); + root.appendChild(child2); + Element grandChild = d.createElementNS("urn:foo2", "grandchild"); + child2.appendChild(grandChild); + Element child3 = d.createElementNS("urn:foo2", "child"); + root.appendChild(child3); + StringWriter sw = new StringWriter(); + DOMElementWriter w = + new DOMElementWriter(false, + DOMElementWriter.XmlNamespacePolicy + .ONLY_QUALIFY_ELEMENTS); + w.write(root, sw, 0, " "); + assertEquals("" + + StringUtils.LINE_SEP + + " " + + StringUtils.LINE_SEP + + " " + + StringUtils.LINE_SEP + + " " + + StringUtils.LINE_SEP + + " " + + StringUtils.LINE_SEP + + " " + + StringUtils.LINE_SEP + + "" + + StringUtils.LINE_SEP, sw.toString()); + } }