zoukankan      html  css  js  c++  java
  • How to display XML in a JTree using JDOM

    How to display XML in a JTree using JDOM



    This brief tutorial will explain how to use Java to make and display a tree view of XML using JDOM and JTree. I don't claim this is original as most of the content is fromhere but hopefully this will be easier to understand. 

    For this example I'm using the following configuration and jar files: 
    Java 1.5 
    JDOM 1.0 
    Apache commons-io 1.1


    Java comes with a nice tree JTree that we will use. To put XML data into the tree we will use the adapter pattern. This means we will wrap the XML data in wrappers that the JTree can understand and work with. More specifically, we need to implement the TreeModel interface.

    The TreeModel interface has the following methods: 

        public Object getRoot();
        public Object getChild(Object parent, int index);
        public int getChildCount(Object parent);
        public boolean isLeaf(Object node);
        public int getIndexOfChild(Object parent, Object child);
        public void valueForPathChanged(TreePath path, Object newValue);
        public void addTreeModelListener(TreeModelListener l);
        public void removeTreeModelListener(TreeModelListener l);
    
    
     download source code
    
    
    For this example we only need to worry about the first five since we won't be able to modify the underlying XML from the display. We need to create an "model adapter" that will use the TreeModel interface to explain the JDOM format of XML. Here is the bare bones version of our model adapter: 

    public class JDOMTreeModelAdapter implements TreeModel {
    
        //need to implement these
        public Object getRoot(){};
        public Object getChild(Object parent, int index){};
        public int getChildCount(Object parent){};
        public boolean isLeaf(Object node){};
        public int getIndexOfChild(Object parent, Object child){};
    
        // won't worry about these 
        public void valueForPathChanged(TreePath path, Object newValue){};
        public void addTreeModelListener(TreeModelListener l){};
        public void removeTreeModelListener(TreeModelListener l){};
    
    }
    
    

    In JDOM, an XML file is referred to as a Document. Every Document contains a root Element, which in turn contains other Elements. To make things easier, we will also create a "node adapter" as a kind of helper class for the model adapter. The node adapter will wrap a JDOM Element object, since this is the main class used to model data in JDOM. Here is a basic node adapter (the version you can download below has additional functionality for display): 

    public class JDOMAdapterNode {
        
        /** the Element encapsulated by this node */
        public Element node;
    
        /**
         * Creates a new instance of the JDOMAdapterNode class
         * @param Element node
         */
        public JDOMAdapterNode(Element node) {
            this.node = node;
        }
    
        /**
         * Finds index of child in this node.
         * 
         * @param child The child to look for
         * @return index of child, -1 if not present (error)
         */
        public int index(JDOMAdapterNode child) {
    
            int count = childCount();
            for (int i = 0; i < count; i++) {
                JDOMAdapterNode n = this.child(i);
                if (child.node == n.node) {
                    return i;
                }
            }
            return -1; // Should never get here.
        }
    
        /**
         * Returns an adapter node given a valid index found through
         * the method: public int index(JDOMAdapterNode child)
         * 
         * @param searchIndex find this by calling index(JDOMAdapterNode)
         * @return the desired child
         */
        public JDOMAdapterNode child(int searchIndex) {
            Element child = (Element)node.getChildren().get(searchIndex);
            return new JDOMAdapterNode(child);
        }
    
        /**
         * Return the number of children for this element/node
         * 
         * @return int number of children
         */
        public int childCount() {
            return node.getChildren().size();
        }
    }
    
     download source code
    

    Now that we have an adapter node it should be much easier to implement the TreeModel interface in our JDOMTreeModelAdapter. We need to add a constructor for passing in the JDOM Document for the tree to display. 

    public class JDOMToTreeModelAdapter implements TreeModel {
    
        //JDOM Document to view as a tree
        private Document document;
        
        //listeners for changes, not used in this example
        private ArrayList listenerList = new ArrayList();
        
        //constructor used to set the document to view
        public JDOMToTreeModelAdapter(Document doc) {
            document = doc;
        }
        
        //override from TreeModel
        public Object getRoot() {
            if(document == null) return null;
            return new JDOMAdapterNode(document.getRootElement());
        }
        
        //override from TreeModel
        public Object getChild(Object parent, int index) {
            JDOMAdapterNode node = (JDOMAdapterNode) parent;
            return node.child(index);
        }
        
        //override from TreeModel
        public int getIndexOfChild(Object parent, Object child) {
            JDOMAdapterNode node = (JDOMAdapterNode) parent;
            return node.index((JDOMAdapterNode) child);
        }
    
        //override from TreeModel
        public int getChildCount(Object parent) {
            JDOMAdapterNode jdomNode = (JDOMAdapterNode)parent;
            return jdomNode.childCount();
        }
    
        //override from TreeModel
        public boolean isLeaf(Object node) {
            JDOMAdapterNode jdomNode = (JDOMAdapterNode)node;
            return (jdomNode.node.getTextTrim().length() > 0);
        }
    
        //override from TreeModel
        public void valueForPathChanged(TreePath path, Object newValue) {
            // Null. We won't be making changes in the GUI
            // If we did, we would ensure the new value was really new,
            // adjust the model, and then fire a TreeNodesChanged event.
        }
        
        
        /*
         * Use these methods to add and remove event listeners.
         * (Needed to satisfy TreeModel interface, but not used.)
         */
        
        // override from TreeModel
    	public void addTreeModelListener(TreeModelListener listener) {
    		if (listener != null && !listenerList.contains(listener)) {
    			listenerList.add(listener);
    		}
    	}
        // override from TreeModel
    	public void removeTreeModelListener(TreeModelListener listener) {
    		if (listener != null) {
    			listenerList.remove(listener);
    		}
    	}
    
        /*
    	 * Invoke these methods to inform listeners of changes. (Not needed for this
    	 * example.) Methods taken from TreeModelSupport class described at
    	 * http://java.sun.com/products/jfc/tsc/articles/jtree/index.html That
    	 * architecture (produced by Tom Santos and Steve Wilson) is more elegant. I
    	 * just hacked 'em in here so they are immediately at hand.
    	 */
        public void fireTreeNodesChanged(TreeModelEvent e) {
    		Iterator listeners = listenerList.iterator();
    		while (listeners.hasNext()) {
    			TreeModelListener listener = (TreeModelListener) listeners.next();
    			listener.treeNodesChanged(e);
    		}
    	} 
        public void fireTreeNodesInserted(TreeModelEvent e) {
    		Iterator listeners = listenerList.iterator();
    		while (listeners.hasNext()) {
    			TreeModelListener listener = (TreeModelListener) listeners.next();
    			listener.treeNodesInserted(e);
    		}
    	}   
        public void fireTreeNodesRemoved(TreeModelEvent e) {
    		Iterator listeners = listenerList.iterator();
    		while (listeners.hasNext()) {
    			TreeModelListener listener = (TreeModelListener) listeners.next();
    			listener.treeNodesRemoved(e);
    		}
    	}   
        public void fireTreeStructureChanged(TreeModelEvent e) {
    		Iterator listeners = listenerList.iterator();
    		while (listeners.hasNext()) {
    			TreeModelListener listener = (TreeModelListener) listeners.next();
    			listener.treeStructureChanged(e);
    		}
    	}
    }
    
     download source code
    

    Now we need to create a Document from some raw xml to pass into our TreeModel adaptor for display. There are many choices for doing this and in this case we will use the JDOM SAXBuilder. The SAXBuilder can take multiple forms of xml to build the Document. I prefer the Apache IOUtils. Here I'll create a new class that uses a SAXBuilder to create a JDOM Document from the xml source. 

    public class XMLTree {
        
        //keep handles on the documents and readers
        private static Document document;
        private static SAXBuilder saxBuilder;
        private static boolean validate = false;
        private static BufferedReader reader;
        private static byte[] xml = new byte[] {};
        
        //tree to be displayed
        private static JTree tree;
    
        /**
         * Creates a new instance of the JDOMTree class
         */
        public XMLTree() {
            saxBuilder = new SAXBuilder("org.apache.xerces.parsers.SAXParser", validate);
        }
        
    
        /**
         * Returns the JTree with xml inside.
         * 
         * @return JTree is present, or null.
         */
        public JTree getTree() {
            return tree;
        }
    
        
        /**
         * Read in an XML file to display in the tree
         * 
         * @param xmlFile Path to an XML file.
         */
        public void parseFile(File xmlFile) throws Exception {
    
            try {
                
                //read file into a Document object
                reader = new BufferedReader(new FileReader(xmlFile));            
                xml = IOUtils.toByteArray(reader);
                
                try {
                    document = saxBuilder.build(new ByteArrayInputStream(xml));
                    //TODO later I'll add validation against the schema 
                    //validate(document);
                } catch (JDOMException jdome) {
                    throw new Exception("
    "+jdome.toString());
                }
                
                //convert the document object into a JTree
                JDOMToTreeModelAdapter model = new JDOMToTreeModelAdapter(document);
                tree = new JTree(model);
                tree.setCellRenderer(new XMLTreeCellRenderer());
                
            } catch (Exception e){
                //if any exception set to null so we will  
            	//refresh the display with the exception
                tree = null;            
                throw e;
            }
        }
    }
    
     download source code
    

    That's it for the internals. All we need now is a simple gui to test that this code really works. Here is a simple gui with a file chooser that allows the user to browse for the xml file to display. I am by no means a gui programmer so this will be the bare bones. 

    public class XMLViewer extends JFrame {
       
        private final String title = "JDOM XML Tree";
        private final MenuBar menuBar = new MenuBar();
        private final Menu fileMenu = new Menu();
        private final MenuItem open = new MenuItem();
        private final JFileChooser fileChooser = new JFileChooser();
      
        private final XMLTree xmlTree;
        private File file;
        private JTree tree;
        private Exception exception;
        
        private final int windowHeight = 600;
        private final int leftWidth = 380;
        private final int rightWidth = 600;
        private final int windowWidth = leftWidth + rightWidth;
        private final Font treeFont = new Font("Lucida Console", Font.BOLD, 14);
        private final Font textFont = new Font("Lucida Console", Font.PLAIN, 13);
        
        
        /**
         * Creates a simple gui for viewing xml in a tree.
         */
        public XMLViewer() {        
            
            setTitle(getClass().getSimpleName());
            setPreferredSize(new Dimension(windowWidth, windowHeight));
            setFocusable(true);
            setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);        
            
            xmlTree = new XMLTree();
            
            fileChooser.setFileSelectionMode(JFileChooser.FILES_AND_DIRECTORIES);
            fileChooser.setFileFilter(new XMLFileFilter());
            fileChooser.setCurrentDirectory(new File("C:/"));
            
            
            fileMenu.setLabel("File");
            
            open.setLabel("Browse");
            open.addActionListener(new MyActionListener());
            
            makeFrame();
            
            open.dispatchEvent(new ActionEvent(open,1001,open.getActionCommand()));
        }
        
        /**
         * Construct a frame of the most recently read-in document.
         */
        private void makeFrame() {
            
            getContentPane().removeAll();
                    
            fileMenu.add(open);
            menuBar.add(fileMenu);
            setMenuBar(menuBar);
            
            pack();
            setVisible(true);
        }
        
        /**
         * Displays the tree.
         * 
         * @param tree JTree to display
         */
        public void display() {
            try {
                makeFrame();
                
                JScrollPane treeScrollPane = null;
                JScrollPane textScrollPane = null;
                
                // Build left-side view
                if(tree != null) {
                    tree.setFont(treeFont);        
                    treeScrollPane = new JScrollPane(tree);
                    treeScrollPane.setPreferredSize(new Dimension(leftWidth, windowHeight));
                } else {
                    JEditorPane errorMessagePane = new JEditorPane();
                    errorMessagePane.setEditable(false);
                    errorMessagePane.setContentType("text/plain");
                    errorMessagePane.setText("Error: unable to build tree from xml:
    "+ exception.toString());
                    errorMessagePane.setCaretPosition(0);
                    treeScrollPane = new JScrollPane(errorMessagePane);
                }
                
                // Build right-side view
                if(file != null) {
                    StringBuilder sb = new StringBuilder();
                    
                    //TODO show validation
    
                    try {
                        BufferedReader reader = new BufferedReader(new FileReader(file));
                        String line = "";
                        while((line = reader.readLine()) != null) {
                            sb.append(line + "
    ");
                        }
                        reader.close();
                    } catch (Exception e) {
                        System.err.println("exception when reading file for display");
                        e.printStackTrace();
                    } 
                    
                    JEditorPane textPane = new JEditorPane();
                    textPane.setEditable(false);
                    textPane.setContentType("text/plain");
                    textPane.setText(sb.toString());
                    textPane.setCaretPosition(0);
                    textPane.setFont(textFont);
                    textScrollPane = new JScrollPane(textPane);
                    textScrollPane.setPreferredSize(new Dimension(rightWidth, windowHeight));
                }
        
                // Build split-pane view
                JSplitPane splitPane = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
                        treeScrollPane, textScrollPane);
                
                splitPane.setContinuousLayout(true);
                splitPane.setDividerLocation(leftWidth);
                splitPane.setPreferredSize(new Dimension(windowWidth + 10,
                        windowHeight + 10));
        
                // Add GUI components
                setLayout(new BorderLayout());
                add("Center", splitPane);
                pack();
                setVisible(true);
            } catch (Exception e) {
                System.err.println("error when updating xml viewer");
                e.printStackTrace();
            }
        }
        
        /** listener for when user selects a file to view */
        private class MyActionListener implements ActionListener {
    
            public void actionPerformed(ActionEvent ae) {
                if (ae.getSource() == open) {
    
                    int returnVal = fileChooser.showOpenDialog(null);
    
                    if (returnVal == JFileChooser.APPROVE_OPTION) {
    
                        //reset for currently selected message
                        exception = null;
                        
                        file = fileChooser.getSelectedFile();
                        
                        // update the gui for this file
                        setTitle(title + " | " + (file != null ? file.getAbsolutePath() : "Select A File"));
                        
                        // remember last directory used
                        fileChooser.setCurrentDirectory(file);
                        
                        try {
                            xmlTree.parseFile(file);
                        } catch (Exception e) {
                            exception = e;
                        }
                        tree = xmlTree.getTree();
                        display();
                    }
                }
            }
        }
        
        public static void main(String[] argv) {
            new XMLViewer();
        }
    }
    
     download source code
    

    Even though the XMLViewer class contains a main method it won't compile for you. That's because I have yet to explain two additional classes that are used to make things easier. First off, it's really helpful when browsing for files to display if your application is smart enough to hide non-xml file. This can be easily accomplished with a FileFilter, so here is a simple FileFilter. 

    public class XMLFileFilter extends FileFilter {
        
        @Override
        public boolean accept(File f) {
            
            if (f.isDirectory()) {
                return true;
            }
            
            String extension = getExtension(f);
            if(extension != null) {
                if(extension.equals("xml")) {
                    return true;
                }
            }
            return false;
        }
    
        @Override
        public String getDescription() {
            return ".xml";
        }
        
        /**
         * Get the lower case extension of a file.
         */  
        private String getExtension(File f) {
            String ext = null;
            String s = f.getName();
            int i = s.lastIndexOf('.');
    
            if (i > 0 &&  i < s.length() - 1) {
                ext = s.substring(i+1).toLowerCase();
            }
            return ext;
        }
    }
    
     download source code
    

    In addition to the FileFilter we can gain some display control of the JTree using an TreeCellRenderer. The TreeCellRenderer will directly control how the xml is displayed inside the JTree. I will extend the DefaultTreeCellRenderer because it already has some useful code. 

    public class XMLTreeCellRenderer extends DefaultTreeCellRenderer {
        
        //colors for tree items
        private final Color elementColor = new Color(0, 0, 128);
        private final Color textColor = new Color(0, 128, 0);
     
        //remove icons
        public XMLTreeCellRenderer() {
            setOpenIcon(new ImageIcon("open.gif"));
            setClosedIcon(new ImageIcon("closed.gif"));
            setLeafIcon(new ImageIcon("leaf.gif"));
        }
        
        @Override
        public Component getTreeCellRendererComponent(JTree tree, Object value,
                boolean sel, boolean expanded, boolean leaf, int row, boolean hasFocus) {
            JDOMAdapterNode adapterNode = (JDOMAdapterNode)value;
            if(adapterNode.node.isRootElement()) {
                value = adapterNode.node.getName();
            } else if(adapterNode.node.getChildren().size() > 0) {
                value = adapterNode.node.getName();
            } else {
                value = adapterNode.node.getName() +" ["+adapterNode.node.getTextTrim()+"]";
            }
            
            super.getTreeCellRendererComponent(tree, value, sel, expanded, leaf, row, hasFocus);
            
            if(!selected) {
                if(adapterNode.node.getTextTrim().length() == 0) {
                    setForeground(elementColor);
                } else {
                    setForeground(textColor);
                }
            }
            
            return this; 
        }
    }
    
     download source code
    

    Don't forget to use the .jar files mentioned at the top of this page as they are also required to run this code. 

    Enjoy! 
  • 相关阅读:
    commands.getstatusoutput和subprocess.call结果不一致
    win10 企业版 2015 长期服务激活
    pycharm设置护眼模式
    实战--滚动菜单
    javascript中this的用法
    jQuery之remove与empty的区别
    clone方法案例实践
    jQuery内部插入与外部插入
    jQuery文档处理
    2020/02/11星期二复习
  • 原文地址:https://www.cnblogs.com/daichangya/p/12959343.html
Copyright © 2011-2022 走看看