8

I am creating a W3C Document object using a String value. Once I created the Document object, I want to add a namespace to the root element of this document. Here's my current code:

Document document = builder.parse(new InputSource(new StringReader(xmlString)));
document.getDocumentElement().setAttributeNS("http://com", "xmlns:ns2", "Test");
document.setPrefix("ns2");
TransformerFactory tranFactory = TransformerFactory.newInstance();
Transformer aTransformer = tranFactory.newTransformer();
Source src = new DOMSource(document);
Result dest = new StreamResult(new File("c:\\xmlFileName.xml"));
aTransformer.transform(src, dest);

What I use as input:

<product>
    <arg0>DDDDDD</arg0>
    <arg1>DDDD</arg1>
</product>

What the output should look like:

<ns2:product xmlns:ns2="http://com">
    <arg0>DDDDDD</arg0>
    <arg1>DDDD</arg1>
</ns2:product>

I need to add the prefix value and namespace also to the input xml string. If I try the above code I am getting this exception:

NAMESPACE_ERR: An attempt is made to create or change an object in a way which is incorrect with regard to namespaces.

Appreciate your help!

Pieter VDE
  • 2,195
  • 4
  • 25
  • 44
Dilip
  • 929
  • 3
  • 13
  • 32

4 Answers4

30

Since there is not an easy way to rename the root element, we'll have to replace it with an element that has the correct namespace and attribute, and then copy all the original children into it. Forcing the namespace declaration is not needed because by giving the element the correct namespace (URI) and setting the prefix, the declaration will be automatic.

Replace the setAttribute and setPrefix with this (line 2,3)

String namespace = "http://com";
String prefix = "ns2";
// Upgrade the DOM level 1 to level 2 with the correct namespace
Element originalDocumentElement = document.getDocumentElement();
Element newDocumentElement = document.createElementNS(namespace, originalDocumentElement.getNodeName());
// Set the desired namespace and prefix
newDocumentElement.setPrefix(prefix);
// Copy all children
NodeList list = originalDocumentElement.getChildNodes();
while(list.getLength()!=0) {
    newDocumentElement.appendChild(list.item(0));
}
// Replace the original element
document.replaceChild(newDocumentElement, originalDocumentElement);

In the original code the author tried to declare an element namespace like this:

.setAttributeNS("http://com", "xmlns:ns2", "Test");

The first parameter is the namespace of the attribute, and since it's a namespace attribute it need to have the http://www.w3.org/2000/xmlns/ URI. The declared namespace should come into the 3rd parameter

.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:ns2", "http://com");
radicalbiscuit
  • 487
  • 1
  • 6
  • 18
alexvanboxel
  • 685
  • 6
  • 9
  • Thanks a lot! It really helped me, in my case the namespace has never been included in the digest calculation, because i added it as a regular attribute instead of adding it as a real namespace. +1 – Robert P Nov 27 '14 at 15:51
  • Looks this approach cannot add same namespace to child element, so I use this approach, then generate XML string from document, and parse generated XML string again. – wangf Jul 01 '15 at 06:26
  • 2
    Prefix is usually not important. I've had problems when using `replaceChild()` (probably something I did wrong), but it looks like it's easier to just rename a node: `doc.renameNode(doc.getDocumentElement(), nameSpaceNeeded, doc.getDocumentElement().getTagName());` – Pawel Veselov Nov 16 '18 at 01:53
1

Bellow approach also works for me, but probably should not use in performance critical case.

  1. Add name space to document root element as attribute.
  2. Transform the document to XML string. The purpose of this step is to make the child element in the XML string inherit parent element namespace.
  3. Now the xml string have name space.
  4. You can use the XML string to build a document again or used for JAXB unmarshal, etc.

private static String addNamespaceToXml(InputStream in)
        throws ParserConfigurationException, SAXException, IOException,
        TransformerException {
    DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
    /*
     * Must not namespace aware, otherwise the generated XML string will
     * have wrong namespace
     */
    // dbf.setNamespaceAware(true);
    DocumentBuilder db = dbf.newDocumentBuilder();
    Document document = db.parse(in);
    Element documentElement = document.getDocumentElement();
    // Add name space to root element as attribute
    documentElement.setAttribute("xmlns", "http://you_name_space");
    String xml = transformXmlNodeToXmlString(documentElement);
    return xml;
}

private static String transformXmlNodeToXmlString(Node node)
        throws TransformerException {
    TransformerFactory transFactory = TransformerFactory.newInstance();
    Transformer transformer = transFactory.newTransformer();
    StringWriter buffer = new StringWriter();
    transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "yes");
    transformer.transform(new DOMSource(node), new StreamResult(buffer));
    String xml = buffer.toString();
    return xml;
}
wangf
  • 895
  • 9
  • 12
1

Partially gleaned from here, and also from a comment above, I was able to get it to work (transforming an arbitrary DOM Node and adding a prefix to it and all its children) thus:

  private String addNamespacePrefix(Document doc, Node node) throws TransformerException {
    Element mainRootElement = doc.createElementNS(
            "http://abc.de/x/y/z", // namespace
            "my-prefix:fake-header-element" // prefix to "register" it with the DOM so we don't get exceptions later...
    );
    List<Element> descendants = nodeListToArrayRecurse(node.getChildNodes()); // for some reason we have to grab all these before doing the first "renameNode" ... no idea why ...

    mainRootElement.appendChild(node);
    doc.renameNode(node, "http://abc.de/x/y/z", "my-prefix:" + node.getNodeName());
    descendants.stream().forEach(c -> doc.renameNode(c, "http://abc.de/x/y/z", "my-prefix:" + c.getNodeName()));
  }

  private List<Element> nodeListToArrayRecurse(NodeList entryNodes) {
    List<Element> allEntries = new ArrayList<>();
    for (int i = 0; i < entryNodes.getLength(); i++) {
      Node child = entryNodes.item(i);
      if (child.getNodeType() == Node.ELEMENT_NODE) {
        allEntries.add((Element) child);
        allEntries.addAll(nodeListToArray(child.getChildNodes())); // recurse
      } // ignore other [i.e. text] nodes https://stackoverflow.com/questions/14566596/loop-through-all-elements-in-xml-using-nodelist
    }
    return allEntries;
  }

If it helps anybody. I then convert it to string, then manually remove the extra header and closing lines. What a pain, I must be doing something wrong...

rogerdpack
  • 62,887
  • 36
  • 269
  • 388
0

This seems to be working for me, and it's much simpler than those answers provided:

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();
document = builder.parse(new File(filename));
document.getDocumentElement().setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:yourNamespace", "http://whatever/else");
unhuman
  • 19
  • 2
  • 1
    Your answer could be improved with additional supporting information. Please [edit] to add further details, such as citations or documentation, so that others can confirm that your answer is correct. You can find more information on how to write good answers [in the help center](/help/how-to-answer). – Community Sep 04 '22 at 12:03