Java+DOM: How do I set the base namespace of an (already created) Document?
Asked Answered
P

8

22

I am dealing with an already created Document object. I have to be able to set it's base namespace (attribute name "xmlns") to certain value. My input is DOM and is something like:

<root>...some content...</root>

What I need is DOM which is something like:

<root xmlns="myNamespace">...some content...</root>

That's it. Easy, isn't it? Wrong! Not with DOM!

I have tried the following:

1) Using doc.getDocumentElement().setAttribute("xmlns","myNamespace")

I get a document with empty xmlns (it works on any other attribute name!)

<root xmlns="">...</root>

2) Using renameNode(...)

First clone the document:

Document input = /*that external Document whose namespace I want to alter*/;

DocumentBuilderFactory BUILDER_FACTORY_NS = DocumentBuilderFactory.newInstance();
BUILDER_FACTORY_NS.setNamespaceAware(true);
Document output = BUILDER_NS.newDocument();
output.appendChild(output.importNode(input.getDocumentElement(), true));

I'm really missing document.clone(), but perhaps it's just me.

Now rename the root node:

output.renameNode(output.getDocumentElement(),"myNamespace",
    output.getDocumentElement().getTagName());

Now isn't that straightforward? ;)

What I get now is:

<root xmlns="myNamespace">
    <someElement xmlns=""/>
    <someOtherElement xmlns=""/>
</root>

So (as all of us have expected, right?), this renames the namespace only of the the root node.

Curse you, DOM!

Is there any way to do this recursively (without writing an own recursive method)?

Please help ;)

Please don't advice me to do some fancy workaround, such as transforming DOM to something else, alter the namespace there, and transform it back. I need DOM because it's the fastest standard way to manipulate XML.

Note: I'm using the latest JDK.

EDIT
Removed wrong assumptions from the question, which had to do with namespace prefix.

Portly answered 29/9, 2009 at 13:11 Comment(1)
Changing a namespace of a node is like writing an article, set a language property and expect it to be translated. You would have to translate the words. So, the "clean way" is to recreate the nodes in a second document using a recursive function.Enrique
B
11

I had the very same problem today. I ended up using parts of @ivan_ivanovich_ivanoff answer but removed the recursion and fixed some bugs.

Very important: if old namespace is null you must add two translations, one from null to your new namespaceURI and another from "" to your new namespaceURI. This happens because the first call to renameNode will change existing nodes that have a null namespaceURI to xmlns="".

Example of usage:

Document xmlDoc = ...;

new XmlNamespaceTranslator()
    .addTranslation(null, "new_ns")
    .addTranslation("", "new_ns")
    .translateNamespaces(xmlDoc);

// xmlDoc will have nodes with namespace null or "" changed to "new_ns"

Full source code follows:

public  class XmlNamespaceTranslator {

    private Map<Key<String>, Value<String>> translations = new HashMap<Key<String>, Value<String>>();

    public XmlNamespaceTranslator addTranslation(String fromNamespaceURI, String toNamespaceURI) {
        Key<String> key = new Key<String>(fromNamespaceURI);
        Value<String> value = new Value<String>(toNamespaceURI);

        this.translations.put(key, value);

        return this;
    }

    public void translateNamespaces(Document xmlDoc) {
        Stack<Node> nodes = new Stack<Node>();
        nodes.push(xmlDoc.getDocumentElement());

        while (!nodes.isEmpty()) {
            Node node = nodes.pop();
            switch (node.getNodeType()) {
            case Node.ATTRIBUTE_NODE:
            case Node.ELEMENT_NODE:
                Value<String> value = this.translations.get(new Key<String>(node.getNamespaceURI()));
                if (value != null) {
                    // the reassignment to node is very important. as per javadoc renameNode will
                    // try to modify node (first parameter) in place. If that is not possible it
                    // will replace that node for a new created one and return it to the caller.
                    // if we did not reassign node we will get no childs in the loop below.
                    node = xmlDoc.renameNode(node, value.getValue(), node.getNodeName());
                }
                break;
            }

            // for attributes of this node
            NamedNodeMap attributes = node.getAttributes();
            if (!(attributes == null || attributes.getLength() == 0)) {
                for (int i = 0, count = attributes.getLength(); i < count; ++i) {
                    Node attribute = attributes.item(i);
                    if (attribute != null) {
                        nodes.push(attribute);
                    }
                }
            }

            // for child nodes of this node
            NodeList childNodes = node.getChildNodes();
            if (!(childNodes == null || childNodes.getLength() == 0)) {
                for (int i = 0, count = childNodes.getLength(); i < count; ++i) {
                    Node childNode = childNodes.item(i);
                    if (childNode != null) {
                        nodes.push(childNode);
                    }
                }
            }
        }
    }

