8.1. Document Object Model (DOM)

The document object model is standardized by the W3C consortium, but its Java bindings can depend on the implementation. Java, since version 1.4, integrates XML processing packages that we use in our examples; no other special library is needed this way. The Java program given in Example 8.1 is a command line application that accepts an XML file as parameter and outputs the same compact text representation as the one used in Example 5.8 (page ) that we obtained with the XSLT stylesheet of Example 5.9.

The first lines of Example 8.1 import the necessary packages to process the XML files. The main method (line 26‑1) creates a DocumentBuilderFactory object (line 34‑2) from which we will obtain a DOM parser after having configured the necessary options. By default, parsing only checks for well-formedness, so in order for the parsing to validate against a DTD a flag must be set (line 36‑3). To validate against an XML Schema another one must be set (line 38‑4). As explained in Section 3.1.1 and Section 3.5, the XML instances reference their corresponding DTD or XML Schema.

Creating a parser to build a new DOM document is done (line 41‑5) by using a factory method which returns a DocumentBuilder object to which an error handler (line 5‑2 of Example 8.2) is assigned to get a notification of possible error messages. If the file is valid (i.e. no SAXParseException is raised)[3], a Document object can be obtained and the compact method (line 83‑16) is called (line 46‑9) on the root element.

In order to simplify further processing, we define (line 66‑12) a method to remove from the DOM structure, starting from a given node, all text nodes containing only spaces and carriage returns and nodes other than element or text nodes, such as comments or processing instructions nodes. This method goes through each children removing nodes (line 76‑14) that are not interesting for further processing; some care has to be taken to save the next sibling (line 71‑13) before removing it from the DOM because the removal has the side-effect of setting the sibling nodes to null. For nodes that it does not remove, it calls itself recursively (line 78‑15) to strip-space from the current child node. When it is called on the document element (line 45‑8), it will go through all the document to remove empty text nodes and nodes that are not text or elements.

Within compact (line 83‑16), the processing depends on the type of element obtained on line 85‑17. If it is an element node (line 86‑18), we first print the node name followed by an opening bracket. On line 91‑19, attributes are printed with their names preceded by an @, followed by their value in square brackets. A new line is started if it is not the first attribute. The processing of the children elements, starting on line 97‑20, is a simple traversal algorithm with a recursive call to compact (line 100‑21), followed by the printing of a closing bracket. In the case of a text node (line 106‑22), we print the content of the text node removing leading and trailing spaces, which could be carriage returns or newlines.

Example 8.1. [DOMCompact.java] Text compaction of the cellar book (Example 2.2) with Java using the DOM model

  1 import org.w3c.dom.Attr;
    import org.w3c.dom.Document;
    import org.w3c.dom.NamedNodeMap;
    import org.w3c.dom.Node;
  5 
    import javax.xml.parsers.DocumentBuilder;
    import javax.xml.parsers.DocumentBuilderFactory;
    import javax.xml.parsers.FactoryConfigurationError;
    import javax.xml.parsers.ParserConfigurationException;
 10 
    import org.xml.sax.InputSource;
    import org.xml.sax.ErrorHandler;
    import org.xml.sax.SAXException;
    import org.xml.sax.SAXParseException;
 15 
    import java.io.IOException;
    
    import javax.swing.JTree;
    import javax.swing.JFrame;
 20 import javax.swing.JScrollPane;
    import javax.swing.tree.DefaultMutableTreeNode;
    import javax.swing.tree.DefaultTreeModel;
    
    public class DOMCompact{
 25 
        public static void main(String argv[]) {                         (1)
            // is there anything to do?
            if (argv.length != 1) {
                System.out.println("Usage: java DOMCompact file");
 30             System.exit(1);
            }
            // parse file
            try {
                DocumentBuilderFactory factory =                         (2)
 35                DocumentBuilderFactory.newInstance();
                factory.setValidating(true);                             (3)
                factory.setNamespaceAware(true);
                factory.setAttribute(                                    (4)
                    "http://java.sun.com/xml/jaxp/properties/schemaLanguage",
 40                 "http://www.w3.org/2001/XMLSchema");
                DocumentBuilder builder = factory.newDocumentBuilder();  (5)
                builder.setErrorHandler(new CompactErrorHandler());
                Document doc = builder.parse(argv[0]);                   (6)
                Node docElem=doc.getDocumentElement();                   (7)
 45             stripSpace(docElem);                                     (8)
                compact(docElem,"");                                     (9)
                System.out.println();
                new TreeViewer(                                          (10)
                    new JTree(new DefaultTreeModel(
 50                       TreeViewer.jTreeBuild(docElem)))).setVisible(true);
            } catch (SAXParseException e) {                              (11)
                System.out.println(argv[0]+"is not well-formed");
                System.out.println(e.getMessage()+"at line "+e.getLineNumber()+
                                   ", column "+e.getColumnNumber());
 55         } catch (SAXException e){
                System.out.println(e.getMessage());
            } catch (ParserConfigurationException e){
                System.out.println("Parser configuration error");
            } catch (IOException e) {
 60             System.out.println("IO Error on "+argv[0]);
            }
        } 
        
        // remove empty text nodes (ie nothing else than spaces and carriage return)
 65     // and nodes that are not text or element ones
         private static void stripSpace(Node node){                      (12)
             Node child = node.getFirstChild();
             while(child!=null){
                 // save the sibling of the node that will
 70              // perhaps be removed and set to null
                 Node c = child.getNextSibling();                        (13)
                 if((child.getNodeType()==Node.TEXT_NODE &&
                     child.getNodeValue().trim().length()==0) ||
                    ((child.getNodeType()!=Node.TEXT_NODE)&&
 75                  (child.getNodeType()!=Node.ELEMENT_NODE)))
                     node.removeChild(child);                            (14)
                 else // process children recursively
                 	stripSpace(child);                                     (15)
                 child=c;
 80          }
         }
        
        public static void compact(Node node,String indent) {            (16)
            if (node == null)return;
 85         switch (node.getNodeType()) {                                (17)
                case Node.ELEMENT_NODE: {                                (18)
                    System.out.print(node.getNodeName()+'[');
                    indent += blanks(node.getNodeName().length()+1);
                    NamedNodeMap attrs = node.getAttributes();
 90                 boolean first=true;
                    for (int i = 0; i < attrs.getLength(); i++) {        (19)
                        if(!first)System.out.print('\n'+indent);
                        System.out.print('@'+attrs.item(i).getNodeName()+'['
                                         +attrs.item(i).getNodeValue()+']');
 95                     first=false;
                    }
                    for(Node child = node.getFirstChild();               (20)
                             child != null; child = child.getNextSibling()){
                        if(!first) System.out.print('\n'+indent);
100                     compact(child,indent);                           (21)
                        first=false;
                    }
                    System.out.print(']');
                    break;
105             }
                case Node.TEXT_NODE: {                                   (22)
                    System.out.print(node.getNodeValue().trim());
                    break;
                }
110         }
        }
    
        // production of string of spaces with a lazy StringBuffer
        private static StringBuffer blanks = new StringBuffer();
115     private static String blanks(int n){                             (23)
            for(int i=blanks.length();i<n;i++)
                blanks.append(' ');
            return blanks.substring(0,n);
        }
120 }
    

