8.4. Showing an Interactive Tree View

Figure 8.1. JTree display of Example 2.2

JTree display of Example 2.2

The Java API already provides a graphical view of trees with the JTree class. It displays the nodes in a window and they can be expanded and collapsed by clicking on their handles. This kind of display can also be obtained by XSL-FO (see section 6.9 of [7]) but few systems currently implement the full specification which would allow this to happen. Internet Explorer (top right of Figure 1.2) and Firefox use a similar scheme when displaying XML files but it is based on the visible property of Cascading Style Sheets (CSS). This form of interaction is used on most operating systems to display the contents of directories. For example, the XML file of Example 2.2 (page ) can be displayed in a window like the one shown in Figure 8.1. Nodes are shown there with directory icons and can be expanded or collapsed by clicking on the triangle to the left of the icon. A node showing a collapsed subtree has a triangle that points downward when it is expanded. A different display can be obtained by changing the look and feel in the Java API but the principle stays the same.

8.4.1. Building a JTree with DOM

Creating such a view from a DOM structure is only a matter of traversing the structure to create nodes that will be part of the JTree display. Its nodes are instances of the predefined DefaultMutableTreeNode class. To obtain the display in Figure 8.1, Example 8.1 (line 48‑10) creates a new TreeViewer instance that displays a JTree using a tree model built from the DOM tree constructed using the jTreeBuild method. Example 8.6 defines the class TreeViewer (line 13‑1) that creates a window to display the JTree instance. It is assigned an initial constant position and size and made scrollable; the application should terminate when the window is closed.

jTreeBuild (line 21‑2) is a static method so it is called with the instruction TreeViewer.jTreeBuild(.), (line 21‑2 and line 48‑10 of Example 8.2). It uses the same algorithm as compact (line 83‑16 of Example 8.1) by recursively processing element or text DOM nodes. In the case of an element node (line 25‑3), it creates a new DefaultMutableTreeNode for the element and adds attributes as its first child; it then processes each child by recursively building its subtree (line 37‑4) which is added as a child of the current node. A non-empty text node is simply a DefaultMutableTreeNode having the text as label.

Example 8.6. [TreeViewer.java]: JTree building with DOM and StAX Processing of an XML file

  1 import javax.swing.JFrame;
    import javax.swing.JScrollPane;
    import javax.swing.JTree;
    import javax.swing.tree.DefaultMutableTreeNode;
  5 
    import javax.xml.stream.XMLStreamException;
    import javax.xml.stream.XMLStreamReader;
    import javax.xml.stream.events.XMLEvent;
    
 10 import org.w3c.dom.NamedNodeMap;
    import org.w3c.dom.Node;
    
    public class TreeViewer extends JFrame{                              (1)
        TreeViewer(JTree jtree) {
 15         super("Tree viewer");
            setBounds(100,100,600,450);
            getContentPane().add(new JScrollPane(jtree));
            setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        }
 20 
        public static DefaultMutableTreeNode jTreeBuild(Node node) {     (2)
            if (node == null)
                return null;
            switch (node.getNodeType()) {
 25             case Node.ELEMENT_NODE: {                                (3)
                    DefaultMutableTreeNode treeNode = 
                       new DefaultMutableTreeNode(node.getNodeName());
                    NamedNodeMap attrs = node.getAttributes();
                    for (int i = 0; i < attrs.getLength(); i++)
 30                     treeNode.add(
                           new DefaultMutableTreeNode(
                                '@'+attrs.item(i).getNodeName()+
                                '['+attrs.item(i).getNodeValue()+']'));
                    // process children
 35                 Node child = node.getFirstChild();
                    while(child != null){
                        DefaultMutableTreeNode childTree = jTreeBuild(child); (4)
                        if(childTree!=null)
                            treeNode.add(childTree);                     (5)
 40                     child = child.getNextSibling();
                    }
                    return treeNode;
                }
               case Node.TEXT_NODE: {
 45                 String text = node.getNodeValue().trim();
                    return text.length()==0 ? null                       (6)
                                            : new DefaultMutableTreeNode(text);
                }
                default : return null;  // ignore other types of nodes
 50         }
        }
     
        public  static DefaultMutableTreeNode jTreeBuild                 (7)
                (XMLStreamReader xmlsr) throws XMLStreamException{
 55         if(xmlsr.isStartElement()){                                  (8)
                DefaultMutableTreeNode treeNode 
                    = new DefaultMutableTreeNode(xmlsr.getLocalName());
                int count = xmlsr.getAttributeCount();// attributes      (9)
                for (int i = 0; i < count; i++) {
 60                 treeNode.add(
                            new DefaultMutableTreeNode(
                                    "@"+xmlsr.getAttributeLocalName(i)+
                                    '['+xmlsr.getAttributeValue(i)+']'));
                }
 65             while (true){                                            (10)
                    do {xmlsr.next();} while(xmlsr.isWhiteSpace());
                    if(xmlsr.getEventType()==XMLEvent.END_ELEMENT)break;
                    DefaultMutableTreeNode childTree = jTreeBuild(xmlsr);
                    if(childTree!=null) treeNode.add(childTree);
 70             }
                return treeNode;
            } else if (xmlsr.isCharacters()){                            (11)
                return new DefaultMutableTreeNode(xmlsr.getText());
            } else 
 75             return null;
        }
    }

1

Inherits from JFrame so that the tree is displayed in a new window. The content of the constructor describes the position and dimensions of the window, inserts it in a scrolling pane and makes the program end when the window is closed.