    // these will allow null values to be stored on a map so that we can distinguish
    // from values being on the map or not. map implementation returns null if the there
    // is no map element with a given key. If the value is null there is no way to
    // distinguish from value not being on the map or value being null. these classes
    // remove ambiguity.
    private static class Holder<T> {

        protected final T value;

        public Holder(T value) {
            this.value = value;
        }

        public T getValue() {
            return value;
        }

        @Override
        public int hashCode() {
            final int prime = 31;
            int result = 1;
            result = prime * result + ((value == null) ? 0 : value.hashCode());
            return result;
        }

        @Override
        public boolean equals(Object obj) {
            if (this == obj)
                return true;
            if (obj == null)
                return false;
            if (getClass() != obj.getClass())
                return false;
            Holder<?> other = (Holder<?>) obj;
            if (value == null) {
                if (other.value != null)
                    return false;
            } else if (!value.equals(other.value))
                return false;
            return true;
        }

    }

    private static class Key<T> extends Holder<T> {

        public Key(T value) {
            super(value);
        }

    }

    private static class Value<T> extends Holder<T> {

        public Value(T value) {
            super(value);
        }

    }
}
Badderlocks answered 1/10, 2010 at 14:29 Comment(1)
Instead of traversing the DOM tree it is easier to iterate the NodeList returned by document.getElementsByTagName("*").Bertabertasi
J
8

In addition to setting the prefix, you must also declare your namespace somewhere.

[EDIT] If you look into the package org.w3c.dom, you'll notice that there is no support for namespaces whatsoever except that you can create a Document node with a namespace URI:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setNamespaceAware(true);
DocumentBuilder builder = factory.newDocumentBuilder();
DOMImplementation DOMImplementation = builder.getDOMImplementation();
Document doc = DOMImplementation.createDocument(
    "http://www.somecompany.com/2005/xyz", // namespace
    "root",
    null /*DocumentType*/);

Element root = doc.getDocumentElement();
root.setPrefix("xyz");
root.setAttribute(
    "xmlns:xyz",
    "http://www.somecompany.com/2005/xyz");

With the standard W3C DOM API of Java 5 (and up), it's not possible to modify the namespace of a node.

But the W3C DOM API is just a couple of interfaces. So what you should try is to look at the implementation (i.e. the actual class of your document instance), cast it to the real type. This type should have additional methods and if you're lucky, you can use those to modify the namespace.

