namespaces and JDOM
Asked Answered
H

1

7

My current code is printing out the xml like this :

<type xmlns="http://www.example.com">
  <OBJECT_TYPE xmlns="">x3000</OBJECT_TYPE> 
- <prop xmlns="">
    <DESCRIPTION>a very fast train</DESCRIPTION> 
    <PARENT>NULL</PARENT> 
    <VIRTUAL>0</VIRTUAL> 
    <VISIBLE>1</VISIBLE> 
    <PICTURE>NULL</PICTURE> 
    <HELP>NULL</HELP> 
    <MIN_NO>NULL</MIN_NO> 
    <MAX_NO>NULL</MAX_NO> 
    <NAME_FORMAT>NULL</NAME_FORMAT> 
  </prop>
</type>

But I want this output :

<type xmlns="http://www.example.com">
  <OBJECT_TYPE>x3000</OBJECT_TYPE> 
- <prop>
    <DESCRIPTION>a very fast train</DESCRIPTION> 
    <PARENT>NULL</PARENT> 
    <VIRTUAL>0</VIRTUAL> 
    <VISIBLE>1</VISIBLE> 
    <PICTURE>NULL</PICTURE> 
    <HELP>NULL</HELP> 
    <MIN_NO>NULL</MIN_NO> 
    <MAX_NO>NULL</MAX_NO> 
    <NAME_FORMAT>NULL</NAME_FORMAT> 
  </prop>
</type>

How to do that ? This is my current code :

public void saveXmlToFile(Type objType, Properties property)
    throws IOException, ParserConfigurationException, SAXException,
    JDOMException {

        File xmlFile = new File(XMLEditorService.getXMLEditorService()
                .getFile());
        org.jdom2.Document doc = new SAXBuilder().build(xmlFile);
        Element root = doc.getRootElement();
        Namespace ns = Namespace.getNamespace("http://www.example.com");
        Element type = new Element("type");
        Element prop = new Element("prop");

        // Add <type> as a child of <root>
        root.addContent(type);

        // Set namespace on <type>
        type.setNamespace(ns);

        type.addContent(new Element("OBJECT_TYPE").setText(objType.getObjectType()));

        // Turn off namespace on <prop>
        prop.setNamespace(Namespace.NO_NAMESPACE);

        // Add <prop> as a child of <type>
        type.addContent(prop);

        prop.addContent(new Element("DESCRIPTION").setText(property.getDescription()));
        prop.addContent(new Element("PARENT").setText(property.getParent()));
        prop.addContent(new Element("VIRTUAL").setText(property.getVirtual()));
        prop.addContent(new Element("VISIBLE").setText(property.getVisible()));
        prop.addContent(new Element("PICTURE").setText(property.getPicture()));
        prop.addContent(new Element("HELP").setText(property.getHelp()));
        prop.addContent(new Element("MIN_NO").setText(property.getMin_no()));
        prop.addContent(new Element("MAX_NO").setText(property.getMax_no()));
        prop.addContent(new Element("NAME_FORMAT").setText(property.getName_format()));

        XMLOutputter xmlOutput = new XMLOutputter(Format.getPrettyFormat());
        // Create a new file and write XML to it
        xmlOutput.output(doc, new FileOutputStream(new File(XMLEditorService.getXMLEditorService().getFile())));
        System.out.println("Wrote to file");

}
Hoot answered 14/2, 2014 at 12:6 Comment(0)
P
7

In an XML document, if you add xmlns="http://a.b.c" to an Element, then that Element, and all it's descendant Elements, will be in the namespace "http://a.b.c", unless the descendant Elements happen to change the xmlns declaration. This means that, in an XML Document, the default namespace 'cascades' to the descendants. This process is a 'convenience' so that the XML is not as 'cluttered'. The 'default' namespace is the namespace that has no prefix. The Child Elements of a parent that sets the default Namespace to some value "http://a.b.c" are 'in' that namespace, even though they do not have an xmlns="http://a.b.c" declaration on their tag-line.

When JDOM models a document like this, it sets the Namespace of each Element node to the exact valu it should be. It does not 'calculate', or 'cascade' the namespace of an Element. In JDOM, if you change the namespace of a parent Element, it does not change the namespaces of the child elements as well. So, for example, if you have:

<root xmlns="http://a.b.c">
    <child />
</root>

Then you have both root and child in the default Namespace "http://a.b.c". That document could be created, in JDOM, as:

Namesapce ns = Namespace.get("http://a.b.c");
Element root = new Element("root", ns);
Element child = new Element("child", ns);
root.addConent(child);

Note how ns was added to both Elements. The output of this JDOM would be, as expeced:

<root xmlns="http://a.b.c">
    <child />
</root>

Now, if you change the root's Namespace:

Namesapce ns = Namespace.get("http://a.b.c");
Element root = new Element("root", ns);
Element child = new Element("child", ns);
root.addConent(child);
root.setNamespace(Namespace.NO_NAMESPACE);

you will get:

