| ElementParser.java |
/*
* $Id: ElementParser.java,v 1.46 2010/09/29 20:04:51 agoubard Exp $
*
* See the COPYRIGHT file for redistribution and use restrictions.
*/
package org.xins.common.xml;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.io.StringReader;
import java.util.Stack;
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.Utils;
import org.xins.common.text.ParseException;
import org.xins.common.text.TextUtils;
/**
* XML element parser. XML is parsed to produce {@link Element} objects.
*
* <p>Note: This parser is
* <a href="http://www.w3.org/TR/REC-xml-names/">XML Namespaces</a>-aware.
*
* @version $Revision: 1.46 $ $Date: 2010/09/29 20:04:51 $
*
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.1.0
* @deprecated since XINS 3.0. Use {@link org.w3c.dom.Element}
*/
@Deprecated
public class ElementParser {
/**
* Error state for the SAX event handler.
*/
private static final State ERROR = new State("ERROR");
/**
* State for the SAX event handler in the data section (at any depth within
* the <code>data</code> element).
*/
private static final State PARSING = new State("PARSING");
/**
* State for the SAX event handler for the final state, when parsing is
* finished.
*/
private static final State FINISHED = new State("FINISHED");
/**
* Constructs a new <code>ElementParser</code>.
*/
public ElementParser() {
// empty
}
/**
* Parses the specified String to create an XML <code>Element</code> object.
*
* @param text
* the XML text to be parsed, not <code>null</code>.
*
* @return
* the parsed result, not <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>text == null</code>.
*
* @throws ParseException
* if the content of the character stream is not considered to be valid XML.
*
* @since XINS 2.0
*/
public Element parse(String text)
throws IllegalArgumentException,
ParseException {
// Check preconditions
MandatoryArgumentChecker.check("text", text);
try {
return parse(new StringReader(text));
} catch (IOException ioe) {
throw Utils.logProgrammingError(ioe);
}
}
/**
* 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>.
*
* @return
* the parsed result, 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.
*
* @since XINS 2.0
*/
public Element parse(InputStream in)
throws IllegalArgumentException,
IOException,
ParseException {
// Check preconditions
MandatoryArgumentChecker.check("in", in);
// Wrap the Reader in a SAX InputSource object
InputSource source = new InputSource(in);
return parse(source);
}
/**
* 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>.
*
* @return
* the parsed result, 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.
*/
public Element 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);
return parse(source);
}
/**
* Parses content of a file to create an XML <code>Element</code> object.
*
* @param file
* the file that is supposed to contain XML to be parsed,
* not <code>null</code>.
*
* @return
* the parsed result, not <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>file == null</code>.
*
* @throws IOException
* if there is an I/O error, e.g. the file does not exist or is actually
* a directory.
*
* @throws ParseException
* if the content of the file is not considered to be valid XML.
*
* @since XINS 2.2
*/
public Element parse(File file)
throws IllegalArgumentException,
IOException,
ParseException {
// Check preconditions
MandatoryArgumentChecker.check("file", file);
FileInputStream fis = null;
try {
fis = new FileInputStream(file);
return parse(fis);
} finally {
try {
if (fis != null) {
fis.close();
}
} catch (IOException ex) {
// Never mind
}
}
}
/**
* Parses content of a character stream to create an XML
* <code>Element</code> object.
*
* @param source
* the input source that is supposed to contain XML to be parsed,
* not <code>null</code>.
*
* @return
* the parsed result, not <code>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 Element parse(InputSource source) throws IOException, ParseException {
// TODO: Consider using an XMLReader instead of a SAXParser
// 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) {
// TODO: Log: Parsing failed
String exMessage = exception.getMessage();
// Construct complete message
String message = "Failed to parse XML";
if (TextUtils.isEmpty(exMessage)) {
message += '.';
} else {
message += ": " + exMessage;
}
// Throw exception with message, and register cause exception
throw new ParseException(message, exception, exMessage);
}
Element element = handler.getElement();
return element;
}
/**
* SAX event handler that will parse XML.
*
* @version $Revision: 1.46 $ $Date: 2010/09/29 20:04:51 $
* @author <a href="mailto:anthony.goubard@japplis.com">Anthony Goubard</a>
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*/
private static class Handler extends DefaultHandler {
/**
* The current state. Never <code>null</code>.
*/
private State _state;
/**
* The element resulting of the parsing.
*/
private Element _element;
/**
* The character content (CDATA or PCDATA) of the element currently
* being parsed.
*/
private StringBuffer _characters;
/**
* The stack of child elements within the data section. The top element
* is always <code><data/></code>.
*/
private Stack _dataElementStack;
/**
* 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;
/**
* Constructs a new <code>Handler</code> instance.
*/
private Handler() {
_state = PARSING;
_level = -1;
_characters = new StringBuffer(145);
_dataElementStack = new Stack();
}
/**
* 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 {
// Temporarily enter ERROR state, on success this state is left
State currentState = _state;
_state = ERROR;
// Make sure namespaceURI is either null or non-empty
namespaceURI = "".equals(namespaceURI) ? null : namespaceURI;
// Check preconditions
MandatoryArgumentChecker.check("localName", localName, "atts", atts);
// Increase the element depth level
_level++;
if (currentState == ERROR) {
String detail = "Unexpected state "
+ currentState
+ " (level=" + _level + ')';
throw Utils.logProgrammingError(detail);
} else {
// Find the namespace prefix
String prefix = null;
if (qName != null && qName.indexOf(':') != -1) {
prefix = qName.substring(0, qName.indexOf(':'));
}
// Construct a Element
Element element = new Element(prefix, namespaceURI, localName);
// Add all attributes
for (int i = 0; i < atts.getLength(); i++) {
String attrNamespaceURI = atts.getURI(i);
String attrLocalName = atts.getLocalName(i);
String attrValue = atts.getValue(i);
String attrQName = atts.getQName(i);
String attrPrefix = null;
if (attrQName != null && attrQName.indexOf(':') != -1) {
attrPrefix = attrQName.substring(0, attrQName.indexOf(':'));
}
element.setAttribute(attrPrefix, attrNamespaceURI, attrLocalName, attrValue);
}
// Push the element on the stack
_dataElementStack.push(element);
// Reserve buffer for PCDATA
_characters = new StringBuffer(145);
// Reset the state from ERROR back to PARSING
_state = PARSING;
}
}
/**
* 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 {
// Temporarily enter ERROR state, on success this state is left
State currentState = _state;
_state = ERROR;
// Check preconditions
MandatoryArgumentChecker.check("localName", localName);
if (currentState == ERROR) {
String detail = "Unexpected state " + currentState + " (level=" + _level + ')';
throw Utils.logProgrammingError(detail);
// Within data section
} else {
// Get the Element for which we process the end tag
Element child = (Element) _dataElementStack.pop();
// Set the PCDATA content on the element
if (_characters.length() > 0) {
child.setText(_characters.toString());
}
// Add the child to the parent
if (_dataElementStack.size() > 0) {
Element parent = (Element) _dataElementStack.peek();
parent.addChild(child);
// Reset the state back from ERROR to PARSING
_state = PARSING;
} else {
_element = child;
_state = FINISHED;
}
}
_level--;
_characters = new StringBuffer(145);
}
/**
* 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 {
// Temporarily enter ERROR state, on success this state is left
State currentState = _state;
_state = ERROR;
_characters.append(ch, start, length);
// Reset _state
_state = currentState;
}
/**
* Gets the parsed element.
*
* @return
* the element resulting of the parsing of the XML.
*/
Element getElement() {
// Check state
if (_state != FINISHED) {
String detail = "State is " + _state + " instead of " + FINISHED;
throw Utils.logProgrammingError(detail);
}
return _element;
}
public InputSource resolveEntity(String publicId, String systemId) {
return new InputSource(new ByteArrayInputStream(new byte[0]));
}
}
/**
* State of the event handler.
*
* @version $Revision: 1.46 $ $Date: 2010/09/29 20:04:51 $
* @author <a href="mailto:ernst@ernstdehaan.com">Ernst de Haan</a>
*
* @since XINS 1.0.0
*/
private static final class State {
/**
* Constructs a new <code>State</code> object.
*
* @param name
* the name of this state, cannot be <code>null</code>.
*
* @throws IllegalArgumentException
* if <code>name == null</code>.
*/
State(String name) throws IllegalArgumentException {
// Check preconditions
MandatoryArgumentChecker.check("name", name);
_name = name;
}
/**
* The name of this state. Cannot be <code>null</code>.
*/
private final String _name;
/**
* Returns the name of this state.
*
* @return
* the name of this state, cannot be <code>null</code>.
*/
public String getName() {
return _name;
}
/**
* Returns a textual representation of this object.
*
* @return
* the name of this state, never <code>null</code>.
*/
public String toString() {
return _name;
}
}
}