Johann answered 29/9, 2009 at 13:16 Comment(5)
Good, where? Document.declareNamespaceSomewhere(...) donesn't exist ;)Portly
Correct, this is not part of W3C's DOM API. See my edits for a way out.Johann
Thank you. The problem here is: I create a new document, the way you described, with a root element (without it doesn't work, thanks DOM). So now I would have to copy sub-nodes of the existing document to the new one, right? Would it be enought to ensure a full copy?Portly
To copy nodes, you must first remove them from the source document and then add them to the target document. That way, everything should be preserved. If you omit the remove, you'll get "node already has a parent" errors.Johann
And you're right: Namespaces are already hard enough and the W3C API sure doesn't help to make them any more simple.Johann
P
5

Well, here goes the recursive "solution":
(I still hope that someone might find a better way to do this)

public static void renameNamespaceRecursive(Document doc, Node node,
        String namespace) {

    if (node.getNodeType() == Node.ELEMENT_NODE) {
        System.out.println("renaming type: " + node.getClass()
            + ", name: " + node.getNodeName());
        doc.renameNode(node, namespace, node.getNodeName());
    }

    NodeList list = node.getChildNodes();
    for (int i = 0; i < list.getLength(); ++i) {
        renameNamespaceRecursive(doc, list.item(i), namespace);
    }
}

Seems to work, although I don't know if it's correct to rename only the node type ELEMENT_NODE, or if other node types must be renamed.

Portly answered 29/9, 2009 at 14:21 Comment(0)
R
1

we can change the xml namespace using sax parser, try this

import java.util.ListIterator;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Namespace;
import org.dom4j.QName;
import org.dom4j.Visitor;
import org.dom4j.VisitorSupport;
import org.dom4j.io.SAXReader;

public class VisitorExample {

  public static void main(String[] args) throws Exception {
    Document doc = new SAXReader().read("test.xml");
    Namespace oldNs = Namespace.get("oldNamespace");
    Namespace newNs = Namespace.get("newPrefix", "newNamespace");
    Visitor visitor = new NamespaceChangingVisitor(oldNs, newNs);
    doc.accept(visitor);
    System.out.println(doc.asXML());
  }
}

class NamespaceChangingVisitor extends VisitorSupport {
  private Namespace from;
  private Namespace to;

  public NamespaceChangingVisitor(Namespace from, Namespace to) {
    this.from = from;
    this.to = to;
  }

  public void visit(Element node) {
    Namespace ns = node.getNamespace();

    if (ns.getURI().equals(from.getURI())) {
      QName newQName = new QName(node.getName(), to);
      node.setQName(newQName);
    }

    ListIterator namespaces = node.additionalNamespaces().listIterator();
    while (namespaces.hasNext()) {
      Namespace additionalNamespace = (Namespace) namespaces.next();
      if (additionalNamespace.getURI().equals(from.getURI())) {
        namespaces.remove();
      }
    }
  }

}
Remainder answered 14/4, 2011 at 8:26 Comment(1)
This solution only works with dom4j Document, not w3c Document.Puerperal
T
1

A slight variation of Ivan's original post worked for me: setting the attribute on the document node.

xslRoot.setAttribute("xmlns:fo", "http://www.w3.org/1999/XSL/Format");

where

  • xslRoot is the document/root element/node,
  • fo is the namespace ID

Hope that helps someone!

Mike Watts

Tarter answered 6/11, 2012 at 11:0 Comment(1)
Setting the "xmlns" attribute doesn't actually change the namespace. Try calling the getNamespaceURI() methodFlight
I
0

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 the

DOMParse p = new MyDOMParser(...);
p.parse(new InputSource(inputStream));
Document doc = p.getDocument();
Intellectual answered 13/2, 2013 at 19:50 Comment(0)
B
0

Let's say you've got your Document instance..

import org.dom4j.*;

{

    static final String         YOUR_NAMESPACE_PREFIX =   "PREFIX"; 
    static final String         YOUR_NAMESPACE_URI    =   "URI"; 

    Document document = ...

    //now get the root element
    Element element = document.getRootElement();
    renameNamespaceRecursive(element);
    ...

    //End of this method
}

//the recursive method for the operation
void renameNamespaceRecursive(Element element) {
    element.setQName(new QName(element.getName(), DocumentHelper.createNamespace(YOUR_NAMESPACE_PREFIX, YOUR_NAMESPACE_URI)));
    for (Iterator i  = element.elementIterator(); i.hasNext();) {
        renameNamespaceRecursive((Element)i.next());
    }
}

That should do.

Bartholomew answered 22/4, 2013 at 16:38 Comment(0)
S
-1

I solved using org.jdom.Element:

Java:

import org.jdom.Element;
...
Element kml = new Element("kml", "http://www.opengis.net/kml/2.2");

XML:

<kml xmlns="http://www.opengis.net/kml/2.2">; 
...
</kml>
Shanell answered 11/7, 2013 at 8:27 Comment(1)
The question is about an already created DOM Document, so this approach cannot work. JDOM is not DOM.Flight

© 2022 - 2024 — McMap. All rights reserved.