XPathParser是Mybatis中定义的进行解析XML文件的类,此类用于读取XML文件中的节点文本与属性;本篇我们主要介绍XPathParser解析XML的原理。
一、XPathParser构造方法
这里我们介绍主要的构造方法
public XPathParser(InputStream inputStream, boolean validation, Properties variables, EntityResolver entityResolver) {
commonConstructor(validation, variables, entityResolver);
this.document = createDocument(new InputSource(inputStream));
}
此构造方法有四个参数:
inputStream:XML文件输入流
validation:是否验证
variables:XML文件中的变量属性集
entityResolver:实体解析器
此构造方法中主要执行了commonConstrutor、createDocument方法
1、commonConstructor
private void commonConstructor(boolean validation, Properties variables, EntityResolver entityResolver) {
this.validation = validation;
this.entityResolver = entityResolver;
this.variables = variables;
XPathFactory factory = XPathFactory.newInstance();
this.xpath = factory.newXPath();
}
此方法中主要是将参数赋值给对象的属性,并使用XPathFactory工厂创建XPath对象赋值给对象的xpath属性。
2、createDocument
private Document createDocument(InputSource inputSource) {
// important: this must only be called AFTER common constructor
try {
DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
factory.setValidating(validation);
factory.setNamespaceAware(false);
factory.setIgnoringComments(true);
factory.setIgnoringElementContentWhitespace(false);
factory.setCoalescing(false);
factory.setExpandEntityReferences(true);
DocumentBuilder builder = factory.newDocumentBuilder();
builder.setEntityResolver(entityResolver);
builder.setErrorHandler(new ErrorHandler() {
@Override
public void error(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void fatalError(SAXParseException exception) throws SAXException {
throw exception;
}
@Override
public void warning(SAXParseException exception) throws SAXException {
}
});
return builder.parse(inputSource);
} catch (Exception e) {
throw new BuilderException("Error creating document instance. Cause: " + e, e);
}
}
此方法中使用DocumentBuilderFactory工厂创建了DoucumentBuilder对象,通过DocumentBuilder对象将XML文件流转换成Document对象赋值给对象的document属性。
二、获取XNode节点
public XNode evalNode(String expression) {
return evalNode(document, expression);
}
evalNode方法用于根据XPath表达式获取XNode节点对象,此方法调用了evalNode的重载方法获取XNode节点对象。
public XNode evalNode(Object root, String expression) {
Node node = (Node) evaluate(expression, root, XPathConstants.NODE);
if (node == null) {
return null;
}
return new XNode(this, node, variables);
}
此方法用于从root参数对象中根据XPath表达式获取XNode节点对象,方法中先使用evaluate方法获取Node节点对象,然后使用Node节点对象创建XNode节点对象并返回。
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
evaluate方法中使用xpath对象在root参数对象中查询XPath表达式对应的指定类型的结果。
三、获取XNode节点列表
public List<XNode> evalNodes(String expression) {
return evalNodes(document, expression);
}
evalNodes方法用于根据XPath表达式获取XNode节点集合,此方法调用了evalNodes的重载方法获取XNode节点集合。
public List<XNode> evalNodes(Object root, String expression) {
List<XNode> xnodes = new ArrayList<XNode>();
NodeList nodes = (NodeList) evaluate(expression, root, XPathConstants.NODESET);
for (int i = 0; i < nodes.getLength(); i++) {
xnodes.add(new XNode(this, nodes.item(i), variables));
}
return xnodes;
}
此方法用于从root参数对象中根据XPath表达式获取XNode节点集合,方法中先使用evaluate方法获取Node节点集合,然后遍历Node节点创建XNode节点放入XNode节点集合中,最终返回XNode节点集合。
private Object evaluate(String expression, Object root, QName returnType) {
try {
return xpath.evaluate(expression, root, returnType);
} catch (Exception e) {
throw new BuilderException("Error evaluating XPath. Cause: " + e, e);
}
}
evaluate方法中使用xpath对象在root参数对象中查询XPath表达式对应的指定类型的结果。
四、XNode节点类
1、构造方法
public XNode(XPathParser xpathParser, Node node, Properties variables) {
this.xpathParser = xpathParser;
this.node = node;
this.name = node.getNodeName();
this.variables = variables;
this.attributes = parseAttributes(node);
this.body = parseBody(node);
}
从构造方法中,可以看出XNode节点对象创建完成后,XNode中包含了XPathParser对象引用、Node节点对象引用、variable变量属性集引用,同时初始化了Node节点的属性、Node节点的内容数据。
构造方法中使用parseAttributes转换了Node节点的属性,使用parseBody转换了Node节点的内容数据。
下面我们分别看一下两个方法:
1)parseAttributes
private Properties parseAttributes(Node n) {
Properties attributes = new Properties();
NamedNodeMap attributeNodes = n.getAttributes();
if (attributeNodes != null) {
for (int i = 0; i < attributeNodes.getLength(); i++) {
Node attribute = attributeNodes.item(i);
String value = PropertyParser.parse(attribute.getNodeValue(), variables);
attributes.put(attribute.getNodeName(), value);
}
}
return attributes;
}
方法中先获取到Node的属性节点对象列表,再遍历属性节点对象获取到属性值,并使用PropertyParser类中的parse方法对属性值中的变量进行转换,最终将属性名称与属性值存储到XNode节点对象的attributes属性中。
2)parseBody
private String parseBody(Node node) {
String data = getBodyData(node);
if (data == null) {
NodeList children = node.getChildNodes();
for (int i = 0; i < children.getLength(); i++) {
Node child = children.item(i);
data = getBodyData(child);
if (data != null) {
break;
}
}
}
return data;
}
private String getBodyData(Node child) {
if (child.getNodeType() == Node.CDATA_SECTION_NODE
|| child.getNodeType() == Node.TEXT_NODE) {
String data = ((CharacterData) child).getData();
data = PropertyParser.parse(data, variables);
return data;
}
return null;
}
调用getBodyData获取Node节点的内容数据,如果获取到的内容数据不为null,则返回内容数据;获取的内容数据为null,则获取Node节点的子节点列表,遍历子节点列表,依次调用getBodyData获取子节点的内容数据,直到获取到不为null的内容数据后返回。
getBodyData方法:获取节点内容数据,判断Node节点如果是文本节点或者CDATA节点,则直接获取节点的内容数据,再使用PropertyParser类中的parse方法对内容数据中的变量进行转换并返回,否则返回null
2、获取内容数据
public String getStringBody(String def) {
if (body == null) {
return def;
} else {
return body;
}
}
方法中判断内容数据如果为空,则返回默认值数据(def),否则返回内容数据。
另外,此类方法还有很多,比如:
public Boolean getBooleanBody(Boolean def) {
if (body == null) {
return def;
} else {
return Boolean.valueOf(body);
}
}
public Integer getIntBody(Integer def) {
if (body == null) {
return def;
} else {
return Integer.parseInt(body);
}
}
public Long getLongBody(Long def) {
if (body == null) {
return def;
} else {
return Long.parseLong(body);
}
}
public Double getDoubleBody(Double def) {
if (body == null) {
return def;
} else {
return Double.parseDouble(body);
}
}
public Float getFloatBody(Float def) {
if (body == null) {
return def;
} else {
return Float.parseFloat(body);
}
}
与getStringBody不同的是,getStringBody是为了得到String类型的内容数据,而这些方法都是为了得到对应类型的内容数据。
3、获取属性值
public String getStringAttribute(String name, String def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return value;
}
}
方法中根据属性名称获取属性值,判断属性值如果为空,则返回默认值属性值(def),否则返回获取到的属性值。
另外,此类方法还有很多,比如:
public Boolean getBooleanAttribute(String name, Boolean def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return Boolean.valueOf(value);
}
}
public Integer getIntAttribute(String name, Integer def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return Integer.parseInt(value);
}
}
public Long getLongAttribute(String name, Long def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return Long.parseLong(value);
}
}
public Double getDoubleAttribute(String name, Double def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return Double.parseDouble(value);
}
}
public Float getFloatAttribute(String name, Float def) {
String value = attributes.getProperty(name);
if (value == null) {
return def;
} else {
return Float.parseFloat(value);
}
}
与getStringAttribute不同的是,getStringAttribute是为了得到String类型的属性值,而这些方法都是为了得到对应类型的属性值。
4、获取子节点列表
public List<XNode> getChildren() {
List<XNode> children = new ArrayList<XNode>();
NodeList nodeList = node.getChildNodes();
if (nodeList != null) {
for (int i = 0, n = nodeList.getLength(); i < n; i++) {
Node node = nodeList.item(i);
if (node.getNodeType() == Node.ELEMENT_NODE) {
children.add(new XNode(xpathParser, node, variables));
}
}
}
return children;
}
- 获取Node节点的所有子节点
- 遍历子节点创建了XNode节点对象并放入children集合中
- 返回children集合
5、获取子节点属性集
public Properties getChildrenAsProperties() {
Properties properties = new Properties();
for (XNode child : getChildren()) {
String name = child.getStringAttribute("name");
String value = child.getStringAttribute("value");
if (name != null && value != null) {
properties.setProperty(name, value);
}
}
return properties;
}
- 调用getChildren获取到了XNode节点集合
- 遍历XNode节点,分别获取节点中的name、value属性对应的属性值name、value,如果name、value都存在,则将name、value作为键值对放到属性集中
- 返回属性集
五、XPathParser的使用
1、引入依赖
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.5</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.1</version>
<scope>test</scope>
</dependency>
2、准备XML文件
在src/test/resources下的新建user_info.xml配置文件
<user id="${id}">
<name>${name}</name>
<age>20</age>
<birth>
<year>1999</year>
<month>2</month>
<day>10</day>
</birth>
<educations>
<education>xx小学</education>
<education>xx中学</education>
<education>xx大学</education>
</educations>
</user>
3、解析XML文件
在src/test/java下的cn.horse.demo.parser包下新建XPathParserTest测试类
package cn.horse.demo.parser;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.parsing.XPathParser;
import org.junit.Assert;
import org.junit.Test;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class XPathParserTest {
@Test
public void testXPathParserMethods() throws IOException {
InputStream inputStream = Resources.getResourceAsStream("user_info.xml");
Properties variables = new Properties();
variables.put("id", "1");
variables.put("name", "张三");
XPathParser parser = new XPathParser(inputStream, false, variables, null);
Assert.assertEquals("1", parser.evalNode("/user").getStringAttribute("id"));
Assert.assertEquals("1", parser.evalNode("/user/@id").getStringBody());
Assert.assertEquals("<id>1</id>", parser.evalNode("/user/@id").toString().trim());
Assert.assertEquals("张三", parser.evalNode("/user/name").getStringBody());
Assert.assertEquals((Long) 1999L, parser.evalNode("/user/birth/year").getLongBody());
Assert.assertEquals((Long) 2L, parser.evalNode("/user/birth/month").getLongBody());
Assert.assertEquals((Long) 10L, parser.evalNode("/user/birth/day").getLongBody());
Assert.assertEquals(4, parser.evalNodes("/user/*").size());
Assert.assertEquals(3, parser.evalNodes("/user/educations/education").size());
}
}
执行单元测试通过