In the first part of this series, I introduced SAX, the Simple API for XML. SAX is an event based API, and is best suited for reading XML once only (our example was a data import). But what if you want to read, alter and save your XML in the course of your application? This is where the DOM fits in. This article will describe DOM, how it came to be and how to use it.
A brief history of DOM
The Document Object Model is a platform- and language-neutral interface that will allow programs and scripts to dynamically access and update the content, structure and style of documents. The document can be further processed and the results of that processing can be incorporated back into the presented page. This is an overview of DOM-related materials here at W3C and around the web.
DOM started life in September 1997 as a W3C (World Wide Web Consortium) requirements publication. From an XML parsing perspective, the most important requirements are:
General
-
The Object Model can be used to construct and deconstruct the document.
-
It must be possible to read in a document and write out a structurally identical document to disk (both documents can be represented by the same raw structural model).
Document Manipulation
-
There will be a way to add, remove and change elements and/or tags (if permitted by the Document Type Definition and not precluded by security or validity considerations) in the document structure.
-
There will be a way to add, remove and change attributes (if permitted by the Document Type Definition and not precluded by security or validity considerations) in the document structure.
-
Operations must restore consistency before they return.
Content Manipulation
-
There will be a way to determine the containing element from any text part of the document.
-
There will be a way to manipulate (add, change, delete) content.
-
There will be a way to navigate content.
After a few drafts, the DOM Level 1 Specification became a W3C recommendation in October 1998. So what does the "Level 1" bit mean? The DOM working group plan to release their work in stages. Level one concentrates on the actual core, HTML, and XML document models. It contains functionality for document navigation and manipulation.
Level two will include a style sheet object model, and define functionality for manipulating the style information attached to a document. It will also allow rich queries of the document and define some event models, and provide support for XML namespaces.
Further levels will specify some interface with the possibly underlying window system, including some ways to prompt the user. They will also contain functions to manipulate the document's DTD. Finally, they will include some security models.
On 23 September 1999, the working group released the first version of the working draft of the level 2 specification.
org.w3c.dom
Like SAX, DOM is only an interface specification. It is up to vendors to provide implementations of the interfaces. Fortunately, many of the SAX based XML parser implementations also provide a DOM interface. I will be using the XML4J from IBM Alphaworks for the examples in this article.
Access to the DOM is through the Document interface. The Document object represents the root of the document tree. The Document interface contains a number of factory method for creating elements, attributes, comments and processing instructions. The object model is often implied to have a tree structure. Although W3C were very cautious not to use the word tree (in case they were seen to be mandating implementation details), DOM does imply a logical tree structure even if the implementer has chosen a different structure.
All nodes on the document tree implement the Node interface (Document inherits from the Node interface). The Node interface provides for navigation between child, parent and sibling nodes, as well as a direct link to the owning Document object via the getOwnerDocument() method. A Node object can be made to reveal the type of node it represents via the getNodeType() method. The table on the next page provides a mapping from XML terminology to the node types defined on the Node interface.
Each of the node types has a corresponding class type whose interface extends Node. The Node object can be cast to its corresponding type class. For example:
XML entity | org.w3c.dom.Node type |
---|---|
Root element | DOCUMENT_NODE |
Tag / element <..> or </..> | ELEMENT_NODE |
Element Attributes | ATTRIBUTE_NODE |
Text | CDATA_SECTION_NODE or TEXT_NODE |
XML processinginstruction <? … ?> | PROCESSING_INSTRUCTION_NODE |
Entity references | ENTITY_REFERENCE_NODE |
public Document getDocument(Node node) { Document doc = null; if (node.getType() == Node.DOCUMENT_NODE) { // if we call getOwnerDocument on the document object it returns null doc = (Document) node; } else { doc = node.getOwnerDocument(); } return doc; }
Using DOM to handle XML
As I stated at the beginning of the article, DOM is better suited to read / alter / write XML than SAX is. The example we will be using is that of an application's properties file written in XML.
Since there are numerous XML parsers that support the DOM interfaces, we're going to use IBM Alphaworks' XML4J parser to construct the object model, and write our own code to manipulate and write back our model to XML. Since parsing/loading and saving back XML files is common behaviour for a DOM application, it makes sense to factor that behaviour out to a common class. I've written such a class, called DOMManager :
package org.accu.cornish.xml.dom; import org.w3c.dom.*; import com.ibm.xml.dom.*; import com.ibm.xml.parsers.DOMParser; import java.io.IOException; import java.io.PrintWriter; import org.xml.sax.SAXException; public class DOMManager { private PrintWriter out = null; public DOMManager() {} public Document loadDocument(String uri) throws IOException { // this is all the code necessary to get the Document object from the URI DOMParser domParser = new DOMParser(); Document doc; try { domParser.parse( uri ); doc = domParser.getDocument(); } catch (SAXException badCall) { // convert it into an IOException throw new IOException("Error parsing XML: " + uri); } return doc; } public void setOutputTarget(PrintWriter output) { this.out = output; } public void saveDocument(Node node) throws java.io.IOException { if (this.out == null) { throw new IOException("No target identified for save"); } print( node ); } private void print(Node node) { /* see dom.DOMWriter in the IBM Samples */ } }
The method print(Node node) was pinched in whole from IBM's sample application DOMWriter, along with helper methods (normalize and sortAttributes). The source code for DOMWriter is available from the installation for XML4J.
Now, to write any application that needs to import and export XML, you can have your Property manager class delegate your Document class management to a DOMManager member.
Inheritance vs. Delegation
I originally designed DOMManager to be a super class for DOM manipulating classes to inherit from. After consideration, I've realised that it is more useful to have your class to implement DOMManager as a private member variable, and delegate calls to it. This changes the nature of DOMManager from being a behaviour "template" to being a helper object. I can identify two very good reasons to make this change of viewpoint:
We don't mix the loading/saving interface into our property management interface.
Delegating to our member variable allows our class to manage the lifetime of the DOM. This would allow us to implement alternative memory management policies such as lazy evaluation or saving on a separate thread behind the scenes.
A very simple Property manager class that echoes the document back to the standard output has its relevant functionality shown here.
import java.io.*; public class PropertiesViewer { private DOMManager settings; private Document doc; private String inFile; ... public void printToSystemOut() { try { doc = settings.loadDocument(inFile); // inherited // change our output to be the System.out settings.setOutputTarget(new PrintWriter(System.out)); settings.saveDocument(doc); // inherited } catch (IOException ioe) { System.out.println("I/O Problem with " + inFile); } } }
Editing documents with DOM
Now that we have a basic framework in place for loading / saving our XML documents, we can proceed to a less trivial example. Consider an application that has the following property file:
<?xml version="1.0"?> <domapp> <display> <default_scheme>normal</default_scheme> <scheme name="normal"> <normal>black</normal> <error>red</error> <background>white</background> <highlight>blue</highlight> <highlight_background>green</highlight_background> </scheme> <scheme name="weird"> <normal>yellow</normal> <error>red</error> <background>black</background> <highlight>purple</highlight> <highlight_background>grey</highlight_background> </scheme> </display> </domapp>
Our application wants to encapsulate the properties as a Java object. As an example of showing how to edit the DOM, we will change the highlight_background of the "normal" scheme from green to yellow.
First, we create our DomAppProperties class.
package org.accu.cornish.xml.dom; import org.accu.cornish.xml.*; import org.w3c.dom.*; import java.io.*; import java.util.Vector; public class DomAppProperties { private String propertiesFile = "D:\\xml\\domapp.properties"; private DOMManager domManager; private Document doc; public DomAppProperties() { domManager = new DOMManager(); } public void load() { … } public void save() { … } public boolean changeDisplaySchemeValue(String scheme,String name,String value) { if (doc == null) { System.err.println("Error: no document node"); return false; } try { Node schemeNode = this.getDisplaySchemeNode(scheme); Node node = this.findElement(schemeNode, name); this.setElementValue(node, value); } catch (BadXMLException bXML) { System.err.println(bXML.message()); } return true; } private Node getRootElementNode() throws BadXMLException { // start at the document node // get the root element <domapp> Node node = doc.getDocumentElement(); if ( !node.getNodeName().equals("domapp") ) { throw new BadXMLException("Unexpected root node <" + node.getNodeName() + ">, was expecting <domapp>"); } return node; } private Node getDisplayNode(Node rootElementNode) throws BadXMLException { Node node = null; // verified node = <domapp> // get the child element <display> NodeList children = rootElementNode.getChildNodes(); for (int i = 0; i < children.getLength(); ++i) { if (children.item(i).getNodeName().equals("display")) { node = children.item(i); } } if (! node.getNodeName().equals("display") ) { throw new BadXMLException("Unexpected structure, was expecting " + "<display> after <domapp>"); } return node; } private Node getDisplaySchemeNode(String schemeName) throws BadXMLException { Node node = getDisplayNode( getRootElementNode() ); // verified node = <display> // get the children - can be <default_scheme> or <scheme> boolean bFound = false; NodeList children = node.getChildNodes(); for (int i = 0; i < children.getLength() && !bFound; ++i) { if (children.item(i).getNodeName().equals("scheme")) { // we are in a <scheme> node - is it the right one? // // it's the one with with name attribute equal to the schemeName passed into this method NamedNodeMap attrs = children.item(i).getAttributes(); Node attNode = null; if ( (attNode = attrs.getNamedItem("name")) == null) { throw new BadXMLException("<scheme> node has no name attribute"); } else { // we're in the <scheme name=" node - check the value if (attNode.getNodeValue().equals(schemeName)) { // BINGO! bFound = true; node = children.item(i); } } } //if scheme } // children of <display> children = null; if (! bFound ) { throw new BadXMLException("Cannot find <scheme name=\"" + schemeName + "\">"); } // return the node return node; } private Node findElement(Node node, String name) { Node returnNode = null; if (node.getNodeType() == Node.ELEMENT_NODE) { if (node.getNodeName().equals(name)) { returnNode = node; } else { // go through all the children NodeList children = node.getChildNodes(); if (children != null) { int len = children.getLength(); Node tempNode; found: for (int i = 0; i < len; ++i) { returnNode = findElement(children.item(i), name); if (returnNode != null) { break found; // from this for loop } } } } } return returnNode; } private void setElementValue(Node element, String newValue) { NodeList children = element.getChildNodes(); if (children != null) { int len = children.getLength(); for (int i = 0; i < len; ++i) { if (children.item(i).getNodeType() == Node.TEXT_NODE) { children.item(i).setNodeValue(newValue); } } } } }
First note that there is no parser specific code in this class - this is because we've hidden it all away in the DomManager class. The load() and save() methods are trivial enough not to detail them. The important public method is the ChangeDisplaySchemeValue(scheme, element, value) method. This method performs three distinct tasks (each as private methods - highlighted in bold). First it finds the appropriate <scheme> node (recall that our XML file can contain more than one scheme). Next, the correct element in the scheme is identified. Finally, the new value is set.
The private methods of the class give some useful examples of processing with the DOM tree. Note that because I'm using elements and values (tags and text) in my XML to represent the settings, this shapes the setElementValue() method. If I had used tags with attributes, this method would need to be implemented differently.
Now we need a harness to show the DomAppProperties in action. For convenience, I'll define a main method on the class.
public static void main(String[] args) { DomAppProperties editor = new DomAppProperties(); editor.load(); editor.changeDisplaySchemeValue("normal", "highlight_background", "yellow"); editor.changeDisplaySchemeValue("weird", "normal", "orange"); editor.save(); }
This is all we need to make our changes.
Of course, the DomAppProperties is a class of dubious usefulness - it doesn't even have methods to report existing values. I hope it fulfills its intended purpose - to demonstrate how to use DOM and DOM compliant parsers to manipulate XML documents.
DOM provides quite a rich API for manipulating documents. Most users would likely wrap it with an interface more useful to the task at hand (e.g. property files). DOM parsers are best suited to processing XML documents that need to be read (possibly more than once) and may be subject to a non-trivial amount of manipulation. DOM is definitely suited to recreating the XML document at a later time. As you may recall from the previous article, SAX is better suited to read-once, process immediately usage.
Free DOM parsers
There are a number of free XML Parsers that support the DOM Level 1. Here are the main players:
-
XML4J (IBM) - http://www.alphaworks.ibm.com/formula/xml
-
Java Project X (Sun) - http://developer.java.sun.com/developer/earlyAccess/xml/index.html
-
XML Parser for Java 2 (Oracle) - http://technet.oracle.com
-
Docuverse DOM SDK - this is a DOM implementation that can sit on top of any SAX compliant parser - http://www.docuverse.com/domsdk/
Further References
DOMit is an online utility for checking the validity of your XML files. The idea is you paste your document into the text pane, then hit the button to create a DOM representation of your XML. Very useful http://www.networking.ibm.com/xml/XmlValidatorForm.html
The World Wide Web Consortium DOM site - http://www.w3.org/DOM/
XML-Dev mailing list - < xml-dev@ic.ac.uk > and a good XML site from Seybold and O'Reilly - www.xml.com
OASIS - www.oasis-open.org/cover/cml.html
Another XML site (close links with OASIS) - www.xml.org