001: package com.ibatis.common.xml;
002:
003: import org.w3c.dom.*;
004: import org.xml.sax.*;
005:
006: import javax.xml.parsers.DocumentBuilder;
007: import javax.xml.parsers.DocumentBuilderFactory;
008: import javax.xml.parsers.FactoryConfigurationError;
009: import javax.xml.parsers.ParserConfigurationException;
010: import java.io.IOException;
011: import java.io.InputStream;
012: import java.io.Reader;
013: import java.util.*;
014:
015: /**
016: * The NodeletParser is a callback based parser similar to SAX. The big
017: * difference is that rather than having a single callback for all nodes,
018: * the NodeletParser has a number of callbacks mapped to
019: * various nodes. The callback is called a Nodelet and it is registered
020: * with the NodeletParser against a specific XPath.
021: */
022: public class NodeletParser {
023:
024: private Map letMap = new HashMap();
025:
026: private boolean validation;
027: private EntityResolver entityResolver;
028:
029: /**
030: * Registers a nodelet for the specified XPath. Current XPaths supported
031: * are:
032: * <ul>
033: * <li> Text Path - /rootElement/childElement/text()
034: * <li> Attribute Path - /rootElement/childElement/@theAttribute
035: * <li> Element Path - /rootElement/childElement/theElement
036: * <li> All Elements Named - //theElement
037: * </ul>
038: */
039: public void addNodelet(String xpath, Nodelet nodelet) {
040: letMap.put(xpath, nodelet);
041: }
042:
043: /**
044: * Begins parsing from the provided Reader.
045: */
046: public void parse(Reader reader) throws NodeletException {
047: try {
048: Document doc = createDocument(reader);
049: parse(doc.getLastChild());
050: } catch (Exception e) {
051: throw new NodeletException("Error parsing XML. Cause: "
052: + e, e);
053: }
054: }
055:
056: public void parse(InputStream inputStream) throws NodeletException {
057: try {
058: Document doc = createDocument(inputStream);
059: parse(doc.getLastChild());
060: } catch (Exception e) {
061: throw new NodeletException("Error parsing XML. Cause: "
062: + e, e);
063: }
064: }
065:
066: /**
067: * Begins parsing from the provided Node.
068: */
069: public void parse(Node node) {
070: Path path = new Path();
071: processNodelet(node, "/");
072: process(node, path);
073: }
074:
075: /**
076: * A recursive method that walkes the DOM tree, registers XPaths and
077: * calls Nodelets registered under those XPaths.
078: */
079: private void process(Node node, Path path) {
080: if (node instanceof Element) {
081: // Element
082: String elementName = node.getNodeName();
083: path.add(elementName);
084: processNodelet(node, path.toString());
085: processNodelet(node, new StringBuffer("//").append(
086: elementName).toString());
087:
088: // Attribute
089: NamedNodeMap attributes = node.getAttributes();
090: int n = attributes.getLength();
091: for (int i = 0; i < n; i++) {
092: Node att = attributes.item(i);
093: String attrName = att.getNodeName();
094: path.add("@" + attrName);
095: processNodelet(att, path.toString());
096: processNodelet(node, new StringBuffer("//@").append(
097: attrName).toString());
098: path.remove();
099: }
100:
101: // Children
102: NodeList children = node.getChildNodes();
103: for (int i = 0; i < children.getLength(); i++) {
104: process(children.item(i), path);
105: }
106: path.add("end()");
107: processNodelet(node, path.toString());
108: path.remove();
109: path.remove();
110: } else if (node instanceof Text) {
111: // Text
112: path.add("text()");
113: processNodelet(node, path.toString());
114: processNodelet(node, "//text()");
115: path.remove();
116: }
117: }
118:
119: private void processNodelet(Node node, String pathString) {
120: Nodelet nodelet = (Nodelet) letMap.get(pathString);
121: if (nodelet != null) {
122: try {
123: nodelet.process(node);
124: } catch (Exception e) {
125: throw new RuntimeException("Error parsing XPath '"
126: + pathString + "'. Cause: " + e, e);
127: }
128: }
129: }
130:
131: /**
132: * Creates a JAXP Document from a reader.
133: */
134: private Document createDocument(Reader reader)
135: throws ParserConfigurationException,
136: FactoryConfigurationError, SAXException, IOException {
137: DocumentBuilderFactory factory = DocumentBuilderFactory
138: .newInstance();
139: factory.setValidating(validation);
140:
141: factory.setNamespaceAware(false);
142: factory.setIgnoringComments(true);
143: factory.setIgnoringElementContentWhitespace(false);
144: factory.setCoalescing(false);
145: factory.setExpandEntityReferences(true);
146:
147: DocumentBuilder builder = factory.newDocumentBuilder();
148: builder.setEntityResolver(entityResolver);
149: builder.setErrorHandler(new ErrorHandler() {
150: public void error(SAXParseException exception)
151: throws SAXException {
152: throw exception;
153: }
154:
155: public void fatalError(SAXParseException exception)
156: throws SAXException {
157: throw exception;
158: }
159:
160: public void warning(SAXParseException exception)
161: throws SAXException {
162: }
163: });
164:
165: return builder.parse(new InputSource(reader));
166: }
167:
168: /**
169: * Creates a JAXP Document from an InoutStream.
170: */
171: private Document createDocument(InputStream inputStream)
172: throws ParserConfigurationException,
173: FactoryConfigurationError, SAXException, IOException {
174: DocumentBuilderFactory factory = DocumentBuilderFactory
175: .newInstance();
176: factory.setValidating(validation);
177:
178: factory.setNamespaceAware(false);
179: factory.setIgnoringComments(true);
180: factory.setIgnoringElementContentWhitespace(false);
181: factory.setCoalescing(false);
182: factory.setExpandEntityReferences(true);
183:
184: DocumentBuilder builder = factory.newDocumentBuilder();
185: builder.setEntityResolver(entityResolver);
186: builder.setErrorHandler(new ErrorHandler() {
187: public void error(SAXParseException exception)
188: throws SAXException {
189: throw exception;
190: }
191:
192: public void fatalError(SAXParseException exception)
193: throws SAXException {
194: throw exception;
195: }
196:
197: public void warning(SAXParseException exception)
198: throws SAXException {
199: }
200: });
201:
202: return builder.parse(new InputSource(inputStream));
203: }
204:
205: public void setValidation(boolean validation) {
206: this .validation = validation;
207: }
208:
209: public void setEntityResolver(EntityResolver resolver) {
210: this .entityResolver = resolver;
211: }
212:
213: /**
214: * Inner helper class that assists with building XPath paths.
215: * <p/>
216: * Note: Currently this is a bit slow and could be optimized.
217: */
218: private static class Path {
219:
220: private List nodeList = new ArrayList();
221:
222: public Path() {
223: }
224:
225: public Path(String path) {
226: StringTokenizer parser = new StringTokenizer(path, "/",
227: false);
228: while (parser.hasMoreTokens()) {
229: nodeList.add(parser.nextToken());
230: }
231: }
232:
233: public void add(String node) {
234: nodeList.add(node);
235: }
236:
237: public void remove() {
238: nodeList.remove(nodeList.size() - 1);
239: }
240:
241: public String toString() {
242: StringBuffer buffer = new StringBuffer("/");
243: for (int i = 0; i < nodeList.size(); i++) {
244: buffer.append(nodeList.get(i));
245: if (i < nodeList.size() - 1) {
246: buffer.append("/");
247: }
248: }
249: return buffer.toString();
250: }
251: }
252:
253: }
|