tags:

views:

1124

answers:

7

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 DOM doesn't seem to have a setNamespaceURI method on it, which is inconvenient.

I've tried XSL approaches, but they've failed to work in the JAXP transformers (although they work all right in Saxon9B, which I can't use for various other reasons).

Basically, I need a pure core java solution that will allow me to take one document and change its namespace.

+1  A: 

Based on my hugely biased opinion what you want will be a huge pain in the ass. I can see the typecasting hell and numerous recursive for loops needed to do this reliably already! Ahh, Java's default implementation, how I hate your NPE:s at internals, the reversed logic, the extra steps needed for simple operations!

So yes, my suggestion would be recursive for loops with typecasting for every single possible node type, based on my very personal experience the Java's default implementation sucks that badly.

Esko
I think that some of the other solutions here are more elegant, but this is what I ended up doing :)
Chris R
+2  A: 

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.

bobince
+1  A: 

If intent is to just change name space, then just use some stream editor to change NS mapping to URL.

A Namspace is more or less a binding between namespace prefix and a URI. In order to quickly change namespace, just change the mapping:

Before: xmlns:myNS="my-namespace-uri"

After: xmlns:myNS="my-new-namespace-uri"

Basically changing mapping is sufficient, if intent is simply to change the namespace. Moreover if XML Document has default namespace, then changing the default namespace URL value would change namespace for whole of the document.

Before: xmlns="my-namespace-uri"

After: xmlns="my-new-namespace-uri"

Suraj
+1 for pure Java implementation
Zach Bonham
Nice idea, but at this point that'd require re-serializing to a byte stream and re-parsing, which is NOT acceptable from a performance standpoint. I have a DOM already, which I have to use.
Chris R
A: 

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

+1  A: 

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.

McDowell
That was what I originally used, almost word-for-word, but it turns out that our client's use of XML is... primitive. Or stupid. You pick. Their tools break if the XML isn't _laid out_ properly, for christ's sake.
Chris R
A: 

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.

Shay