How can I, given a w3c DOM (Java\'s default implementation, specifically) change the namespace of every element/attribute/node in that DOM? Efficiently, preferably. The DO
How can I, given a w3c DOM (Java's default implementation, specifically) change the namespace of every element/attribute/node in that DOM? Efficiently, preferably.
I don't think there is an efficient solution that is also robust. You can't just rename something on the root element. Consider these documents:
Doc1
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="urn:all" xmlns:f="urn:fleet" xmlns:m="urn:mission">
<f:starfleet>
<m:bold>
<f:ship name="Enterprise" />
</m:bold>
</f:starfleet>
</root>
Doc2
<?xml version="1.0" encoding="UTF-8"?>
<root xmlns="urn:all">
<starfleet xmlns="urn:fleet">
<bold xmlns="urn:mission">
<ship xmlns="urn:fleet" name="Enterprise" />
</bold>
</starfleet>
</root>
Doc3
<?xml version="1.0" encoding="UTF-8"?>
<r:root xmlns:r="urn:all">
<r:starfleet xmlns:r="urn:fleet">
<r:bold xmlns:r="urn:mission">
<r:ship xmlns:r="urn:fleet" name="Enterprise" />
</r:bold>
</r:starfleet>
</r:root>
These three documents are equivalent in a namespace-aware DOM. You could run the same namespaced XPath queries against any of them.
Since the DOM allows you to specify exactly how nodes should be namespaced, there is no catch-all, one-step call to change a namespace. You need to walk the DOM, taking into consideration not only prefix and URI values, but their scope at any given time.
This XSLT can be used with a Transformer to change elements namespaced as urn:fleet
to be namespaced as urn:new
:
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:f="urn:fleet" version="1.0">
<xsl:output method="xml" indent="yes" />
<xsl:template match="*">
<xsl:copy>
<xsl:copy-of select="@*" />
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="f:*">
<xsl:variable name="var.foo" select="local-name()" />
<xsl:element namespace="urn:new" name="{$var.foo}">
<xsl:copy-of select="@*" />
<xsl:apply-templates />
</xsl:element>
</xsl:template>
</xsl:stylesheet>
Caveats: further tweaking would be required to handle namespaced attributes; dangling urn:fleet
declarations can be left behind, which is messy, but largely inconsequential; probably other stuff I haven't thought of.
If you are ok with using the Xerces classes, you can create a DOMParser that replaces the URI of attributes and elements with your fixed up URIs:
import org.apache.xerces.parsers.DOMParser;
public static class MyDOMParser extends DOMParser {
private Map<String, String> fixupMap = ...;
@Override
protected Attr createAttrNode(QName attrQName)
{
if (fixupMap.containsKey(attrQName.uri))
attrQName.uri = fixupMap.get(attrQName.uri);
return super.createAttrNode(attrQName);
}
@Override
protected Element createElementNode(QName qName)
{
if (fixupMap.containsKey(qName.uri))
qName.uri = fixupMap.get(qName.uri);
return super.createElementNode(qName);
}
}
The elsewhere, you can parse into a DOM Document:
DOMParse p = new MyDOMParser(...);
p.parse(new InputSource(inputStream));
Document doc = p.getDocument();
You may copy your DOM tree to another tree and make some tweaks during process. For example, using org.apache.xml.utils.DOMBuilder as the implementation of ContentHandler, you may override methods in such way:
public void startElement(String ns, String localName, String name, Attributes atts) throws SAXException {
super.startElement("new_namespace", localName, name, atts);
}
DOMBuilder will handle all dirty work during copying leaving to you only namespace replacement logic.
This is not efficient on a namespace-aware DOM. You would have to use the DOM Level 3 Core method Document.renameNode (javadoc) on every descendant Element whose namespace you wanted to change. (You wouldn't normally need to change so many Attr nodes, because the namespace of an Attr node with no prefix is always null, rather than the Element's namespace.)
If all you want to do is substitute one namespace for another, it might be quicker to use a namespace-unaware DOM, and simply change the xmlns attribute in question. You should be able to get a namespace-unaware DOM by setting the DOMConfiguration ‘namespaces’ parameter to false, but I've not tried this in Java and it's the sort of obscure little thing DOM imps would get wrong.
The namespace is changed on every element without a defined namespace prefix by applying a targetnamespace attribute to your root element. Doing this will also require that you then alter each of your elements with a namespace prefix. You can make this prefix change manually or write some script logic to walk your DOM tree to applying it only where necessary.
Here is more reading about the targetnamespace attribute and the nonamespaceschema attribute:
http://www.xml.com/pub/a/2000/11/29/schemas/part1.html?page=8 http://www.computerpoweruser.com/editorial/article.asp?article=articles%2Farchive%2Fc0407%2F48c07%2F48c07.asp
This code given a DOM Document will return a new DOM Document in which a given set of namespace URI translations have been applied (uriMap). The keys must be the URIs in the source document, the values the replacement URIs in the destination document. Unknown namespace URIs pass through unchanged. It knows to change the value of xmlns:* attributes, but will not change other attributes that might happen to have namespace URIs as their values (e.g. XSD targetNamespace)
private static Node makeClone(Node kid, Node to, Map<String, String> uriMap) {
Document doc = to.getNodeType() == Node.DOCUMENT_NODE ?
(Document) to :
to.getOwnerDocument();
if (kid.getNodeType() == Node.ELEMENT_NODE) {
String newURI =
uriMap.containsKey(kid.getNamespaceURI()) ?
uriMap.get(kid.getNamespaceURI()) :
kid.getNamespaceURI();
Element clone = doc.createElementNS(newURI, kid.getNodeName());
to.appendChild(clone);
for (int i = 0; i < kid.getAttributes().getLength(); i++) {
Attr attr = (Attr) kid.getAttributes().item(i);
String newAttrURI =
uriMap.containsKey(attr.getNamespaceURI()) ?
uriMap.get(attr.getNamespaceURI()) :
attr.getNamespaceURI();
String newValue = attr.getValue();
if (attr.getNamespaceURI() != null &&
attr.getNamespaceURI().equals(
"http://www.w3.org/2000/xmlns/") &&
uriMap.containsKey(attr.getValue()))
newValue = uriMap.get(attr.getValue());
clone.setAttributeNS(newAttrURI, attr.getNodeName(), newValue);
}
return clone;
}
Node clone = kid.cloneNode(false);
doc.adoptNode(clone);
to.appendChild(clone);
return clone;
}
private static void copyKidsChangingNS(Node from, Node to,
Map<String, String> uriMap) {
NodeList kids = from.getChildNodes();
for (int i = 0; i < kids.getLength(); i++) {
Node kid = kids.item(i);
Node clone = makeClone(kid, to, uriMap);
copyKidsChangingNS(kid, clone, uriMap);
}
}
public static Document changeDocNS(Document doc, Map<String, String> uriMap)
throws Exception {
DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
dbf.setNamespaceAware(true);
DocumentBuilder db = dbf.newDocumentBuilder();
Document newDoc = db.newDocument();
copyKidsChangingNS(doc, newDoc, uriMap);
return newDoc;
}