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