1

Beginning of the Java program that checks if there is a parameter to be used as a file name to compact.

2

Beginning of the parsing process creating a document builder that will be used for validation and for creating the DOM structure of the document.

3

Indicates that document validation will be performed, on top of the default well-formedness check.

4

Validation should be done with a Schema, not a DTD.

5

Creates a class that will be used for creating the DOM structure.

6

Parses the document using the previously created objects.

7

Saves a reference to the root document element.

8

Remove all white space nodes and non text or element nodes from the whole DOM structure.

9

Creates a compact form by traversing the DOM structure from the root. compact is defined on line 83‑16

10

Creates an interactive window representing the DOM structure.

11

Deals with exceptions that can occur during the parsing phase, such as non well-formedness, validation error or file-access exceptions.

12

Empty text children nodes are removed so that only meaningful content nodes are compacted.

13

Saves a reference to the next sibling.

14

Removes the current child from the DOM structure.

15

Strips space recursively on this child node.

16

Prints a compact representation of the current node. indent is a string that is added to the start of each line of output in this node.

17

Determines the type of the current node in order to choose the appropriate processing step.

18

An element node is first printed followed by an opening bracket and the indentation is updated so that recursive calls will print their content more indented that the name of the current node.

19

Attributes are printed on separate (indented) lines; an attribute name is prefixed with an @ and the value is enclosed in square brackets.

20

The children are processed with the current indentation. Finally, a closing bracket is added.

21

Compact this child recursively.

22

Normalizes and prints a text node.

23

Returns a string of a given number of spaces as a substring of static StringBuffer that is expanded as necessary.


Example 8.2. [CompactErrorHandler.java] Error handler of the DOM parsing of Example 8.1

  1 import org.xml.sax.ErrorHandler;
    import org.xml.sax.SAXException;                                     (1)
    import org.xml.sax.SAXParseException;
    
  5  public class CompactErrorHandler implements ErrorHandler{           (2)
        private void message(String mess,SAXParseException e)            (3)
        throws SAXException{
            System.out.println("\n"+mess+
                               "\n Line:"+e.getLineNumber()+
 10                            "\n URI:" +e.getSystemId()+
                               "\n Message:"+e.getMessage());
        }
        
        public void fatalError(SAXParseException e) throws SAXException{ (4)
 15         message("Fatal error",e);
        }
        
        public void error(SAXParseException e) throws SAXException{      (5)
            message("Error",e);
 20     }
        
        public void warning(SAXParseException e) throws SAXException{    (6)
            message("Warning",e);
        }
 25 }
    

1

SAXException and SAXParseException are used even though DOM parsing is used.

2

Implementation of XML parsing error handling.

3

Prints out an error message with the current error position. This method is used for all three kinds of errors.

4

An irrecoverable error.

5

An XML validation error.

6

A simple warning not requiring processing to stop.




[3] Even in the DOM model, SAXExceptions and SAXParseExceptions can be raised by the document builder.