极简模板语言实现

简介: 在项目中遇到这样一个功能,需要用户输入一段带参数的字符串模板,然后服务端根据一个上下文去解析它。查了一些资料发现了[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

目录
相关文章
|
11月前
|
存储 编译器 C++
【C++】什么是模板?怎样使用模板?
【C++】什么是模板?怎样使用模板?
68 0
|
自然语言处理 编译器 C语言
泛型编程——模板【C++】
泛型编程——模板【C++】
39 0
|
5月前
|
编译器 程序员 C++
C++一分钟之-模板基础:泛型编程
【6月更文挑战第21天】C++模板,泛型编程的关键,让代码跨类型工作,增强重用与灵活性。理解模板基础,如函数和类模板,注意避免特化与偏特化的混淆、编译时膨胀及复杂的错误调试。通过明确特化目的、限制模板使用及应用现代C++技术来优化。示例展示了模板如何自动或显式推导类型。模板元编程虽强大,但初学者宜从基础开始。正确使用模板,提升代码质量,同时保持简洁。
51 3
|
1月前
|
编译器 C++
【C++】模板进阶:深入解析模板特化
【C++】模板进阶:深入解析模板特化
|
6月前
|
程序员 C++
C++语言模板学习应用案例
C++模板实现通用代码,以适应多种数据类型。示例展示了一个计算两数之和的模板函数`add&lt;T&gt;`,可处理整数和浮点数。在`main`函数中,展示了对`add`模板的调用,分别计算整数和浮点数的和,输出结果。
50 2
|
6月前
|
编译器 C++
C++泛型编程——模板(初识)
C++泛型编程——模板(初识)
|
编译器 C++
C++基础语法(模板详解)
C++基础语法(模板详解)
53 0
|
C语言 C++ 容器
【C++】模板进阶:非类型模板参数&模板的特化&模板分离编译(上)
【C++】模板进阶:非类型模板参数&模板的特化&模板分离编译(上)
|
编译器 C语言 C++
【C++】模板进阶:非类型模板参数&模板的特化&模板分离编译(下)
【C++】模板进阶:非类型模板参数&模板的特化&模板分离编译(下)
|
编译器 虚拟化 C++
c++学习之模板
c++学习之模板
74 0