<root>
    <child xmlns="http://a.b.c"/>
</root>

But, more interesting, if you leave the root namespace, and change the child namespace,

Namesapce ns = Namespace.get("http://a.b.c");
Element root = new Element("root", ns);
Element child = new Element("child", ns);
root.addConent(child);
child.setNamespace(Namespace.NO_NAMESPACE);

you get:

<root xmlns="http://a.b.c">
    <child xmlns="" />
</root>

When you create a JDOM Element without a namespace argument new Element("tag") instead of new Element("tag", Namespace) then JDOM will automatically put the new Element in the NO_NAMESPACE namespace (the same as new Element("tag", Namespace.NO_NAMESPACE); ). This is what you are doing.

So, JDOM is doing what you ask it to do... but what you are asking it to do does not appear to be what you want.

What you say you want is:

<type xmlns="http://www.example.com">
  <OBJECT_TYPE>x3000</OBJECT_TYPE> 
- <prop>
    <DESCRIPTION>a very fast train</DESCRIPTION> 
    <PARENT>NULL</PARENT> 
    <VIRTUAL>0</VIRTUAL> 
    <VISIBLE>1</VISIBLE> 
    <PICTURE>NULL</PICTURE> 
    <HELP>NULL</HELP> 
    <MIN_NO>NULL</MIN_NO> 
    <MAX_NO>NULL</MAX_NO> 
    <NAME_FORMAT>NULL</NAME_FORMAT> 
  </prop>
</type>

The above XML has everything in the namespace Namespace.getNamespace("http://www.example.com").

Your code is putting a lot of things in to the NO_NAMESPACE namespace instead. Your code should probably look like this (note all the , ns I have added to the new Element(...))...

See the JavaDoc for Element(String), and for Element(String, Namespace);.

    org.jdom2.Document doc = new SAXBuilder().build(xmlFile);
    Element root = doc.getRootElement();
    Namespace ns = Namespace.getNamespace("http://www.example.com");
    Element type = new Element("type", ns);
    Element prop = new Element("prop", ns);

    // Add <type> as a child of <root>
    root.addContent(type);

    // Set namespace on <type>
    // type.setNamespace(ns); NO NEED, done on new Element("type", ns); above

    type.addContent(new Element("OBJECT_TYPE", ns).setText(objType.getObjectType()));

    // Turn off namespace on <prop>
    // NO!!!! You want to keep the namespace ON!!!
    // prop.setNamespace(Namespace.NO_NAMESPACE);

    // Add <prop> as a child of <type>
    type.addContent(prop);

    prop.addContent(new Element("DESCRIPTION", ns).setText(property.getDescription()));
    prop.addContent(new Element("PARENT", ns).setText(property.getParent()));
    prop.addContent(new Element("VIRTUAL", ns).setText(property.getVirtual()));
    prop.addContent(new Element("VISIBLE", ns).setText(property.getVisible()));
    prop.addContent(new Element("PICTURE", ns).setText(property.getPicture()));
    prop.addContent(new Element("HELP", ns).setText(property.getHelp()));
    prop.addContent(new Element("MIN_NO", ns).setText(property.getMin_no()));
    prop.addContent(new Element("MAX_NO", ns).setText(property.getMax_no()));
    prop.addContent(new Element("NAME_FORMAT", ns).setText(property.getName_format()));

    XMLOutputter xmlOutput = new XMLOutputter(Format.getPrettyFormat());
    // Create a new file and write XML to it
    xmlOutput.output(doc, new FileOutputStream(new File(XMLEditorService.getXMLEditorService().getFile())));
    System.out.println("Wrote to file");

In the above code, everything is in the "http://www.example.com" namespace, and, as a result, JDOM only needs to output the top-level Element with an xmlns declaration, and you should get exactly what you want.

For what it's worth, you can't only blame JDOM for this confusion.... it is the nature of Namespaces that it is complicated.

Paryavi answered 14/2, 2014 at 14:3 Comment(8)
You did not add the , ns to all the prop.addContent(....) like I have in the code, did you?Paryavi
yeh right. This worked the oposit from what I had done. This explaination deserves plenty of upvotes. one of the best i ever read on Stackoverflow :-)Hoot
@Hoot it helps that I wrote a chunk of that code.... you should have been helping me a few days ago when I asked for more information ;-) Instead of deleting your question, you could have saved yourself tons of stress.Paryavi
If I wanna do the oposit like hide namespaces If I wanna print just <type> how do i do then?Hoot
Remove all the , ns members (all of them), and everything will then be in the default namespace which will be "".... or change Namespace ns = Namespace.NO_NAMESPACE;Paryavi
hm that will just print <type xmlns=""> not <type>Hoot
Then type is inside some other Element that has a different Namespace. and you're not showing me what that other element is (or what its namespace is)Paryavi
yeh root has namespace so ill guess ill just do : root.setNamespace(Namespace.NO_NAMESPACE);Hoot

© 2022 - 2024 — McMap. All rights reserved.