2

Recursive method for creating a JTreeNode from a document node.

3

When the node is an element node, creates a new JTree node to which are added the attributes as simple JTree nodes and the children nodes which are built recursively.

4

Recursive building of the tree corresponding to a child.

5

Adds the new children JTree node as a child of the current JTree node.

6

In the case of a non-empty text node, creates a simple JTree node.

7

Recursive method for creating a JTreeNode from tokens returned by an XMLStreamReader.

8

Creates a new tree node with the name of the element.

9

Adds a node for each attribute name and value.

10

Loops on children nodes that are not whitespace and adds a new child for each new tree built.

11

Creates a node with the character content.


8.4.2. Building a JTree with SAX

A JTree using SAX processing is is created with a special-purpose handler whose methods will be called when XML elements are encountered during the parsing process. This is similar to the process described in Section 8.2 where we only needed to keep the current indentation value.

In Example 8.7, we build a tree using DefaultMutableTreeNode instances and keep track of the current node being built (line 13‑1). When an XML element start-tag is encountered, in startElement (line 19‑3), a new tree node is created and made the current node (line 25‑6). Its attributes are then added as children (line 29‑7). A text node is simply added as a child of the current node (line 41‑10). When an end-tag is encountered (line 34‑8), we set the current node to the parent of the current node. The constructor (line 15‑2) only keeps a copy of the JTree that will be displayed. Its model is initialized on the first call of startElement (line 22‑5).

To create a JTree, we add (line 40‑6 of Example 8.3) a new JTreeHandler given as argument to the SAX parser. Display of the tree is achieved by creating an instance (line 42‑7) of the TreeViewer class (line 13‑1 of Example 8.6). The JTree instance is built by calling the SAX parser and giving it a JTreeHandler (line 40‑6).

SAX allows the pipelining of handlers that work in succession during a single parse of the file, a process called filtering, but here we simplify by parsing the file twice: once for the textual input (line 36‑5 of Example 8.3) and once more for the tree display (line 40‑6).

Example 8.7. [JTreeHandler.java]: JTree building with SAX Processing of an XML file

  1 import org.xml.sax.SAXException;
    import org.xml.sax.Attributes;
    import org.xml.sax.helpers.DefaultHandler;
    
  5 import javax.swing.JTree;
    import javax.swing.tree.DefaultMutableTreeNode;
    import javax.swing.tree.DefaultTreeModel;
    
    public class JTreeHandler extends DefaultHandler{
 10 
        private DefaultTreeModel treeModel;
        private DefaultMutableTreeNode node = null;
        private JTree jtree;                                             (1)
    
 15     JTreeHandler(JTree jtree){                                       (2)
            this.jtree=jtree;
        }
    
        public void startElement(String uri, String localName,           (3)
 20                  String raw, Attributes attrs) throws SAXException {
            super.startElement(uri,localName,raw,attrs);                 (4)
            if(node==null) //initialise node and model                   (5)
                jtree.setModel(
                    new DefaultTreeModel(
 25                     node=new DefaultMutableTreeNode(localName)));    (6)
            else // add child and set node to the child
                node.add(node=new DefaultMutableTreeNode(localName));
            // add attributes as children
            for (int i = 0; i < attrs.getLength(); i++)                  (7)
 30             node.add(new DefaultMutableTreeNode(
                       '@'+attrs.getLocalName(i)+"["+attrs.getValue(i)+']'));
        }
    
        public void endElement(String uri, String localName, String raw) (8)
 35         throws SAXException { 
            super.endElement(uri,localName,raw);                         (9)
            node = (DefaultMutableTreeNode) node.getParent();
        }
        
 40     public void characters(char[] ch, int start, int length) 
            throws SAXException {                                        (10)
            super.characters(ch,start,length);
            String text = new String(ch,start,length).trim();
            if(text.length()>0) node.add(new DefaultMutableTreeNode(text));
 45     }
    }
    
    

1

The JTree node currently being built.

2

Initializes the JTree structure to which the JTree nodes will be added.

3

Deals with a start element.

4

Calls the start method of the superclass.

5

If the node is not initialized, creates a new tree model and uses it for the root.

6

Creates a new node and adds it to the current JTree node. Note that the new current node is updated to this new node.

7

Adds attributes as nodes of the current node.

8

Processes the end tag.

9

Calls the endElement method of the superclass and the current node is now the parent of the current node.

10

Processes a text node by creating a new JTree node with text as its content if it is not empty.


8.4.3. Building a JTree with StAX

A JTree using StAX processing is created with the same recursive process described for the DOM approach. Looking at Example 8.6, we immediately see the strong similiraties between the two approaches. It is only a matter of traversing the structure to create nodes that will be part of the JTree display. Its nodes are instances of the predefined DefaultMutableTreeNode class. To obtain the display in Figure 8.1, Example 8.5 (line 38‑7) creates a new TreeViewer instance that displays a JTree by parsing again the file, this is why we create a new instance of a XMLStreamReader that is given as parameter to the second TreeViewer.JTreeBuild static method in Example 8.6

It uses the same algorithm as compact (line 83‑16 of Example 8.1) by recursively processing element or text DOM nodes. In the case of an element node (line 55‑8), it creates a new DefaultMutableTreeNode for the element and adds attributes as its first children (line 65‑10); it then processes each child by recursively building its subtree (line 65‑10) which is added as a child of the current node. In the case of character content (line 72‑11), a single node with this character content is returned.