解析器模块XPathParser
XPathParser类用来解析xml文件
我们可以看下mybatis工程中附带的XPathParserTest
@Test
void constructorWithInputStreamValidationVariablesEntityResolver() throws Exception {
try (InputStream inputStream = Resources.getResourceAsStream(resource)) {
XPathParser parser = new XPathParser(inputStream, false, null, null);
System.out.println(parser.evalLong("/employee/birth_date/year"));
}
}
其中nodelet_test.xml如下
<employee id="${id_var}">
<blah something="that"/>
<first_name>Jim</first_name>
<last_name>Smith</last_name>
<birth_date>
<year>1970</year>
<month>6</month>
<day>15</day>
</birth_date>
<height units="ft">5.8</height>
<weight units="lbs">200</weight>
<active>true</active>
</employee>
程序的结果输出是1970
下面首先来看下XPathParser中都有哪些属性
public class XPathParser {
// 代表一个文档对象
private final Document document;
// 是否开启校验
private boolean validation;
// 用来加载本地dtd文件
private EntityResolver entityResolver;
// xml中<properties>对应的键值对
private Properties variables;
// 主要属性,用来从xml中根据表达式来获取相应的值
// 相当于用来查询xml的sql
private XPath xpath;
}
EntityResolver
EntityResolver是用来加载本地dtd文件的,通常情况下是联网进行加载,,看下子类XMLMapperEntityResolver的源码
/**
* Offline entity resolver for the MyBatis DTDs.
*
* @author Clinton Begin
* @author Eduardo Macarron
*/
public class XMLMapperEntityResolver implements EntityResolver {
// config.dtd是mybatis-config.xml的dtd文件,mapper.dtd是****Mapper.xml的dtd文件
// dtd文件规定了在xml中能够出现哪些标签,并且标签的取值情况等
private static final String IBATIS_CONFIG_SYSTEM = "ibatis-3-config.dtd";
private static final String IBATIS_MAPPER_SYSTEM = "ibatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_SYSTEM = "mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_SYSTEM = "mybatis-3-mapper.dtd";
private static final String MYBATIS_CONFIG_DTD = "org/apache/ibatis/builder/xml/mybatis-3-config.dtd";
private static final String MYBATIS_MAPPER_DTD = "org/apache/ibatis/builder/xml/mybatis-3-mapper.dtd";
@Override
public InputSource resolveEntity(String publicId, String systemId) throws SAXException {
try {
if (systemId != null) {
String lowerCaseSystemId = systemId.toLowerCase(Locale.ENGLISH);
// 从这里可以看出当需要读取指定的dtd文件时,会读取本地的dtd文件
if (lowerCaseSystemId.contains(MYBATIS_CONFIG_SYSTEM) || lowerCaseSystemId.contains(IBATIS_CONFIG_SYSTEM)) {
return getInputSource(MYBATIS_CONFIG_DTD, publicId, systemId);
} else if (lowerCaseSystemId.contains(MYBATIS_MAPPER_SYSTEM) || lowerCaseSystemId.contains(IBATIS_MAPPER_SYSTEM)) {
return getInputSource(MYBATIS_MAPPER_DTD, publicId, systemId);
}
}
return null;
} catch (Exception e) {
throw new SAXException(e.toString());
}
}
}
private InputSource getInputSource(String path, String publicId, String systemId) {
InputSource source = null;
if (path != null) {
try {
InputStream in = Resources.getResourceAsStream(path);
source = new InputSource(in);
source.setPublicId(publicId);
source.setSystemId(systemId);
} catch (IOException e) {
// ignore, null is ok
}
}
return source;
}
构造函数
public XPathParser(InputStream inputStream) {
commonConstructor(false, null, null);
// 通过传入的输入流或者文件等,创建一个Document对象
this.document = createDocument(new InputSource(inputStream));
}
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();
}
evalString
public String evalString(Object root, String expression) {
// 调用xpath来进行解析
String result = (String) evaluate(expression, root, XPathConstants.STRING);
result = PropertyParser.parse(result, variables);
return result;
}
```
接下来看下PropertyParser的parse方法
```java
public static String parse(String string, Properties variables) {
VariableTokenHandler handler = new VariableTokenHandler(variables);
// 前两个参数分别为openToken和closeToken
GenericTokenParser parser = new GenericTokenParser("${", "}", handler);
return parser.parse(string);
}
```
```java
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
// 定位${
int start = text.indexOf(openToken);
if (start == -1) {
return text;
}
char[] src = text.toCharArray();
// 记录当前解析到的位置
int offset = 0;
// 存放不需要进行变量替换的内容
final StringBuilder builder = new StringBuilder();
StringBuilder expression = null;
while (start > -1) {
if (start > 0 && src[start - 1] == '\\') {
// this open token is escaped. remove the backslash and continue.
// 如果${前面有\,那么将\移除,并且代表当前遇到的${直接显示,不需要进行变量替换
builder.append(src, offset, start - offset - 1).append(openToken);
offset = start + openToken.length();
} else {
// found open token. let's search close token.
// 开始寻找}
// 用来暂存当前需要替换的placeholder的内容
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
// 将${之前未添加到builder中的内容添加进来,并且跳过${
builder.append(src, offset, start - offset);
offset = start + openToken.length();
// 定位}
int end = text.indexOf(closeToken, offset);
while (end > -1) {
if (end > offset && src[end - 1] == '\\') {
// 同样,遇到了\,将\移除,并且当前的}也是直接显示,并不代表替换内容的结束
// this close token is escaped. remove the backslash and continue.
expression.append(src, offset, end - offset - 1).append(closeToken);
offset = end + closeToken.length();
end = text.indexOf(closeToken, offset);
} else {
expression.append(src, offset, end - offset);
break;
}
}
if (end == -1) {
// close token was not found.
// 没有找到匹配的},那么直接将${连带后面的内容全部添加进来,不进行变量替换了
builder.append(src, start, src.length - start);
offset = src.length;
} else {
// 找到了匹配的
// 进行变量替换
builder.append(handler.handleToken(expression.toString()));
offset = end + closeToken.length();
}
}
start = text.indexOf(openToken, offset);
}
if (offset < src.length) {
builder.append(src, offset, src.length - offset);
}
return builder.toString();
}
```
VariableTokenHandler
```java
@Override
public String handleToken(String content) {
if (variables != null) {
String key = content;
// 判断是否需要开启默认值替换
if (enableDefaultValue) {
// 获得当前placeholder和其默认值之间的分隔符,一般情况下默认值和变量之间的分隔符是:
final int separatorIndex = content.indexOf(defaultValueSeparator);
String defaultValue = null;
if (separatorIndex >= 0) {
key = content.substring(0, separatorIndex);
// 将分隔符后面的内容作为默认值
defaultValue = content.substring(separatorIndex + defaultValueSeparator.length());
}
if (defaultValue != null) {
// 判断placeholder是否有对应的配置值,如果没有使用默认值替换
return variables.getProperty(key, defaultValue);
}
}
// 从配置的variables中获取placeholder对应的内容
if (variables.containsKey(key)) {
return variables.getProperty(key);
}
}
// 因为variables为空,无法进行placeholder的替换
return "${" + content + "}";
}
```
看个例子
```java
@Test
void testWithProperties() throws Exception{
try (InputStream inputStream = Resources.getResourceAsStream("resources/student.xml")) {
Properties properties = new Properties();
// 设置配置值
properties.setProperty("name" , "lxl");
XPathParser parser = new XPathParser(inputStream, false, properties, null);
System.out.println(parser.evalString("/student/name/text()"));
}
}
```
```xml
<student>
<name>${name}</name>
</student>
```
输出结果为lxl
看下VariableTokenHandler的构造函数,使用由XPath传入的Properties,来解析是否需要开启默认值和默认值和placeholder之间的分隔符
比如${name:lxl} lxl就是默认值
```java
private VariableTokenHandler(Properties variables) {
this.variables = variables;
this.enableDefaultValue = Boolean.parseBoolean(getPropertyValue(KEY_ENABLE_DEFAULT_VALUE, ENABLE_DEFAULT_VALUE));
this.defaultValueSeparator = getPropertyValue(KEY_DEFAULT_VALUE_SEPARATOR, DEFAULT_VALUE_SEPARATOR);
}
```