com.publicobject.misc.xml.Parser Maven / Gradle / Ivy
/* Glazed Lists (c) 2003-2006 */
/* http://publicobject.com/glazedlists/ publicobject.com,*/
/* O'Dell Engineering Ltd.*/
package com.publicobject.misc.xml;
import ca.odell.glazedlists.EventList;
import org.xml.sax.*;
import org.xml.sax.helpers.DefaultHandler;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.parsers.SAXParserFactory;
import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.List;
import java.util.ArrayList;
/**
* This XML Parser greatly simplifies the task of parsing an XML document and
* inserting the resulting Java Objects into an {@link EventList}. It is
* architected to be very declarative. The user of the Parser object first
* configures it with pairs of objects: an {@link XMLTagPath} object which
* identifies a location within an XML Document, plus {@link PushProcessor} and
* {@link PopProcessor}s which defines logic capable of processing the data read
* from the Document at that point.
*
* After configuring the Parser with repeated calls to
* {@link #addProcessor(XMLTagPath, PushProcessor)}, a Document can be parsed using
* {@link #parse(InputStream, Object)} and the contents of the
* {@link InputStream} will be inserted into the {@link EventList}.
*
*
Sample usage on a simple XML Document defining a customer might resemble:
*
* Parser parser = new Parser();
*
* // create a new Customer object each time the customer start tag is found
* XMLTagPath customerTag = new XMLTagPath("customer", XMLTagPath.BODY);
* Processor customerStartTag = Processors.createNewObject(Customer.class);
* parser.addProcessor(customerTag.start(), customerStartTag);
*
* // add the new Customer object to the target EventList when the end tag is found
* Processor customerEndTag = Processors.addObjectToTargetList();
* parser.addProcessor(customerTag.end(), customerEndTag);
*
* // populate an EventList of Customers from a stream
* EventList customers = ...
* InputStream source = ...
* parser.parse(customers, source);
*
*
* @author James Lemieux
*/
public class Parser {
/**
* A map from each XMLTagPath that represents a location in an XML Document
* to the Processor that defines the processing logic for that location.
*/
private final Map pushProcessors = new HashMap();
private final Map popProcessors = new HashMap();
/**
* Map the logic defined the in the given processor
to the
* location within an XML stream specified by the given path
.
* Each time this parser encounters the path
within an XML
* stream, the processor
will be invoked.
*
* @param path a unique location within an XML Document specified as a
* path of XML tags from the Document root
* @param processor an object containing the processing logic to execute
* when path
is encountered within an XML Document
*/
public void addProcessor(XMLTagPath path, PushProcessor processor) {
pushProcessors.put(path, processor);
}
public void addProcessor(XMLTagPath path, PopProcessor processor) {
popProcessors.put(path, processor);
}
/**
* Parses the objects in the given source
and inserts them
* into the given target
as they are created. In this way, the
* objects will "stream" into the target
EventList.
*
* @param source the stream containing XML Documents of data
* @param handler the ContentHandler that receives the SAX callbacks
* @throws IOException if an error occurs parsing the source
*/
private static void parse(InputStream source, ContentHandler handler) throws IOException {
try {
// configure a SAX parser
XMLReader xmlReader = SAXParserFactory.newInstance().newSAXParser().getXMLReader();
SaxParserSidekick.install(xmlReader);
xmlReader.setContentHandler(handler);
// parse away
xmlReader.parse(new InputSource(source));
} catch (SAXException e) {
e.printStackTrace();
throw new IOException("Parsing failed " + e.getMessage());
} catch (ParserConfigurationException e) {
e.printStackTrace();
throw new IOException("Parsing failed " + e.getMessage());
}
}
/**
* Parse the objects in the given source
and inserts them into
* the given target
as they are created. In this way, the
* objects will "stream" into the target
EventList.
*
* @param target the {@link EventList} to populate with data
* @param source the stream containing XML Documents of data
* @throws IOException if an error occurs parsing the source
*/
public void parse(InputStream source, T target) throws IOException {
parse(source, new Handler(target));
}
/**
* This Handler receives the callbacks from the SAX parser as tags are
* visited. It performs two duties:
*
*
* - It continually tracks an {@link XMLTagPath} which uniquely
* describes the location within the XML stream. It then executes
* any {@link PushProcessor} associated with that {@link XMLTagPath}.
*
*
- It tracks and stores the String data located between opening
* and closing tags and stores it in a Map context for use when
* processing.
*
*
*/
private class Handler extends DefaultHandler {
/** An Object that describes the current path of open XML tags within the XML stream. */
private XMLTagPath currentTagPath = XMLTagPath.emptyPath();
/** A StringBuffer to collect the raw String data between open and close tags within the XML stream. */
private StringBuffer currentChars = new StringBuffer();
/** the stack of miscellaneous objects being processed */
private List