Overloaded?
6.3 DOM Level 2 Modules
7.2.3 Creating XML with JDOM
Once the code has the properties in a (more) usable form, it's time to start using JDOM. The first task is to create a JDOM Document. For that to occur, you need to create a root element for the document, using JDOM's Element class. Since an XML document can't exist without a root element, an instance of the Element class is required as input for the
Document class constructor.
Creating an Element requires only the passing of the element's name. There are alternate versions that take in namespace information, and I'll discuss those a little later. For now, it's easiest to use the root element's name, and since this needs to be a top-level, arbitrary name (to contain all the property nestings), I use "properties" in the code. Once this element is created, it's used to create a new JDOM Document.
Then, it's on to dealing with the properties in the supplied file. The list of property names is obtained as a Java Enumeration through the Properties object's propertyNames( )
method. Once that name is available, it can be used to obtain the property value by using the getProperty( ) method. At this point, you've got the root element of the new XML document, the property name to add, and the value for that property. And then, like any other good program, you iterate through all of the other properties until finished. At each step, this information is supplied to a new method, createXMLRepresentation( ) . This performs the logic for handling conversion of a single property into a set of XML elements. Add this code, as shown here, to your source file:
private void convertToXML(Properties props, String xmlFilename) throws IOException {
// Create a new JDOM Document with a root element "properties" Element root = new Element("properties");
Document doc = new Document(root);
// Get the property names
Enumeration propertyNames = props.propertyNames( ); while (propertyNames.hasMoreElements( )) {
String propertyName = (String)propertyNames.nextElement( ); String propertyValue = props.getProperty(propertyName); createXMLRepresentation(root, propertyName, propertyValue); }
// Output document to supplied filename
XMLOutputter outputter = new XMLOutputter(" ", true);
FileOutputStream output = new FileOutputStream(xmlFilename); outputter.output(doc, output);
}
Don't worry about the last few lines that output the JDOM Document yet. I'll deal with this in the next section, but first I want to cover the createXML-Representation( ) method, which contains the logic for dealing with a single property, and creating XML from it. The easiest (and logically, the first) step in moving from a property to XML is to take the name of the property and create an Element with that name. You've already seen how to do this; simply pass the name of the element to its constructor. Once the element is created, assign the value of the property as the textual content of the element. This can be done easily enough through the setText( ) method, which of course takes a String. Once the element is ready for use, it can be added as a child of the root element through the addContent( ) method. In fact, any legal JDOM construct can be passed to an element's addContent( ) method, as it is overloaded to accept these various types. These include instances of a JDOM Entity, Comment, ProcessingInstruction, and more. But I'll get to those later; for now, add the following method into your source file:
/**
* <p> This will convert a single property and its value to * an XML element and textual value. </p>
*
* @param root JDOM root <code>Element</code> to add children to. * @param propertyName name to base element creation on.
* @param propertyValue value to use for property. */
private void createXMLRepresentation(Element root, String propertyName, String propertyValue) {
Element element = new Element(propertyName); element.setText(propertyValue);
root.addContent(element); }
At this point, you can actually compile the source file, and then use the resulting
PropsToXML class. Supply a properties file (you can type in or download the
enhydra.properties file shown earlier in this chapter), as well as an output filename, as shown here:[3]
[3] If you're not familiar with *NIX, the backslash at the end of each line ( \) simply allows for
continuation of the command on the next line; Windows users should enter the entire command on one line.
/javaxml2/build $ java javaxml2.PropsToXML \
/javaxml2/ch07/properties/enhydra.properties \ enhydraProps.xml
This whirs along for a fraction of a second, and then generates an enhydraProps.xml file. Open this up; it should look like Example 7-3.[4]
[4] Note that the line wraps in the example are for publishing purposes only; in your document, each
property with opening tag, text, and closing tag should be on its own line.
Example 7-3. First version of the enhydraProps.xml document
<?xml version="1.0" encoding="UTF-8"?> <properties> <org.enhydra.classpath.separator>":"</org.enhydra.classpath.separator> <org.enhydra.initialargs>"./bootstrap.conf"</org.enhydra.initialargs> <org.enhydra.initialclass>org.enhydra.multiServer.bootstrap.Bootstrap </org.enhydra.initialclass> <org.enhydra.classpath>"."</org.enhydra.classpath> <org.xml.sax.parser>"org.apache.xerces.parsers.SAXParser" </org.xml.sax.parser> </properties>
In about 50 lines of code, you've gone from Java properties to XML. However, this XML document isn't much better than the properties file; there is still no way to relate the
org.enhydra.initialArgs property to the org.enhydra.classpath property. Our job isn't done yet.
Instead of using the property name as the element name, the code needs to take the property name and split it on the period delimiters. For each of these "sub-names," an element needs to be created and added to the element stack. Then the process can repeat. For the property name org.xml.sax, the following XML structure should result:
<org> <xml>
<sax>[property Value]</sax> </xml>
At each step, using the Element constructor and the addContent( ) method does the trick; and once the name is completely deconstructed, the setText( ) method can be used to set the last element's textual value. The best way is to create a new Element, called current, and use it is a "pointer" (there aren't any pointers in Java—it's just a term); it will always point at the element that content should be added to. At each step, the code also needs to see if the element to be added already exists. For example, the first property,
org.xml.sax, creates an org element. When the next property is added
(org.enhydra.classpath), the org element does not need to be created again. To facilitate this, the getChild( ) method is used. This method takes the name of the child element to retrieve, and is available to all instances of the Element class. If the child specified exists, that element is returned. However, if no child exists, a null value is returned, and it is on this null value that our code can key. In other words, if the return value is an element, that becomes the current element, and no new element needs to be created (it already exists). However, if the return from the getChild( ) call is null, a new element must be created with the current sub-name, added as content to the current
element, and then the current pointer is moved down the tree. Finally, once the iteration is over, the textual value of the property can be added to the leaf element, which turns out to be (nicely) the element that the current pointer references. Add this code to your source file:
private void createXMLRepresentation(Element root, String propertyName, String propertyValue) {
/*
Element element = new Element(propertyName); element.setText(propertyValue);
root.addContent(element); */
int split;
String name = propertyName; Element current = root; Element test = null;
while ((split = name.indexOf(".")) != -1) { String subName = name.substring(0, split); name = name.substring(split+1);
// Check for existing element
if ((test = current.getChild(subName)) == null) { Element subElement = new Element(subName); current.addContent(subElement); current = subElement; } else { current = test; } }
// When out of loop, what's left is the final element's name Element last = new Element(name);
last.setText(propertyValue); current.addContent(last);
}
With this addition in place, recompile the program and run it again. This time, your output should be a lot nicer, as shown in Example 7-4.
Example 7-4. Updated output from PropsToXML <?xml version="1.0" encoding="UTF-8"?> <properties> <org> <enhydra> <classpath> <separator>":"</separator> </classpath> <initialargs>"./bootstrap.conf"</initialargs> <initialclass>org.enhydra.multiServer.bootstrap.Bootstrap</initialclass> <classpath>"."</classpath> </enhydra> <xml> <sax> <parser>"org.apache.xerces.parsers.SAXParser"</parser> </sax> </xml> </org> </properties>
And, just as quickly as you've started in on JDOM, you've got the hang of it. However, you might notice that the XML document violates one of the rules of thumb for document design introduced in Chapter 2 (in the section detailing usage of elements versus usage of attributes). You see, each property value has a single textual value. That arguably makes the property values suitable as attributes of the last element on the stack, rather than content. Proving that rules are meant to be broken, I prefer them as content in this case, but that's neither here nor there.
For no other reason than demonstration purposes, let's look at converting the property values to attributes rather than textual content. This turns out to be quite easy, and can be done in one of two ways. The first is to create an instance of the JDOM Attribute class. The constructor for that class takes the name of the attribute and its value. Then, the resulting instance can be added to the leaf element with that element's setAttribute( )
method. That approach is shown here:
// When out of loop, what's left is the final element's name Element last = new Element(name); /* last.setText(propertyValue); */
Attribute attribute = new Attribute("value", propertyValue); current.setAttribute(attribute);
current.addContent(last);
If you want to compile the file with these changes, be sure you add an import statement for the
Attribute
class:import org.jdom.Attribute;
A slightly easier way is to use one of the convenience methods that JDOM offers. Since adding attributes is such a common task, the Element class provides an overloaded version of setAttribute( ) that takes a name and value, and internally creates an
Attribute object. In this case, that approach is a little clearer:
// When out of loop, what's left is the final element's name Element last = new Element(name);
/* last.setText(propertyValue); */
last.setAttribute("value", propertyValue);
This works just as well, but also avoids having to use an extra import statement. You can compile this change in and run the sample program. The new output should match Example 7-5.
Example 7-5. Output of PropsToXML using attributes
<?xml version="1.0" encoding="UTF-8"?> <properties> <org> <enhydra> <classpath> <separator value="":"" /> </classpath> <initialargs value=""./bootstrap.conf"" /> <initialclass value="org.enhydra.multiServer.bootstrap.Bootstrap" /> <classpath value=""."" /> </enhydra> <xml> <sax> <parser value=""org.apache.xerces.parsers.SAXParser"" /> </sax> </xml> </org> </properties>
Each property value is now an attribute of the innermost element. Notice that JDOM converts the quotation marks within the attribute values, which are disallowed, to entity references so the document as output is well-formed. However, this makes the output a little less clean, so you may want to switch your code back to using textual data within elements, rather than attributes.