/**
   @version 1.10 2004-09-04
   @author Cay Horstmann
*/

import java.awt.*;
import java.awt.event.*;
import java.io.*;
import javax.swing.*;
import javax.swing.event.*;
import javax.swing.table.*;
import javax.swing.tree.*;
import javax.xml.parsers.*;
import org.w3c.dom.*;
import org.xml.sax.*;

/**
   This program displays an XML document as a tree.
*/
public class DOMTreeTest
{ 
   public static void main(String[] args)
   {  
      JFrame frame = new DOMTreeFrame();
      frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
      frame.setVisible(true);
   }
}

/**
   This frame contains a tree that displays the contents of
   an XML document.
*/
class DOMTreeFrame extends JFrame
{  
   public DOMTreeFrame()
   {  
      setTitle("DOMTreeTest");
      setSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);

      JMenu fileMenu = new JMenu("File");
      JMenuItem openItem = new JMenuItem("Open");
      openItem.addActionListener(new
         ActionListener()
         {
            public void actionPerformed(ActionEvent event) { openFile(); }
         });
      fileMenu.add(openItem);

      JMenuItem exitItem = new JMenuItem("Exit");
      exitItem.addActionListener(new
         ActionListener()
         {
            public void actionPerformed(ActionEvent event) { System.exit(0); }
         });
      fileMenu.add(exitItem);

      JMenuBar menuBar = new JMenuBar();
      menuBar.add(fileMenu);
      setJMenuBar(menuBar);
   }

   /**
      Open a file and load the document.
   */
   public void openFile()
   {  
      JFileChooser chooser = new JFileChooser();
      chooser.setCurrentDirectory(new File("."));

      chooser.setFileFilter(new
         javax.swing.filechooser.FileFilter()
         {  
            public boolean accept(File f)
            {  
               return f.isDirectory() || f.getName().toLowerCase().endsWith(".xml");
            }
            public String getDescription() { return "XML files"; }
         });
      int r = chooser.showOpenDialog(this);
      if (r != JFileChooser.APPROVE_OPTION) return;
      File f = chooser.getSelectedFile();
      try
      {
         if (builder == null)
         {
            DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
            builder = factory.newDocumentBuilder();
         }

         Document doc = builder.parse(f);
         JTree tree = new JTree(new DOMTreeModel(doc));
         tree.setCellRenderer(new DOMTreeCellRenderer());

         setContentPane(new JScrollPane(tree));
         validate();
      }
      catch (IOException e)
      {
         JOptionPane.showMessageDialog(this, e);
      }
      catch (ParserConfigurationException e)
      {
         JOptionPane.showMessageDialog(this, e);
      }
      catch (SAXException e)
      {
         JOptionPane.showMessageDialog(this, e);
      }
   }

   private DocumentBuilder builder;
   private static final int DEFAULT_WIDTH = 400;
   private static final int DEFAULT_HEIGHT = 400;
}

/**
   This tree model describes the tree structure of an XML document.
*/
class DOMTreeModel implements TreeModel
{ 
   /**
      Constructs a document tree model.
      @param doc the document
   */
   public DOMTreeModel(Document doc) { this.doc = doc; }

   public Object getRoot() { return doc.getDocumentElement(); }

   public int getChildCount(Object parent)
   {  
      Node node = (Node) parent;
      NodeList list = node.getChildNodes();
      return list.getLength();
   }

   public Object getChild(Object parent, int index)
   {  
      Node node = (Node) parent;
      NodeList list = node.getChildNodes();
      return list.item(index);
   }

   public int getIndexOfChild(Object parent, Object child)
   {  
      Node node = (Node) parent;
      NodeList list = node.getChildNodes();
      for (int i = 0; i < list.getLength(); i++)
         if (getChild(node, i) == child)
            return i;
      return -1;
   }

   public boolean isLeaf(Object node) { return getChildCount(node) == 0; }
   public void valueForPathChanged(TreePath path, Object newValue) {}
   public void addTreeModelListener(TreeModelListener l) {}
   public void removeTreeModelListener(TreeModelListener l) {}

   private Document doc;
}

/**
   This class renders an XML node.
*/
class DOMTreeCellRenderer extends DefaultTreeCellRenderer
{  
   public Component getTreeCellRendererComponent(JTree tree, Object value, 
      boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus)
   {  
      Node node = (Node) value;
      if (node instanceof Element) return elementPanel((Element) node);

      super.getTreeCellRendererComponent(tree, value,
         selected, expanded, leaf, row, hasFocus);
      if (node instanceof CharacterData)
         setText(characterString((CharacterData) node));
      else
         setText(node.getClass() + ": " + node.toString());
      return this;
   }

   public static JPanel elementPanel(Element e)
   {
      JPanel panel = new JPanel();
      panel.add(new JLabel("Element: " + e.getTagName()));
      panel.add(new JTable(new AttributeTableModel(e.getAttributes())));
      return panel;      
   }
   
   public static String characterString(CharacterData node)
   {
      StringBuilder builder = new StringBuilder(node.getData());
      for (int i = 0; i < builder.length(); i++)
      {
         if (builder.charAt(i) == '\r') 
         {
            builder.replace(i, i + 1, "\\r");
            i++;
         }
         else if (builder.charAt(i) == '\n') 
         {
            builder.replace(i, i + 1, "\\n");
            i++;
         }
         else if (builder.charAt(i) == '\t') 
         {
            builder.replace(i, i + 1, "\\t");
            i++;
         }
      }
      if (node instanceof CDATASection)
         builder.insert(0, "CDATASection: ");
      else if (node instanceof Text)
         builder.insert(0, "Text: ");
      else if (node instanceof Comment)
         builder.insert(0, "Comment: ");
      
      return builder.toString();
   }
}

/** 
   This table model describes the attributes of an XML element.
*/
class AttributeTableModel extends AbstractTableModel
{  
   /**
      Constructs an attribute table model.
      @param map the named node map
   */
   public AttributeTableModel(NamedNodeMap map) { this.map = map; }

   public int getRowCount() { return map.getLength(); }
   public int getColumnCount() { return 2; }
   public Object getValueAt(int r, int c)
   {  
      return c == 0 ? map.item(r).getNodeName() : map.item(r).getNodeValue();
   }

   private NamedNodeMap map;
}


