Setting namespaces and prefixes in a Java DOM document
Asked Answered
B

3

14

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.

Bugeye answered 14/5, 2012 at 13:51 Comment(1)
T
9

You haven't added the namespace declaration in the root node; you just declared the root node in the namespace, two entirely different things. When building a DOM, you need to reference the namespace on every relevant Node. In other words, when you add your attribute, you need to define its namespace (e.g., setAttributeNS).

Side note: Although XML namespaces look like URLs, they really aren't. There's no need to use the URL class here.

Torytoryism answered 14/5, 2012 at 14:20 Comment(0)
C
36

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.

Clam answered 24/10, 2012 at 11:41 Comment(2)
+1, although I didn't need to call setNamespaceAware. The documentation for that function suggests it's related to parsing.Phaedrus
Note that there is a setPrefix(prefix); method which allows to dynamically set the prefix.Cleodal
W
11

Please note that setting an xmlns-prefix with setAttribute is wrong. If you ever want to eg sign your DOM, you have to use setAttributeNS: element.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:PREFIX", "http://example/namespace");

Worms answered 16/10, 2014 at 7:18 Comment(6)
Any specific example in which you noticed a difference? When I transform a DOM to a file, using setAttribute or setAttributeNS gives the same output. Did you notice some difference in the runtime DOM which the transform fixes?Brindisi
This may work but then you have a hard-coded namespace. Unless it's a reference to a standard schema I really like these things in a config file.Boozy
It doesn't matter, where the value comes from. It only matters that you use setAttributeNS, if you use namespaces.Worms
@Brindisi If it's still relevant (2015): We had problems with the XML signing using because of code using setAttribute instead of setAttributeNS. The signature could not be validated.Worms
Thanks for your reply. So setAttributesNS appears to correctly manipulate the in-memory DOM to prepare it for signing whereas setAttribute gives the correct structure only after serialization (maybe it's the serializer which fixes things). I could then see how small differences on the in-memory DOM can give different XML canonicalization results. The verifier obviously reads the serialized XML, so if the signature was generated from a DOM with an "invalid" state those differences in canonicalization can indeed cause an invalid signature to be created.Brindisi
That is exactly the case.Worms
T
9

You haven't added the namespace declaration in the root node; you just declared the root node in the namespace, two entirely different things. When building a DOM, you need to reference the namespace on every relevant Node. In other words, when you add your attribute, you need to define its namespace (e.g., setAttributeNS).

Side note: Although XML namespaces look like URLs, they really aren't. There's no need to use the URL class here.

Torytoryism answered 14/5, 2012 at 14:20 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.