Setting namespaces and prefixes in a Java DOM document

TrashCan picture TrashCan · May 14, 2012 · Viewed 60.9k times · Source

I'm trying to convert a ResultSet to an XML file. I've first used this example for the serialization.

import  org.w3c.dom.bootstrap.DOMImplementationRegistry;
import  org.w3c.dom.Document;
import  org.w3c.dom.ls.DOMImplementationLS;
import  org.w3c.dom.ls.LSSerializer;

...

DOMImplementationRegistry registry = DOMImplementationRegistry.newInstance();

DOMImplementationLS impl = 
    (DOMImplementationLS)registry.getDOMImplementation("LS");

...     

LSSerializer writer = impl.createLSSerializer();
String str = writer.writeToString(document);

After I made this work, I tried to validate my XML file, there were a couple of warnings. One about not having a doctype. So I tried another way to implement this. I came across the Transformer class. This class lets me set the encoding, doctype, etc.

The previous implementation supports automatic namespace fix-up. The following does not.

private static Document toDocument(ResultSet rs) throws Exception {   
    DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
    factory.setNamespaceAware(true);
    DocumentBuilder builder = factory.newDocumentBuilder();
    Document doc = builder.newDocument();

    URL namespaceURL = new URL("http://www.w3.org/2001/XMLSchema-instance");
    String namespace = "xmlns:xsi="+namespaceURL.toString();

    Element messages = doc.createElementNS(namespace, "messages");
    doc.appendChild(messages);

    ResultSetMetaData rsmd = rs.getMetaData();
    int colCount = rsmd.getColumnCount();

    String attributeValue = "true";
    String attribute = "xsi:nil";

    rs.beforeFirst();

    while(rs.next()) {
        amountOfRecords = 0;
        Element message = doc.createElement("message");
        messages.appendChild(message);

        for(int i = 1; i <= colCount; i++) {

            Object value = rs.getObject(i);
            String columnName = rsmd.getColumnName(i);

            Element messageNode = doc.createElement(columnName);

            if(value != null) {
                messageNode.appendChild(doc.createTextNode(value.toString()));
            } else {
                messageNode.setAttribute(attribute, attributeValue);
            }
            message.appendChild(messageNode);
        }
        amountOfRecords++;
    }
    logger.info("Amount of records archived: " + amountOfRecords);

    TransformerFactory tff = TransformerFactory.newInstance();
    Transformer tf = tff.newTransformer();
    tf.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    tf.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
    tf.setOutputProperty(OutputKeys.INDENT, "yes");

    BufferedWriter bf = createFile();
    StreamResult sr = new StreamResult(bf);
    DOMSource source = new DOMSource(doc);
    tf.transform(source, sr);

    return doc;
}

While I was testing the previous implementation I got an TransformationException: Namespace for prefix 'xsi' has not been declared. As you can see I've tried to add a namespace with the xsi prefix to the root element of my document. After testing this I still got the Exception. What is the correct way to set namespaces and their prefixes?

Edit: Another problem I have with the first implementation is that the last element in the XML document doesn't have the last three closing tags.

Answer

Hans picture Hans · Oct 24, 2012

The correct way to set a node on a namespaceAware document is by using:

rootNode.createElementNS("http://example/namespace", "PREFIX:aNodeName");

So you can replace "PREFIX" with your own custom prefix and replace "aNodeName" with the name of your node. To avoid having each node having its own namespace declaration you can define the namespaces as attributes on your root node like so:

rootNode.setAttribute("xmlns:PREFIX", "http://example/namespace");

Please be sure to set:

documentBuilderFactory.setNamespaceAware(true)

Otherwise you don't have namespaceAwareness.