极简模板语言实现

简介: 在项目中遇到这样一个功能,需要用户输入一段带参数的字符串模板,然后服务端根据一个上下文去解析它。查了一些资料发现了[StrSubstitutor](https://commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/apache/commons/lang3/text/StrSubstitutor.html) 这个小工具。其自带例子

在项目中遇到这样一个功能,需要用户输入一段带参数的字符串模板,然后服务端根据一个上下文去解析它。查了一些资料发现了StrSubstitutor 这个小工具。其自带例子如下:

 Map valuesMap = HashMap();
 valuesMap.put("animal", "quick brown fox");
 valuesMap.put("target", "lazy dog");
 String templateString = "The ${animal} jumped over the ${target}.";
 StrSubstitutor sub = new StrSubstitutor(valuesMap);
 String resolvedString = sub.replace(templateString);

现在我想从一个二维表数据结构(List<Map<String, Object>>)中的某一行都去跑这么一个模板,这样上面的例子就不需要了。我甚至还想对结果做个扩展,比如格式化什么的。通过查询相关资料, 发现了StrLookup这个类。结合正则表达式,有了下面的实现

public class ExtraFormatLookup extends StrLookup {

    private static final String FMT = "fmt";
    private final List<Map<String, Object>> result;
    private final Map<String, MetricDO> metrics;

    ExtraFormatLookup(List<Map<String, Object>> result, Map<String, MetricDO> metrics) {
        this.result = result;
        this.metrics = metrics;
    }

    @Override
    public String lookup(String s) {
       s = s.toLowerCase();
        String regex = "([^\\[.]+)(?:\\[(-?\\d+)])?(?:\\.([a-zA-z]+)(?::-(.*))?)?";
        Matcher matcher = Pattern.compile(regex).matcher(s);
        if (matcher.find()) {
            String key = matcher.group(1);
            String indexStr = matcher.group(2);
            String suffix = matcher.group(3);
            String defaultValue = matcher.group(4) == null ? "" : matcher.group(4);
            if (result == null || result.isEmpty()) {
                return defaultValue;
            }
            int index = 0;
            if (!Strings.isNullOrEmpty(indexStr)) {
                index = Integer.parseInt(indexStr);
                if (index < 0 - result.size() || index >= result.size()) {
                    return defaultValue;
                }
                if (index < 0) {
                    index = result.size() + index;
                }
            }
            String r = String.valueOf(result.get(index).getOrDefault(key, defaultValue));

            if (metrics.containsKey(key) && FMT.equalsIgnoreCase(suffix)) {
                r = CommonUtil.getValueFormatStr(metrics.get(key), r);
            }
            return r;
        } else {
            return "";
        }
    }
}

测试代码如下:

 @Test
    public void lookup1() {
       List<Map<String, Object>> result = Lists.newArrayList(
            ImmutableMap.of("abc", "1111111"),
            ImmutableMap.of("abc", "222222"),
            ImmutableMap.of("abc", "33333", "def", "4444")
        );

        Map<String, MetricDO> metrics = Maps.newHashMap();
        metrics.put("abc", new MetricDO().setCode("abc").setType("abs").setPattern(",###,###"));

        StrSubstitutor sub = new StrSubstitutor(new ExtraFormatLookup(result, metrics));

        assertThat(sub.replace("test ${abc}"), Matchers.equalTo("test 1111111"));
        assertThat(sub.replace("test ${abc.fmt}"), Matchers.equalTo("test 1,111,111"));
        assertThat(sub.replace("test ${abc[0]}"), Matchers.equalTo("test 1111111"));
        assertThat(sub.replace("test ${abc[1]}"), Matchers.equalTo("test 222222"));
        assertThat(sub.replace("test ${abc[2]}"), Matchers.equalTo("test 33333"));
        assertThat(sub.replace("test ${abc[1].fmt}"), Matchers.equalTo("test 222,222"));
        assertThat(sub.replace("test ${abc[2].fmt}"), Matchers.equalTo("test 33,333"));
        assertThat(sub.replace("test ${def[2].fmt}"), Matchers.equalTo("test 4444"));
        assertThat(sub.replace("test ${def.fmt}"), Matchers.equalTo("test "));
        assertThat(sub.replace("test ${abc[-1].fmt}"), Matchers.equalTo("test 33,333"));
        assertThat(sub.replace("test ${abc[-2].fmt}"), Matchers.equalTo("test 222,222"));
        assertThat(sub.replace("test ${abc[-5].fmt}"), Matchers.equalTo("test "));
        assertThat(sub.replace("test ${abcd[-5].fmt:-efg}"), Matchers.equalTo("test efg"));
        assertThat(sub.replace("test ${abc[-789].fmt:-efg}"), Matchers.equalTo("test efg"));
        assertThat(sub.replace("test ${abcd[1111].fmt:-efg}"), Matchers.equalTo("test efg"));
    }

其实逻辑很简单,就是通过正则表达式拿到要访问的key,row的index,扩展方法字段以及默认值。后续要扩展也比较简单,事先实现好方法然后增加支持的扩展字段就好了。

另外安利一个验证Java正则表达式的网站https://www.freeformatter.com/java-regex-tester.html#ad-output

目录
相关文章
|
SQL 存储 数据库
OceanBase数据库常见问题之4.2.2写库过程中总是出现transaction is killed如何解决
OceanBase 是一款由阿里巴巴集团研发的企业级分布式关系型数据库,它具有高可用、高性能、可水平扩展等特点。以下是OceanBase 数据库使用过程中可能遇到的一些常见问题及其解答的汇总,以帮助用户更好地理解和使用这款数据库产品。
|
消息中间件 Java 数据安全/隐私保护
Spring Boot 中的 AmqpTemplate 是什么,原理,如何使用
Spring Boot 中的 AmqpTemplate 是什么,原理,如何使用
|
运维 监控 数据可视化
GCeasy使用
GCeasy使用
|
人工智能 程序员 API
为了了解国外AI最新动态,分享我经常逛的6 个 YouTube AI频道
AI 正在迅速发展,每周都会有一篇关于该领域新发展的新论文,一种可以提高您工作效率的 AI 工具,或者一个改变一切的公告。 这就是为什么在本文中,我想与您分享最好的 YouTube 频道,以便及时了解 AI 的最新动态。这些 YouTube 用户精心挑选了最好的 AI 新闻,并创建了有关如何充分利用 ChatGPT 等 AI 工具的详细教程。
1603 0
|
存储 缓存 NoSQL
经验大分享:OHCJava堆外缓存详解与应用
经验大分享:OHCJava堆外缓存详解与应用
374 1
|
存储 关系型数据库 MySQL
MySQL中使用UUID做主键时需要注意的两个坑(译文)
在InnoDB中使用UUID作为主键需要考虑两个问题
642 0
|
存储 算法 安全
深入理解SHA系列哈希算法:安全性的保障与演进
深入理解SHA系列哈希算法:安全性的保障与演进
|
存储 大数据 Apache
深入理解ZooKeeper:分布式协调服务的核心与实践
【5月更文挑战第7天】ZooKeeper是Apache的分布式协调服务,确保大规模分布式系统中的数据一致性与高可用性。其特点包括强一致性、高可用性、可靠性、顺序性和实时性。使用ZooKeeper涉及安装配置、启动服务、客户端连接及执行操作。实际应用中,面临性能瓶颈、不可伸缩性和单点故障等问题,可通过水平扩展、集成其他服务和多集群备份来解决。理解ZooKeeper原理和实践,有助于构建高效分布式系统。
若依如何添加目录菜单,左边的内容,找到左侧目录系统管理下的菜单管理,上级菜单什么意思,创建好放哪里,这里的首页,系统管理,系统监控是主类目是并集 ,显示排序,值越小排名越靠前
若依如何添加目录菜单,左边的内容,找到左侧目录系统管理下的菜单管理,上级菜单什么意思,创建好放哪里,这里的首页,系统管理,系统监控是主类目是并集 ,显示排序,值越小排名越靠前