极简模板语言实现,极简模板语言


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

相关内容