
com.ibatis.common.xml.NodeletParser Maven / Gradle / Ivy
Show all versions of mybatis2 Show documentation
/*
* Copyright 2004-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.ibatis.common.xml;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.util.*;
import javax.xml.XMLConstants;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.FactoryConfigurationError;
import javax.xml.parsers.ParserConfigurationException;
import org.w3c.dom.*;
import org.xml.sax.*;
/**
* The NodeletParser is a callback based parser similar to SAX. The big difference is that rather than having a single
* callback for all nodes, the NodeletParser has a number of callbacks mapped to various nodes. The callback is called a
* Nodelet and it is registered with the NodeletParser against a specific XPath.
*/
public class NodeletParser {
/** The let map. */
private Map letMap = new HashMap();
/** The validation. */
private boolean validation;
/** The entity resolver. */
private EntityResolver entityResolver;
/**
* Registers a nodelet for the specified XPath. Current XPaths supported are:
*
* - Text Path - /rootElement/childElement/text()
*
- Attribute Path - /rootElement/childElement/@theAttribute
*
- Element Path - /rootElement/childElement/theElement
*
- All Elements Named - //theElement
*
*
* @param xpath
* the xpath
* @param nodelet
* the nodelet
*/
public void addNodelet(String xpath, Nodelet nodelet) {
letMap.put(xpath, nodelet);
}
/**
* Begins parsing from the provided Reader.
*
* @param reader
* the reader
*
* @throws NodeletException
* the nodelet exception
*/
public void parse(Reader reader) throws NodeletException {
try {
Document doc = createDocument(reader);
parse(doc.getLastChild());
} catch (Exception e) {
throw new NodeletException("Error parsing XML. Cause: " + e, e);
}
}
/**
* Parses the.
*
* @param inputStream
* the input stream
*
* @throws NodeletException
* the nodelet exception
*/
public void parse(InputStream inputStream) throws NodeletException {
try {
Document doc = createDocument(inputStream);
parse(doc.getLastChild());
} catch (Exception e) {
throw new NodeletException("Error parsing XML. Cause: " + e, e);
}
}
/**
* Begins parsing from the provided Node.
*
* @param node
* the node
*/
public void parse(Node node) {
Path path = new Path();
processNodelet(node, "/");
process(node, path);
}
/**
* A recursive method that walkes the DOM tree, registers XPaths and calls Nodelets registered under those XPaths.
*
* @param node
* the node
* @param path
* the path
*/
private void process(Node node, Path path) {
if (node instanceof Element) {
// Element
String elementName = node.getNodeName();
path.add(elementName);
processNodelet(node, path.toString());
processNodelet(node, new StringBuilder("//").append(elementName).toString());
// Attribute
NamedNodeMap attributes = node.getAttributes();
int n = attributes.getLength();
for (int i = 0; i < n; i++) {
Node att = attributes.item(i);
String attrName = att.getNodeName();
path.add("@" + attrName);
processNodelet(att, path.toString());
processNodelet(node, new StringBuilder("//@").append(attrName).toString());
path.remove();
}
// Children
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
process(children.item(i), path);
}
path.add("end()");
processNodelet(node, path.toString());
path.remove();
path.remove();
} else if (node instanceof Text) {
// Text
path.add("text()");
processNodelet(node, path.toString());
processNodelet(node, "//text()");
path.remove();
}
}
/**
* Process nodelet.
*
* @param node
* the node
* @param pathString
* the path string
*/
private void processNodelet(Node node, String pathString) {
Nodelet nodelet = (Nodelet) letMap.get(pathString);
if (nodelet != null) {
try {
nodelet.process(node);
} catch (Exception e) {
throw new RuntimeException("Error parsing XPath '" + pathString + "'. Cause: " + e, e);
}
}
}
/**
* Creates a JAXP Document from a reader.
*
* @param reader
* the reader
*
* @return the document
*
* @throws ParserConfigurationException
* the parser configuration exception
* @throws FactoryConfigurationError
* the factory configuration error
* @throws SAXException
* the SAX exception
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private Document createDocument(Reader reader)
throws ParserConfigurationException, FactoryConfigurationError, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(new InputSource(reader));
}
/**
* Creates a JAXP Document from an InoutStream.
*
* @param inputStream
* the input stream
*
* @return the document
*
* @throws ParserConfigurationException
* the parser configuration exception
* @throws FactoryConfigurationError
* the factory configuration error
* @throws SAXException
* the SAX exception
* @throws IOException
* Signals that an I/O exception has occurred.
*/
private Document createDocument(InputStream inputStream)
throws ParserConfigurationException, FactoryConfigurationError, SAXException, IOException {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setFeature(XMLConstants.FEATURE_SECURE_PROCESSING, true);
factory.setFeature("http://apache.org/xml/features/disallow-doctype-decl", true);
factory.setFeature("http://xml.org/sax/features/external-general-entities", false);
factory.setFeature("http://xml.org/sax/features/external-parameter-entities", false);
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_DTD, "");
factory.setAttribute(XMLConstants.ACCESS_EXTERNAL_SCHEMA, "");
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(false);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(new InputSource(inputStream));
}
/**
* Sets the validation.
*
* @param validation
* the new validation
*/
public void setValidation(boolean validation) {
this.validation = validation;
}
/**
* Sets the entity resolver.
*
* @param resolver
* the new entity resolver
*/
public void setEntityResolver(EntityResolver resolver) {
this.entityResolver = resolver;
}
/**
* Inner helper class that assists with building XPath paths.
*
* Note: Currently this is a bit slow and could be optimized.
*/
private static class Path {
/** The node list. */
private List nodeList = new ArrayList();
/**
* Instantiates a new path.
*/
public Path() {
}
/**
* Instantiates a new path.
*
* @param path
* the path
*/
public Path(String path) {
StringTokenizer parser = new StringTokenizer(path, "/", false);
while (parser.hasMoreTokens()) {
nodeList.add(parser.nextToken());
}
}
/**
* Adds the.
*
* @param node
* the node
*/
public void add(String node) {
nodeList.add(node);
}
/**
* Removes the.
*/
public void remove() {
nodeList.remove(nodeList.size() - 1);
}
@Override
public String toString() {
StringBuilder builder = new StringBuilder("/");
for (int i = 0; i < nodeList.size(); i++) {
builder.append(nodeList.get(i));
if (i < nodeList.size() - 1) {
builder.append("/");
}
}
return builder.toString();
}
}
}