1

I tried to update from JDOM 1.0 to JDOM2. In JDOM 1.0 this code:

DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
org.w3c.dom.Document doc = dbFactory.newDocumentBuilder().newDocument();
doc.setXmlVersion("1.0");

Element root = doc.createElement("Document");

root.setAttribute("xmlns", "urn:iso:foo");
root.setAttribute("xsi:schemaLocation", "urn:iso:foo bar.xsd");
root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
doc.appendChild(root);

Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("testxml.xml"), "UTF8"));
DOMBuilder builder = new DOMBuilder();
Document jdoc = builder.build(doc);
XMLOutputter fmt = new XMLOutputter();
fmt.setFormat(Format.getPrettyFormat());
fmt.output(jdoc, out);

produces this XML file:

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:foo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:foo bar.xsd" />

When I use JDOM2, the attribute xsi:schemaLocation is changed to schemaLocation (and the XML looks like that):

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:foo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" schemaLocation="urn:iso:foo bar.xsd" />

Is there a way to keep the xsi: part in JDOM2? Without it, the system which processes the generated XML cannot read it (not under my control). Not sure whether this is the same question.

Community
  • 1
  • 1
Roland
  • 1,220
  • 1
  • 14
  • 29
  • 1
    You have an uncomfortable mix of DOM and JDOM content here.... Note that `root` is a DOM, not a JDOM element, and JDOM will not allow you to set attributes with those illegal names. Why are you doing things this way, and not just building the Document as a JDOM document directly? – rolfl Nov 04 '14 at 11:44
  • No particular reason I think. Just a bunch of legacy code. According to some comments in the code it looks like JDOM is only used to format the XML in a somewhat pretty way. With JDOM 1.0 it even worked well. Thanks for the hint! – Roland Nov 04 '14 at 12:05
  • I am looking in to your use case, you may have found a bug in the DOMBuilder not picking up the attribute namespace. It will likely have to wait for me to finish work today before I check that further. The usecase you have is somewhat unusual. Just saying. – rolfl Nov 04 '14 at 12:15

2 Answers2

2

JDOM requires using a namespace-aware DOM implementation to build the JDOM document.

I have put together the following code to illustrate this point:

import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.StringReader;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.jdom2.Document;
import org.jdom2.input.DOMBuilder;
import org.jdom2.output.Format;
import org.jdom2.output.XMLOutputter;
import org.w3c.dom.Element;
import org.xml.sax.InputSource;


public class DOMvsJDOM {

    private static org.w3c.dom.Document buildDOM(String xml) throws Exception {
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
        dbf.setNamespaceAware(true);
        dbf.setValidating(false);
        dbf.setExpandEntityReferences(false);
        DocumentBuilder db = dbf.newDocumentBuilder();
        StringReader sr = new StringReader(xml);
        InputSource is = new InputSource(sr);
        return db.parse(is);
    }

    public static void printDocument(org.w3c.dom.Document doc, OutputStream out) throws Exception {
        TransformerFactory tf = TransformerFactory.newInstance();
        Transformer transformer = tf.newTransformer();
        transformer.setOutputProperty(OutputKeys.OMIT_XML_DECLARATION, "no");
        transformer.setOutputProperty(OutputKeys.METHOD, "xml");
        transformer.setOutputProperty(OutputKeys.INDENT, "yes");
        transformer.setOutputProperty(OutputKeys.ENCODING, "UTF-8");
        transformer.setOutputProperty("{http://xml.apache.org/xslt}indent-amount", "4");

        transformer.transform(new DOMSource(doc), 
             new StreamResult(new OutputStreamWriter(out, "UTF-8")));
    }

    private static void parseUsingJDOM(org.w3c.dom.Document doc) throws Exception {
//      Writer out = new BufferedWriter(new OutputStreamWriter(new FileOutputStream("testxml.xml"), "UTF8"));
      DOMBuilder builder = new DOMBuilder();
      Document jdoc = builder.build(doc);
      XMLOutputter fmt = new XMLOutputter();
      fmt.setFormat(Format.getPrettyFormat());
      fmt.output(jdoc, System.out);
    }

