SAXAdapter.java

/*
 * SAXAdapter
 * 
 * $Id$
 * $HeadURL$
 */
package gov.usgs.util;

import java.io.InputStream;
import java.io.ByteArrayInputStream;
import java.util.LinkedList;

import javax.xml.parsers.SAXParser;
import javax.xml.parsers.SAXParserFactory;

import org.xml.sax.Attributes;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.XMLReader;
import org.xml.sax.helpers.DefaultHandler;


/**
 * SAXAdapter is a sax handler that accumulates element content, which is a
 * common sax handler task.
 * 
 * Users should be cautious because this in some ways removes efficiency gained
 * by handling streaming events, because element content is being buffered. One
 * buffer for each element nesting is maintained, so this works best for shallow
 * documents, whose elements contain little content.
 */
public class SAXAdapter extends DefaultHandler {

    /** Buffers for element content, since it may be delivered in pieces. */
    private LinkedList<StringBuffer> buffers = new LinkedList<StringBuffer>();

    /**
     * SAXAdapter start element handler.
     * 
     * @param uri
     *            element uri.
     * @param localName
     *            element localName.
     * @param qName
     *            element qName.
     * @param attributes
     *            element attributes.
     * @throws SAXException
     *             if there is an error.
     */
    public void onStartElement(final String uri, final String localName,
            final String qName, final Attributes attributes)
            throws SAXException {
    }

    /**
     * SAXAdapter end element handler. Content only includes characters that
     * were read from this element, NOT any characters from child elements.
     * 
     * @param uri
     *            element uri.
     * @param localName
     *            element localName.
     * @param qName
     *            element qName.
     * @param content
     *            element content.
     * @throws SAXException
     *             if onEndElement throws a SAXException.
     */
    public void onEndElement(final String uri, final String localName,
            final String qName, final String content) throws SAXException {
    }

    /**
     * Override DefaultHandler startElement. Adds a new element content buffer
     * and calls onStartElement.
     * 
     * @param uri
     *            element uri.
     * @param localName
     *            element localName.
     * @param qName
     *            element qName.
     * @param attributes
     *            element attributes.
     * @throws SAXException
     *             if onStartElement throws a SAXException.
     */
    public final void startElement(final String uri, final String localName,
            final String qName, final Attributes attributes)
            throws SAXException {
        buffers.add(new StringBuffer());
        onStartElement(uri, localName, qName, attributes);
    }

    /**
     * Override DefaultHandler endElement. Retrieves element content buffer and
     * passes it to onEndElement.
     * 
     * @param uri
     *            element uri.
     * @param localName
     *            element localName.
     * @param qName
     *            element qName.
     * @throws SAXException
     *             if onEndElement throws a SAXException.
     */
    public final void endElement(final String uri, final String localName,
            final String qName) throws SAXException {
        String elementContent = buffers.removeLast().toString();
        onEndElement(uri, localName, qName, elementContent);
    }

    /**
     * Override DefaultHandler characters. Appends content to current element
     * buffer, or skips if before first element.
     * 
     * @param ch
     *            content.
     * @param start
     *            position in content to read.
     * @param length
     *            lenth of content to read.
     * @throws SAXException
     *             never.
     */
    public final void characters(final char[] ch, final int start,
            final int length) throws SAXException {
        if (buffers.size() > 0) {
            buffers.getLast().append(ch, start, length);
        }
    }

    /**
     * Use this handler to parse a string. Wraps string bytes in a
     * ByteArrayInputStream.
     * 
     * @param xml
     *            string containing xml to parse.
     * @return any exception that occurs while parsing, or null if no exceptions
     *         occur.
     */
    public final Exception parse(final String xml) {
        return parse(new ByteArrayInputStream(xml.getBytes()));
    }

    /**
     * Use this handler to parse an input stream. Uses self as a content and
     * error handler in an XMLReader. If an error occurs, use getException to
     * retrieve the error.
     * 
     * @param xml
     *            input stream of xml to parse.
     * @return any exception that occurs while parsing, or null if no exceptions
     *         occur.
     */
    public final Exception parse(InputStream xml) {
        try {
            SAXParserFactory spf = SAXParserFactory.newInstance();
            spf.setNamespaceAware(true);
            SAXParser sp = spf.newSAXParser();
            XMLReader xr = sp.getXMLReader();
            xr.setContentHandler(this);
            xr.setErrorHandler(this);
            xr.parse(new InputSource(xml));
            return null;
        } catch (Exception e) {
            return e;
        } finally {
            StreamUtils.closeStream(xml);
        }
    }

}