XPathParser是Mybatis中定义的进行解析XML文件的类,此类用于读取XML文件中的节点文本与属性;本篇我们主要介绍XPathParser解析XML的原理。
这里我们介绍主要的构造方法
- 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方法
- 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属性。
- 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属性。
- 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表达式对应的指定类型的结果。
- public List
evalNodes(String expression) { - return evalNodes(document, expression);
- }
evalNodes方法用于根据XPath表达式获取XNode节点集合,此方法调用了evalNodes的重载方法获取XNode节点集合。
- public List
evalNodes(Object root, String expression) { - List
xnodes = new ArrayList(); - 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表达式对应的指定类型的结果。
- 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
- 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类型的内容数据,而这些方法都是为了得到对应类型的内容数据。
- 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类型的属性值,而这些方法都是为了得到对应类型的属性值。
- public List
getChildren() { - List
children = new ArrayList(); - 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;
- }
- 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;
- }
-
org.mybatis -
mybatis -
3.4.5 -
junit -
junit -
4.13.1 -
test
在src/test/resources下的新建user_info.xml配置文件
- <user id="${id}">
- <name>${name}name>
- <age>20age>
- <birth>
- <year>1999year>
- <month>2month>
- <day>10day>
- birth>
- <educations>
- <education>xx小学education>
- <education>xx中学education>
- <education>xx大学education>
- educations>
- user>
在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("
1 ", 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());
- }
- }
执行单元测试通过