当前位置: 首页 > news >正文

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 tokenint 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;}@Overridepublic 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;}@Overridepublic 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;//     }// }
}
http://www.lryc.cn/news/518777.html

相关文章:

  • 跨界融合:人工智能与区块链如何重新定义数据安全?
  • android 自定义SwitchCompat,Radiobutton,SeekBar样式
  • 计算机网络的定义与发展历程
  • 对比学习 (Contrastive Learning) 算法详解与PyTorch实现
  • DBeaver执行本地的sql语句文件避免直接在客户端运行卡顿
  • C++ 的 pair 和 tuple
  • Zookeeper 集群安装
  • git merge与rebase区别以及实际应用
  • kvm虚拟机出现应用程序无法正常启动报0xc0000142错误
  • Redis 安装与 Spring Boot 集成指南
  • Flink集成TDEngine来批处理或流式读取数据进行流批一体化计算(Flink SQL)拿来即用的案例
  • 【STM32】利用SysTick定时器定时1s
  • Python中的format格式化、填充与对齐、数字格式化方式
  • winform第三方界面开源库AntdUI的使用教程保姆级环境设置篇
  • 如何使用Yarn Workspaces实现Monorepo模式在一个仓库中管理多个项目
  • SpringCloud系列教程:微服务的未来(十一)服务注册、服务发现、OpenFeign快速入门
  • 物联网:七天构建一个闭环的物联网DEMO
  • 景联文科技提供高质量多模态数据处理服务,驱动AI新时代
  • c#13新特性
  • LeetCode LCP17速算机器人
  • 杭州铭师堂的云原生升级实践
  • 计算机网络之---MAC协议
  • 微服务面试相关
  • Google发布图像生成新工具Whisk:无需复杂提示词,使用图像和人工智能将想法可视化并重新混合
  • docker pull(拉取镜像)的时候,无法下载或者卡在Waiting的解决方法
  • 51c~Pytorch~合集4
  • windows下,golang+vscode+delve 远程调试
  • 弥散张量分析开源软件 DSI Studio 简体中文汉化版可以下载了
  • 视频编辑最新SOTA!港中文Adobe等发布统一视频生成传播框架——GenProp
  • 多维方向性增强分割通过大规模视觉模型实现|文献速递-视觉大模型医疗图像应用