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

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;
    //     }
    // }
}

http://www.kler.cn/a/502242.html

相关文章:

  • 【Rust】引用与借用
  • 【学习路线】Python自动化运维 详细知识点学习路径(附学习资源)
  • fisco bcosV3 Table智能合约开发
  • C++语言的计算机基础
  • 【Redis学习 | 第5篇】Redis缓存 —— 缓存的概念 + 缓存穿透 + 缓存雪崩 + 缓存击穿
  • 网络授时笔记
  • react生命周期方法
  • Shell经典面试题
  • istoreos安装tailscale命令
  • 力扣257(关于回溯算法)二叉树的所有路径
  • 机器学习 - 如何理解几何学中的超平面 ?
  • Qt+ffmpeg+libVlc 实现简单视频播放器
  • [0405].第05节:搭建Redis主从架构
  • Vue.js开发入门:从零开始搭建你的第一个项目
  • [读书日志]从零开始学习Chisel 第十一篇:Scala的类型参数化(敏捷硬件开发语言Chisel与数字系统设计)
  • gojs2.3去除水印
  • C#中的Null注意事项
  • 银河麒麟桌面操作系统搭建FTP服务器
  • 热烈祝贺“钛然科技”选择使用订单日记
  • 国产信创3D- 中望3D Linux 2025发布,助力企业高效转型国产三维CAD
  • 【论文笔记】多个大规模数据集上的SOTA绝对位姿回归方法:Reloc3r
  • 基于springboot+vue的 嗨玩-旅游网站
  • 方法引用与lambda底层原理Java方法引用、lambda能被序列化么?
  • Vue 3前端与Python(Django)后端接口简单示例
  • 74.搜索二维矩阵 python
  • HTTP 常用方法解析