blob: 45d1a6317eb234a293b0239683110dd3e669ea54 [file] [log] [blame]
/*
* The Apache Software License, Version 1.1
*
*
* Copyright (c) 1999 The Apache Software Foundation. All rights
* reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by the
* Apache Software Foundation (http://www.apache.org/)."
* Alternately, this acknowledgment may appear in the software itself,
* if and wherever such third-party acknowledgments normally appear.
*
* 4. The names "Xerces" and "Apache Software Foundation" must
* not be used to endorse or promote products derived from this
* software without prior written permission. For written
* permission, please contact apache@apache.org.
*
* 5. Products derived from this software may not be called "Apache",
* nor may "Apache" appear in their name, without prior written
* permission of the Apache Software Foundation.
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
* ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* This software consists of voluntary contributions made by many
* individuals on behalf of the Apache Software Foundation and was
* originally based on software copyright (c) 1999, International
* Business Machines, Inc., http://www.apache.org. For more
* information on the Apache Software Foundation, please see
* <http://www.apache.org/>.
*/
package ui;
import java.awt.*;
import java.awt.event.*;
import java.util.*;
import java.io.*;
import javax.swing.*;
import javax.swing.tree.*;
import javax.swing.event.*;
import java.net.URL;
import java.net.MalformedURLException;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.w3c.dom.Element;
import org.w3c.dom.Text;
import org.xml.sax.InputSource;
import org.xml.sax.ErrorHandler;
import org.xml.sax.Locator;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;
import org.apache.xerces.parsers.DOMParser;
import ui.DOMParserSaveEncoding;
/**
* A sample DOM Tree Viewer. This sample program illustrates how to
* traverse a DOM tree and display it in a Swing JTree View.
*
* @version
*/
public class TreeView extends JFrame implements ActionListener, TextListener {
//
// Constants
//
static final boolean DEBUG = true;
/** Default parser name. */
static final String
DEFAULT_PARSER_NAME = "org.apache.xerces.parsers.DOMParser";
static int WARNING = 0;
static int ERROR=1;
static int FATAL_ERROR=2;
static final String title = "TreeViewer";
static final String openString = "Open";
static final String quitString = "Quit";
static final String reloadString = "Reload current XML file";
static final String expandString = "Expand Tree";
static final String collapseString = "Collapse Tree";
//
// Data
//
ErrorStorer ef;
String fname;
DOMTree m_tree;
JTextArea sourceText, messageText;
Vector textLine;
FileNameInput fni;
DOMParserSaveEncoding parser;
Image openFolder;
Image closedFolder;
Image leafImage;
/**
* Constructor
*/
public TreeView() {
this(null);
}
/**
* Constructor
*/
public TreeView(String uri) {
super(uri);
openFolder = DefaultImages.createOpenFolderImage();
closedFolder = DefaultImages.createClosedFolderImage();
leafImage = DefaultImages.createLeafImage();
parser = new DOMParserSaveEncoding();
ef = new ErrorStorer();
fname = uri;
JMenuBar jmb = new JMenuBar();
JMenu fileMenu = new JMenu("File");
JMenuItem item;
item = new JMenuItem(openString);
fileMenu.add(item);
item.addActionListener(this);
item = new JMenuItem(quitString);
fileMenu.add(item);
item.addActionListener(this);
JMenu shortcutMenu = new JMenu("Shortcuts");
item = new JMenuItem(expandString);
shortcutMenu.add(item);
item.addActionListener(this);
item = new JMenuItem(collapseString);
shortcutMenu.add(item);
item.addActionListener(this);
item = new JMenuItem(reloadString);
shortcutMenu.add(item);
item.addActionListener(this);
jmb.add(fileMenu);
jmb.add(shortcutMenu);
setJMenuBar(jmb);
getContentPane().add(createUI(fname));
}
/** create and return the entire UI from the root TreeNode
*/
JComponent createUI(String filename) {
if (DEBUG) System.out.println("START createUI:"+filename);
// create the message panel first so we can send messages to it...
messageText = new JTextArea(3,40);
messageText.setFont(new Font("dialog", Font.PLAIN, 12));
JPanel messagePanel = new JPanel(new BorderLayout());
messagePanel.add(new JScrollPane(messageText) {
public Dimension getPreferredSize(){
Dimension size = TreeView.this.getSize();
return new Dimension(size.width, size.height / 4);
}
public Dimension getMinimumSize(){
return new Dimension(100, 100);
}
},
BorderLayout.CENTER);
messagePanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Messages"),
BorderFactory.createEmptyBorder(4, 4, 4, 4)
));
// create the TextArea for XML source
sourceText = new JTextArea();
sourceText.setFont(new Font("monospaced", Font.PLAIN, 12));
sourceText.setBackground(Color.white);
sourceText.setForeground(Color.black);
sourceText.setSelectedTextColor(Color.black);
sourceText.setSelectionColor(Color.red);
sourceText.setEditable(false);
JPanel sourcePanel = new JPanel(new BorderLayout());
sourcePanel.add(new JScrollPane(sourceText){
public Dimension getPreferredSize(){
Dimension size = TreeView.this.getSize();
return new Dimension(size.width / 2, size.height * 3 / 5);
}
public Dimension getMinimumSize(){
return new Dimension(100, 100);
}
},
BorderLayout.CENTER);
sourcePanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Source View"),
BorderFactory.createEmptyBorder(4, 4, 4, 4)
));
// create the JTree and scroll pane.
JPanel treePanel = new JPanel(new BorderLayout());
m_tree = new DOMTree();
m_tree.setCellRenderer(new XMLTreeCellRenderer());
m_tree.getSelectionModel().setSelectionMode
(TreeSelectionModel.SINGLE_TREE_SELECTION);
// Listen for when the selection changes, call nodeSelected(node)
m_tree.addTreeSelectionListener(
new TreeSelectionListener() {
public void valueChanged(TreeSelectionEvent e) {
TreeNode node = (TreeNode)
(e.getPath().getLastPathComponent());
nodeSelected(node);
}
}
);
m_tree.setRowHeight(18);
m_tree.setFont(new Font("dialog", Font.PLAIN, 12));
treePanel.add(new JScrollPane(m_tree) {
public Dimension getPreferredSize(){
Dimension size = TreeView.this.getSize();
return new Dimension(size.width / 2, size.height * 3 / 5);
}
public Dimension getMinimumSize(){
return new Dimension(100, 100);
}
},
BorderLayout.CENTER);
treePanel.setBorder(BorderFactory.createCompoundBorder(
BorderFactory.createTitledBorder("Tree View"),
BorderFactory.createEmptyBorder(4, 4, 4, 4)
));
// refreshUI loads everthything!
refreshUI(filename);
// use the new JSplitPane to dynamically resize...
JComponent split = new JSplitPane(JSplitPane.HORIZONTAL_SPLIT,
true, treePanel, sourcePanel);
JComponent mainSplitPane =
new JSplitPane(JSplitPane.VERTICAL_SPLIT,
true, split, messagePanel);
if (DEBUG) System.out.println("END createUI:"+filename);
return mainSplitPane;
}
/** refreshUI is called when we have a new filename to parse.
*/
void refreshUI(String filename) {
if (DEBUG) System.out.println("START refreshUI:"+filename);
messageText.selectAll();
messageText.cut();
if (filename == null || filename.equals("")) {
messageText.setForeground(Color.red);
messageText.append("No input XML filename specified:"+filename+"\n");
return;
}
fname = filename;
Document newRoot = getRoot(filename);
if (newRoot == null) {
messageText.setForeground(Color.red);
messageText.append("Unable to get new DOM Tree for:"+filename+"\n");
return;
}
m_tree.setDocument(newRoot);
// new Source
sourceText.selectAll();
sourceText.cut();
readXMLFile(fname, sourceText);
setTitle(title+": "+filename);
if (m_tree!= null)
expandTree();
if (ef != null && ef.getErrorNodes()!=null
&& ef.getErrorNodes().size() > 0 ) {
messageText.setForeground(Color.red);
messageText.append("XML source, "+fname+" has errors.\n");
messageText.append("Please click on red Tree View items for details.\n");
/***/
Hashtable errors = ef.getErrorNodes();
Enumeration keys = errors.keys();
while (keys.hasMoreElements()) {
Node node = (Node)keys.nextElement();
messageText.append("node="+node.getNodeName()
+", error="+((ParseError)errors.get(node)).getMsg()+"\n");
}
}
if (DEBUG) System.out.println("END refreshUI:"+filename);
}
/**
* Invoke the Parser on fname and return the root TreeNode.
*/
public Document getRoot(String filename) {
if (DEBUG) System.out.println("START getRoot:"+filename);
if (filename == null || filename.equals(""))
return null;
try {
//
// Reset the Error Storage and handling
//
ef.resetErrors();
parser.setErrorHandler(ef);
parser.setFeature("http://apache.org/xml/features/dom/defer-node-expansion", false); // otherwise parser.getCurrentNode() == null
parser.setFeature("http://apache.org/xml/features/continue-after-fatal-error", true);
parser.setFeature("http://apache.org/xml/features/allow-java-encodings", true);
parser.parse(filename);
Document document = parser.getDocument();
/***/
return document;
} catch (Exception e) {
System.err.println( "Error: Invalid XML document could not get ROOT" );
System.exit( 1 );
//e.printStackTrace(System.err);
}
return null;
}
/** read the xml file from filename and append it to the JTextArea
*/
synchronized void readXMLFile(String filename, JTextArea ta) {
if (DEBUG) System.out.println("START readXMLFile"+filename);
if (filename == null || filename.equals(""))
return;
InputStream fis = null;
BufferedReader dis = null;
try {
java.net.URL file = createURL(filename);
fis = file.openStream();
String javaEncoding = parser.getJavaEncoding(); // get saved java encoding
try
{
dis = new BufferedReader(new InputStreamReader(fis, javaEncoding ));
}
catch( UnsupportedEncodingException ex )
{
dis = new BufferedReader(new InputStreamReader(fis ));
}
} catch (Exception ex) {
System.err.println("ERROR: Xerces.readXMLFile: "+ex);
return;
}
String line;
int i = 0;
int len = 0;
textLine = new Vector();
String nl = "\n";
int nllen = nl.length();
StringBuffer sb = new StringBuffer();
try{
readline: while ((line = dis.readLine()) != null) {
sb.append(line+nl);
textLine.addElement(new Integer(len));
len += line.length()+nllen;
}
ta.append(sb.toString());
} catch (IOException io) {
System.err.println(io);
return;
}
// relayout because contents have changed
//ta.revalidate();
if (DEBUG) System.out.println("END readXMLFile"+filename);
return;
}
/** called when our JTree's nodes are selected.
*/
void nodeSelected(TreeNode treeNode) {
Node node = m_tree.getNode(treeNode);
if( node == null ) // It is possible to get a null node
return;
StringBuffer sb = new StringBuffer();
messageText.selectAll();
messageText.cut();
//fix
//JTextArea sourceText = sourceText;
Object errorObject = ef == null ? null : ef.getError(node);
if (errorObject != null) {
// There *is* an error in this node.
messageText.setForeground(Color.red);
ParseError eip = (ParseError)errorObject;
sb.append("Error: "+eip.getMsg()+"\n");
int lineNo = eip.getLineNo();
int pos = 0;
int next = 0;
int sizeOfTextLine = textLine.size();
if( lineNo < sizeOfTextLine )
{
pos = ((Integer)textLine.elementAt(lineNo-1)).intValue();
next = (lineNo == sizeOfTextLine ) ?
pos :
(((Integer)textLine.elementAt(lineNo)).intValue());
}
else
{
pos = (( Integer) textLine.elementAt( sizeOfTextLine - 1 )).intValue();
next = pos + 2;
}
sourceText.select(pos, next );
//m_textScrollPane.repaint();
} else {
messageText.setForeground(Color.black);
sourceText.select(0, 0 );
}
//fix
if (node.getNodeType() == Node.ELEMENT_NODE
|| node.getNodeType() == Node.TEXT_NODE
|| node.getNodeType() == Node.CDATA_SECTION_NODE )
{
sb.append(node.toString());
}
messageText.append(sb.toString());
}
/** called when a the text value has changed in the FileNameInput.
* read in new XML file.
*/
public void textValueChanged(TextEvent e) {
try {
if (fni != null)
fni.setVisible(false);
fname = ((JTextField)e.getSource()).getText();
if (DEBUG) System.out.println("textValueChanged:"+fname);
refreshUI(fname);
} catch (Exception ex) {
System.err.println( "Error: while trying to refresh gui" );
System.exit( 1 );
// ex.printStackTrace();
}
}
/** called to handle menu actions.
*/
public void actionPerformed(java.awt.event.ActionEvent e) {
if (DEBUG) System.err.println("ACTION: "+e.getActionCommand()+", "+e.paramString());
if (e.getActionCommand().equals(quitString)) {
System.exit(0);
}
else if (e.getActionCommand().equals(openString)) {
fni = new FileNameInput("Open File");
fni.addTextListener(this);
fni.setVisible(true);
}
else if (e.getActionCommand().equals(expandString)) {
expandTree();
}
else if (e.getActionCommand().equals(collapseString)) {
int rows = m_tree.getRowCount();
for (int i = 0; i < rows; i++) {
m_tree.collapseRow(i);
}
}
else
//if (e.getActionCommand().equals(reloadString)) {
refreshUI(fname);
//}
}
void expandTree() {
int rows = 0;
for (int levels=0; levels <= 4; levels++) {
rows=m_tree.getRowCount();
for (int i = 0; i < rows; i++) {
m_tree.expandRow(i);
}
}
}
/*
* The XMLTreeCellRenderer is an inner class which enables the
* highlighting of errors in the tree and shows the gender values
* as different icons.
*/
class XMLTreeCellRenderer extends DefaultTreeCellRenderer
{
public Component getTreeCellRendererComponent(JTree tree, Object value,
boolean selected, boolean expanded,
boolean leaf, int row,
boolean hasFocus)
{
Node node = ((DOMTree)tree).getNode(value);
Component comp = super.getTreeCellRendererComponent(tree, value,
selected, expanded, leaf, row, hasFocus);
if (selected) {
comp.setBackground(Color.blue);
}
if (ef != null
&& ef.getErrorNodes() != null
&& value != null
&& node != null
&& ef.getErrorNodes().containsKey( node )) {
comp.setForeground(Color.red);
}
if (node != null) {
if (leaf) {
setIcon(new ImageIcon(leafImage));
} else if (expanded) {
setIcon(new ImageIcon(openFolder));
} else {
setIcon(new ImageIcon(closedFolder));
}
}
if (node != null && node instanceof Element) {
Element txNode = (Element)node;
Attr txAtt = (Attr)txNode.getAttributeNode("gender");
if (txAtt != null) {
if (txAtt.getValue().equals("male")) {
setIcon(new ImageIcon("male.gif"));
} else
if (txAtt.getValue().equals("female")) {
setIcon(new ImageIcon("female.gif"));
}
}
}
return comp;
}
}
/*
* The FileNameInput is an inner class which allows the user
* to enter a filename. It exists due to a Swing bug which
* has problems with the real file input panel.
*/
class FileNameInput extends JFrame implements ActionListener {
JLabel fileLabel;
JTextField textField;
JButton ok;
JButton cancel;
Vector textListeners;
public FileNameInput() {
this("");
}
public FileNameInput(String title) {
super(title);
fileLabel = new JLabel("Enter XML file name:");
textField = new JTextField();
textField.addActionListener(this);
ok = new JButton("ok");
cancel = new JButton("cancel");
JPanel buttonPanel = new JPanel();
buttonPanel.add(ok);
buttonPanel.add(cancel);
ok.addActionListener(this);
cancel.addActionListener(this);
getContentPane().add(fileLabel, BorderLayout.NORTH);
getContentPane().add(textField, BorderLayout.CENTER);
getContentPane().add(buttonPanel, BorderLayout.SOUTH);
setSize(400,100);
}
public void actionPerformed(ActionEvent e) {
if (e.getSource() == ok || e.getSource() == textField) {
System.out.println("FileNameInput: pressed OK");
TextEvent event = new TextEvent(textField, TextEvent.TEXT_VALUE_CHANGED);
deliverEvent(event);
setVisible(false);
} else
if (e.getSource() == cancel) {
System.out.println("FileNameInput: pressed cancel");
setVisible(false);
}
}
/**
* Adds a TextListener event listener.
*
* @param listener The listener to add.
*
* @see #removeTextListener
*/
public void addTextListener(TextListener listener) {
// is there anything to do?
if (listener == null)
return;
if (textListeners == null)
textListeners = new Vector();
// add listener
textListeners.addElement(listener);
}
/**
* Removes a TextListener event listener.
*
* @param listener The listener to remove.
*
* @see #addTextListener
*/
public void removeTextListener(TextListener listener) {
// is there anything to do?
if (listener == null || textListeners == null)
return;
// add listener
textListeners.removeElement(listener);
}
/**
* This function delivers TextListener events, when the ok
* button is clicked.
*
* @param evt The event to deliver.
*/
protected void deliverEvent(EventObject evt) {
if (evt instanceof TextEvent) {
TextEvent event = (TextEvent)evt;
Vector l;
synchronized (textListeners) { l = (Vector)textListeners.clone(); }
int size = l.size();
for (int i = 0; i < size; i++)
((TextListener)l.elementAt(i)).textValueChanged(event);
}
}
}
//
// Create a URL object from either a URL string or a plain file name.
//
static URL createURL(String name) throws Exception {
try {
URL u = new URL(name);
return u;
} catch (MalformedURLException ex) {
}
URL u = new URL("file:" + new File(name).getAbsolutePath());
return u;
}
/**
* The ErrorStorer maps Nodes to errors. It receives a reference
* to the ErrorTreeFactory in the Constructor.
*
* When error is called, it asks the
* ErrorTreeFactory for the current node, and uses this as the
* "key" of a Hashtable, with the error as a value. The error
* value is wrapped up nicely in an ParseError object.
*
* It is used in the XML Tutorial to illustrate how to implement
* the ErrorListener to provide error storage for later reference.
*
*/
class ErrorStorer
implements ErrorHandler
{
//
// Data
//
Hashtable errorNodes = null;
/**
* Constructor
*/
public ErrorStorer() {
}
/**
* The client is is allowed to get a reference to the Hashtable,
* and so could corrupt it, or add to it...
*/
public Hashtable getErrorNodes() {
return errorNodes;
}
/**
* The ParseError object for the node key is returned.
* If the node doesn't have errors, null is returned.
*/
public Object getError(Node node) {
if (errorNodes == null)
return null;
return errorNodes.get(node);
}
/**
* Reset the error storage.
*/
public void resetErrors() {
if (errorNodes != null)
errorNodes.clear();
}
/***/
public void warning(SAXParseException ex) {
handleError(ex, WARNING);
}
public void error(SAXParseException ex) {
handleError(ex, ERROR);
}
public void fatalError(SAXParseException ex) throws SAXException {
handleError(ex, FATAL_ERROR);
}
private void handleError(SAXParseException ex, int type) {
System.out.println("!!! handleError: "+ex.getMessage());
StringBuffer errorString = new StringBuffer();
errorString.append("at line number, ");
errorString.append(ex.getLineNumber());
errorString.append(": ");
errorString.append(ex.getMessage());
// Node current = parser.getCurrentNode();
Node current = null ;
try
{
current = ( Node ) parser.getProperty( "http://apache.org/xml/properties/dom/current-element-node" );
}
catch( SAXException exception )
{
;
}
if (current == null) {
System.err.println("Error in handleError. getCurrentNode()==null!");
return;
}
if (errorNodes == null)
errorNodes = new Hashtable();
ParseError previous = (ParseError) errorNodes.get(current);
ParseError eip = null;
// if a Node already has an error, we accumulate the text here...
if (previous != null) {
eip = previous;
errorString = new StringBuffer(previous.getMsg()+"\n"+errorString.toString());
eip.setMsg(errorString.toString());
} else {
eip = new
ParseError(
ex.getSystemId(),
ex.getLineNumber(),
ex.getColumnNumber(),
"",
errorString.toString());
}
// put it in the Hashtable.
errorNodes.put(current, eip);
}
}
/**
* The ParseError class wraps up all the error info from
* the ErrorStorer's error method.
*
* @see ErrorStorer
*/
class ParseError extends Object {
//
// Data
//
String fileName;
int lineNo;
int charOffset;
Object key;
String msg;
/**
* Constructor
*/
public ParseError(String fileName, int lineNo, int charOffset,
Object key,
String msg)
{
this. fileName=fileName;
this. lineNo=lineNo;
this. charOffset=charOffset;
this. key=key;
this. msg=msg;
}
//
// Getters...
//
public String getFileName() { return fileName; }
public int getLineNo() { return lineNo; }
public int getCharOffset() { return charOffset;}
public Object getKey() { return key; }
public String getMsg() { return msg; }
public void setMsg(String s) { msg = s; }
}
//
// Main
//
/** Main program entry point. */
public static void main(String argv[]) {
// vars
int parserNameIndex = -1;
String parserName = DEFAULT_PARSER_NAME;
// check parameters
for (int i = 0; i < argv.length; i++) {
String arg = argv[i];
// options
if (arg.startsWith("-")) {
if (arg.equals("-p")) {
if (i == argv.length - 1) {
System.err.println("error: missing parser class");
System.exit(1);
}
parserName = argv[++i];
parserNameIndex = i;
continue;
}
if (arg.equals("-h")) {
printUsage();
System.exit(1);
}
}
// print uri
System.err.println(arg+':');
JFrame frame = null;
if (parserNameIndex == argv.length-1) {
// null behaviour is blank screen - eg no JTree, or file dispalyed
frame = new TreeView("");
} else {
frame = new TreeView(arg);
}
frame.addWindowListener(new java.awt.event.WindowAdapter() {
public void windowClosing(java.awt.event.WindowEvent e) {
System.exit(0);
}
});
frame.setSize(790, 590);
frame.setVisible(true);
}
} // main(String[])
/** Prints the usage. */
private static void printUsage() {
System.err.println("usage: java ui.TreeViewer (options) uri ...");
System.err.println();
System.err.println("options:");
System.err.println(" -p name Specify DOM parser class by name.");
System.err.println(" Default parser: "+DEFAULT_PARSER_NAME);
System.err.println(" -h This help screen.");
} // printUsage()
} // class TreeViewer