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; } } }