| Viewer.java |
/*
* $Id: Viewer.java,v 1.9 2010/09/29 17:21:48 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.common.xml;
import java.awt.Color;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import javax.swing.JTextPane;
import javax.swing.text.BadLocationException;
import javax.swing.text.Style;
import javax.swing.text.StyleConstants;
import org.xins.common.io.IOReader;
import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.helpers.DefaultHandler;
import org.xins.common.MandatoryArgumentChecker;
import org.xins.common.text.ParseException;
/**
* Swing XML Viewer.
* If the text sent is not XML the content is put in the text pane without
* syntax highlighting.
*
* <p>Note: This parser is
* <a href="http://www.w3.org/TR/REC-xml-names/">XML Namespaces</a>-aware.
*
* @version $Revision: 1.9 $ $Date: 2010/09/29 17:21:48 $
*
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
*
* @since XINS 2.1
*/
public class Viewer extends JTextPane {
/**
* Flag to indicate whether the received XML should be indented or left as is.
*/
private boolean indentXML;
/**
* Constructs a new <code>Viewer</code>.
*/
public Viewer() {
indentXML = true;
}
/**
* Parses the specified String to render the XML to the text pane.
*
* @param text
* the XML text to be parsed, not <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>text == null</code>.
*/
public void parse(String text)
throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("text", text);
setText("");
// Write the headers
int nextDecl = 0;
int endDecl = 0;
String decl = text.substring(nextDecl, nextDecl + 2);
while (decl.equals("<?") || decl.equals("<!")) {
endDecl = text.indexOf('>', endDecl);
int middleDecl = text.indexOf('<', endDecl);
if (middleDecl < endDecl) {
endDecl = text.indexOf('>', middleDecl) + 1;
continue;
}
appendText(text.substring(nextDecl, endDecl + 1) + "\n", null);
nextDecl = text.indexOf('<', endDecl);
endDecl = nextDecl;
decl = text.substring(nextDecl, nextDecl + 2);
}
try {
parse(new StringReader(text));
} catch (ParseException pe) {
setText(text);
} catch (IOException ioe) {
setText(text);
}
}
/**
* Parses content of a character stream to create an XML
* <code>Element</code> object.
*
* @param in
* the byte stream that is supposed to contain XML to be parsed,
* not <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>in == null</code>.
*
* @throws IOException
* if there is an I/O error.
*/
public void parse(InputStream in)
throws IllegalArgumentException, IOException {
// Check preconditions
MandatoryArgumentChecker.check("in", in);
String text = IOReader.readFully(in);
parse(text);
}
/**
* Parses content of a character stream to create an XML
* <code>Element</code> object.
*
* @param in
* the character stream that is supposed to contain XML to be parsed,
* not <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>in == null</code>.
*
* @throws IOException
* if there is an I/O error.
*
* @throws ParseException
* if the content of the character stream is not considered to be valid
* XML.
*/
private void parse(Reader in)
throws IllegalArgumentException,
IOException,
ParseException {
// Check preconditions
MandatoryArgumentChecker.check("in", in);
// Wrap the Reader in a SAX InputSource object
InputSource source = new InputSource(in);
// Initialize our SAX event handler
Handler handler = new Handler();
try {
// Let SAX parse the XML, using our handler
SAXParserProvider.get().parse(source, handler);
} catch (SAXException exception) {
String exMessage = exception.getMessage();
// Construct complete message
String message = "Failed to parse XML";
// Throw exception with message, and register cause exception
throw new ParseException(message, exception, exMessage);
}
}
/**
* Indicates whether to indent the XML or leave it as received.
*
* @param indentXML
* <code>true</code> if the XML should be indented, <code>false</code> otherwise.
*/
public void setIndentation(boolean indentXML) {
this.indentXML = indentXML;
}
/**
* Append text at the end of the document.
*
* @param text
* the text to append, cannot be <code>null</code>.
*
* @param style
* the style in which the text should appear, can be <code>null</code>
*/
public void appendText(String text, Style style) {
try {
getDocument().insertString(getDocument().getLength(), text, style);
} catch (BadLocationException ble) {
}
}
/**
* SAX event handler that will parse XML.
*
* @version $Revision: 1.9 $ $Date: 2010/09/29 17:21:48 $
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*/
private class Handler extends DefaultHandler {
/**
* The styles used for the syntax highlighting.
*/
private Style elementStyle;
private Style attrNameStyle;
private Style attrValueStyle;
private Style contentStyle;
/**
* The level for the element pointer within the XML document. Initially
* this field is <code>-1</code>, which indicates the current element
* pointer is outside the document. The value <code>0</code> is for the
* root element (<code>result</code>), etc.
*/
private int _level;
/**
* Flag indicating whether the current element sub-elements (<code>true</code>)
* or not (<code>false</code>).
*/
private boolean _hasSubElement;
/**
* Constructs a new <code>Handler</code> instance.
*/
private Handler() {
_level = -1;
_hasSubElement = false;
// Define the style needed
elementStyle = addStyle("Element", null);
StyleConstants.setForeground(elementStyle, Color.BLUE.darker());
attrNameStyle = addStyle("AttrName", null);
StyleConstants.setForeground(attrNameStyle, Color.GREEN.darker());
attrValueStyle = addStyle("AttrValue", null);
StyleConstants.setForeground(attrValueStyle, Color.RED.darker());
contentStyle = addStyle("Content", null);
StyleConstants.setForeground(contentStyle, Color.BLACK);
}
/**
* Receive notification of the beginning of an element.
*
* @param namespaceURI
* the namespace URI, can be <code>null</code>.
*
* @param localName
* the local name (without prefix); cannot be <code>null</code>.
*
* @param qName
* the qualified name (with prefix), can be <code>null</code> since
* <code>namespaceURI</code> and <code>localName</code> are always
* used instead.
*
* @param atts
* the attributes attached to the element; if there are no
* attributes, it shall be an empty {@link Attributes} object; cannot
* be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>localName == null || atts == null</code>.
*
* @throws SAXException
* if the parsing failed.
*/
public void startElement(String namespaceURI,
String localName,
String qName,
Attributes atts)
throws IllegalArgumentException, SAXException {
// Make sure namespaceURI is either null or non-empty
namespaceURI = "".equals(namespaceURI) ? null : namespaceURI;
// Increase the element depth level
_level++;
_hasSubElement = false;
indent();
appendText("<", null);
appendText(qName, elementStyle);
// Find the namespace prefix
String prefixNS = null;
if (qName != null && qName.indexOf(':') != -1) {
prefixNS = "xmlns:" + qName.substring(0, qName.indexOf(':'));
} else {
prefixNS = "xmlns";
}
if (namespaceURI != null) {
appendText(" " + prefixNS, attrNameStyle);
appendText("=\"", null);
appendText(namespaceURI, attrValueStyle);
appendText("\"", null);
}
// Add all attributes
for (int i = 0; i < atts.getLength(); i++) {
String attrValue = atts.getValue(i);
String attrQName = atts.getQName(i);
appendText(" " + attrQName, attrNameStyle);
appendText("=\"", null);
appendText(attrValue, attrValueStyle);
appendText("\"", null);
}
appendText(">", null);
}
/**
* Receive notification of the end of an element.
*
* @param namespaceURI
* the namespace URI, can be <code>null</code>.
*
* @param localName
* the local name (without prefix); cannot be <code>null</code>.
*
* @param qName
* the qualified name (with prefix), can be <code>null</code> since
* <code>namespaceURI</code> and <code>localName</code> are only
* used.
*
* @throws IllegalArgumentException
* if <code>localName == null</code>.
*/
public void endElement(String namespaceURI,
String localName,
String qName)
throws IllegalArgumentException {
if (_hasSubElement) {
indent();
}
appendText("</", null);
appendText(qName, elementStyle);
appendText(">", null);
_level--;
_hasSubElement = true;
}
/**
* Receive notification of character data.
*
* @param ch
* the <code>char</code> array that contains the characters from the
* XML document, cannot be <code>null</code>.
*
* @param start
* the start index within <code>ch</code>.
*
* @param length
* the number of characters to take from <code>ch</code>.
*
* @throws IndexOutOfBoundsException
* if characters outside the allowed range are specified.
*
* @throws SAXException
* if the parsing failed.
*/
public void characters(char[] ch, int start, int length)
throws IndexOutOfBoundsException, SAXException {
appendText(new String(ch, start, length), contentStyle);
}
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new ByteArrayInputStream(new byte[0]));
}
/**
* Indent if needed.
*/
private void indent() {
if (indentXML) {
String indentation = "\n";
for (int i = 0; i < _level; i++) {
indentation += "\t";
}
appendText(indentation, null);
}
}
}
}