Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser)
Java 模板变量替换——字符串替换器(思路Mybatis的GenericTokenParser)
- 思路
- 字符串替换器
思路
模板变量替换无非是寻找出字符串(模板)中的特殊标记,用对应的变量进行字符串替换。
提到变量替换,大家第一能联想到的可能是Mybatis的动态SQL语句的解析,又或者是mybatis.xml配置文件中用于解析"${username:root}"的默认值。
在Mybatis的源码中,这些功能则是通过GenericTokenParser类来实现的,通过查看源码可以很明显的知道GenericTokenParser只是查找指定的占位符,而具体的解析行为会根据其持有的TokenHandler实现的不同而有所不同,这里是采用了策略模式。
那么接下来我们就使用Mybatis的思路,试着写一个字符串替换器吧
字符串替换器
GenericTokenParser
public class GenericTokenParser {
private final String openToken;
private final String closeToken;
private final TokenHandler handler;
public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
this.openToken = openToken;
this.closeToken = closeToken;
this.handler = handler;
}
public String parse(String text) {
if (text == null || text.isEmpty()) {
return "";
}
// search open token
int start = text.indexOf(openToken, 0);
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.
if (expression == null) {
expression = new StringBuilder();
} else {
expression.setLength(0);
}
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);
offset = end + closeToken.length();
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();
}
}
TokenHandler
public interface TokenHandler {
String handleToken(String content);
}
以上代码是Mybatis的源码,可以直接抄,接下来编写核心的逻辑:
这里的数据使用Map<String,Object>来存储,key是变量名称,value是变量值,value的类型下列代码仅支持了String和List,对应的可以实现直接变量替换和循环变量替换,有其他需要可以在此基础上新增功能,而List的泛型仅支持Map<String,String>,即不支持多层嵌套。
在这个例子中,我使用${变量名}表示普通变量,<变量名></变量名>表示循环变量,标签内的内容将被循环展示
public class ReportGenerator {
public static void main(String[] args) {
String reportTemplate = "这是一份贵司专属总结报告,请查收!\n" +
"您的订单编号:${firstShowId},产品名称:${firstProductName},于${firstOnlineDate}正式生效,今天是贵司正式启用电子签名的第90天,截至今天,您的订单使用情况如下:#{${a}/${b}}\n" +
"<orders>${orders.productName},累计消耗${orders.displayConsumeAmount}${orders.units}</orders>" +
"如对使用情况和数据报告有任何疑问,可随时联系你的客户成功经理。\n" +
"<contracts>合同编号${contracts.contractId},合同负责人:${contracts.contractManager},${name}</contracts>";
Map<String, Object> data = new HashMap<>();
data.put("firstShowId", "123456789");
data.put("firstProductName", "测试产品");
data.put("firstOnlineDate", "2023-01-01");
data.put("productName", "测试产品");
data.put("name", "张三");
data.put("a", "100");
data.put("b", "10");
data.put("c", "10");
List<Map<String, String>> orders = new ArrayList<>();
Map<String, String> param1 = new HashMap<>();
param1.put("productName", "测试产品1");
param1.put("displayConsumeAmount", "100");
param1.put("units", "元");
orders.add(param1);
Map<String, String> param2 = new HashMap<>();
param2.put("productName", "测试产品2");
param2.put("displayConsumeAmount", "200");
param2.put("units", "元");
orders.add(param2);
data.put("orders", orders);
List<Map<String, String>> contracts = new ArrayList<>();
Map<String, String> param3 = new HashMap<>();
param1.put("contractId", "12312543243213");
param1.put("contractManager", "杜甫");
contracts.add(param1);
Map<String, String> param4 = new HashMap<>();
param2.put("contractId", "1234353453");
param2.put("contractManager", "李白");
contracts.add(param2);
data.put("contracts", contracts);
String handler = handler(data, reportTemplate);
// 打印报告
System.out.println(handler);
}
public static String handler(Map<String, Object> data, String templateContent) {
// 循环变量,仅支持一层嵌套
Map<String, List<Map<String, String>>> loopVariables = new HashMap<>();
// 基本参数
Map<String, String> basicVariables = new HashMap<>();
// 分类:循环变量,基本参数,计算参数
for (Map.Entry<String, Object> entry : data.entrySet()) {
String key = entry.getKey();
Object value = entry.getValue();
if (value instanceof List) {
loopVariables.put(key, (List<Map<String, String>>) value);
} else if (value instanceof String) {
basicVariables.put(key, (String) value);
}
}
// 处理循环变量
for (Map.Entry<String, List<Map<String, String>>> entry : loopVariables.entrySet()) {
String key = entry.getKey();
List<Map<String, String>> value = entry.getValue();
ForEachTokenParser forEachTokenParser = new ForEachTokenParser(key, value, "\n");
GenericTokenParser genericTokenParser = new GenericTokenParser("<" + key + ">", "</" + key + ">", forEachTokenParser);
templateContent = genericTokenParser.parse(templateContent);
}
// 处理基本参数
VariableTokenParser variableTokenParser = new VariableTokenParser(basicVariables);
GenericTokenParser genericTokenParser = new GenericTokenParser("${", "}", variableTokenParser);
templateContent = genericTokenParser.parse(templateContent);
// // 计算参数
// EquationTokenParser equationTokenParser = new EquationTokenParser();
// GenericTokenParser equationParser = new GenericTokenParser("#{", "}", equationTokenParser);
// templateContent = equationParser.parse(templateContent);
return templateContent;
}
/**
* 循环变量参数
*/
static class ForEachTokenParser implements TokenHandler {
private final String variableName;
private final List<Map<String, String>> variables;
private final String separator;
public ForEachTokenParser(String variableName, List<Map<String, String>> variable, String separator) {
this.variableName = variableName;
this.variables= variable;
this.separator = separator;
}
@Override
public String handleToken(String content) {
if (variables == null || variables.isEmpty()) {
throw new RuntimeException("变量不存在:" + content);
}
StringBuilder builder = new StringBuilder();
for (Map<String, String> variable : variables) {
VariableTokenParser variableTokenParser = new VariableTokenParser(variable);
GenericTokenParser genericTokenParser = new GenericTokenParser("${" + variableName + ".", "}", variableTokenParser);
builder.append(genericTokenParser.parse(content)).append(separator);
}
return builder.toString();
}
}
/**
* 基础变量参数
*/
static class VariableTokenParser implements TokenHandler {
private final Map<String, String> variables;
public VariableTokenParser(Map<String, String> variable) {
this.variables= variable;
}
@Override
public String handleToken(String content) {
String value = variables.get(content);
if (value == null) {
throw new RuntimeException("变量不存在:" + content);
}
return value;
}
}
/**
* 计算参数
* 目前仅支持除法运算:#{100/10}
*/
// static class EquationTokenParser implements TokenHandler {
//
// @Override
// public String handleToken(String content) {
// return String.valueOf(evaluateExpression(content));
// }
//
// private static double evaluateExpression(String expression) {
// String[] parts = expression.split("/");
// if (parts.length == 2) {
// BigDecimal numerator = new BigDecimal(parts[0].trim());
// BigDecimal denominator = new BigDecimal(parts[1].trim());
// return numerator.divide(denominator, 2, RoundingMode.HALF_UP).doubleValue();
// }
// return 0;
// }
// }
}