    public static void main(String[] args) throws Exception {
        DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
        org.w3c.dom.Document doc = dbFactory.newDocumentBuilder().newDocument();
        doc.setXmlVersion("1.0");

        Element root = doc.createElement("Document");

        root.setAttribute("xmlns", "urn:iso:foo");
        root.setAttribute("xsi:schemaLocation", "urn:iso:foo bar.xsd");
        root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
        doc.appendChild(root);

        printDocument(doc, System.out);

        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        printDocument(doc, baos);

        System.out.println("JDOM Using captured");
        parseUsingJDOM(doc);

        String xml = new String(baos.toByteArray());
        doc = buildDOM(xml);

        System.out.println("JDOM Using parsed");
        parseUsingJDOM(doc);

    }
}

Note that what thie code does, is build the DOM manually, output it, build the JDOM from the DOM, output that, then output the DOM as a string, re-parse the String as DOM, and then build the JDOM from the re-parsed XML.

This is the output (I put a newline in the output manually to make the actual DOM string have the XML declaration on it's own line):

<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:foo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:foo bar.xsd"/>
JDOM Using captured
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:foo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" schemaLocation="urn:iso:foo bar.xsd" />
JDOM Using parsed
<?xml version="1.0" encoding="UTF-8"?>
<Document xmlns="urn:iso:foo" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="urn:iso:foo bar.xsd" />

The bottom line is that the DOM that produces the output is not technically "namespace aware", and thus does not satisfy the expectations of JDOM 2.0.

Now, you use the following code to set the attributes:

root.setAttribute("xmlns", "urn:iso:foo");
root.setAttribute("xsi:schemaLocation", "urn:iso:foo bar.xsd");
root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");

If you used the Namespace-aware versions instead:

    root.setAttribute("xmlns", "urn:iso:foo");
    root.setAttributeNS("http://www.w3.org/2000/xmlns/", "xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
    root.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", "urn:iso:foo bar.xsd");
    doc.appendChild(root);

then JDOM will get it right.

This is why JDOM works in the parsed-from-string version above, because the Parsing is done in a namespace-aware fashion.

So, JDOM has a requirement that when processing DOM content, the DOM content is in an XML Namespace-aware format. This is why my tests all work, because my DOM content is all namespace-aware.

Unfortunately, this does not resolve the actual problem you have.... it just explains it.

JDOM2 should be compatible with JDOM 1.x in this instance, and the incompatibility is a problem. JDOM2 is doing the 'right' thing, but it should also probably do the 'wrong' thing as well, and insist on finding a namespace for those attributes defined on the DOM that are not declared properly too.

I have created issue 138 to track this: https://github.com/hunterhacker/jdom/issues/138

rolfl
  • 17,539
  • 7
  • 42
  • 76
0

According to the hint of rolfl, one way is not to mix up DOM and JDOM content and use only DOM to create and save the XML as a file:

DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
Document doc = dbFactory.newDocumentBuilder().newDocument();
doc.setXmlVersion("1.0");

Element root = doc.createElement("Document");

root.setAttribute("xmlns", "urn:iso:foo");
root.setAttribute("xsi:schemaLocation", "urn:iso:foo bar.xsd");
root.setAttribute("xmlns:xsi", "http://www.w3.org/2001/XMLSchema-instance");
doc.appendChild(root);

TransformerFactory transformerFactory = TransformerFactory.newInstance();
transformerFactory.setAttribute("indent-number", 2);
Transformer transformer = transformerFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
DOMSource source = new DOMSource(doc);
StreamResult xmlfile = new StreamResult(new BufferedWriter(new OutputStreamWriter(new FileOutputStream("testxml.xml"), "UTF8")));
transformer.transform(source, xmlfile);

But this is not a fully valid answer since the question is how to use JDOM2 to get the task done.

Community
  • 1
  • 1
Roland
  • 1,220
  • 1
  • 14
  • 29
  • Just to point out, that the issue is technically the inconsistent use of namespaces in DOM, and that `root.setAttribute("xsi:schemaLocation", "urn:iso:foo bar.xsd");` should really be `root.setAttributeNS("http://www.w3.org/2001/XMLSchema-instance", "xsi:schemaLocation", "urn:iso:foo bar.xsd");`, etc. – rolfl Nov 05 '14 at 12:45