是否有类似
String.format
之类的标准或至少广泛的实现,但带有命名参数?
我想以这样的方式格式化模板化字符串:
Map<String, Object> args = new HashMap<String, Object>();
args.put("PATH", "/usr/bin");
args.put("file", "foo");
String s = someHypotheticalMethod("#{PATH}/ls #{file}");
// "/usr/bin/ls foo"
从技术上讲,它几乎与:
String[] args = new String[] { "/usr/bin", "foo" };
String s = String.format("%1$s/ls %2$s", args);
// "/usr/bin/ls foo"
但带有命名参数。
我知道:
但它们都使用有序或至少编号的参数,而不是命名的参数。我知道实现它很简单,但是我是否在标准 Java 库中或至少在 Apache Commons / Guava / 类似的东西中寻找一种机制,而不引入引人注目的模板引擎?
注意:我对成熟的模板引擎并不真正感兴趣,它具有一些命令式/功能逻辑、流程控制、修饰符、子模板/包含、迭代器等功能。通常以下方法是有效的 4 -line 实现 - 这就是我所需要的:
public static String interpolate(String format, Map<String, ? extends Object> args) {
String out = format;
for (String arg : args.keySet()) {
out = Pattern.compile(Pattern.quote("#{" + arg + "}")).
matcher(out).
replaceAll(args.get(arg).toString());
}
return out;
}
如果无法选择 Java 7,您也可以尝试
org.apache.commons.lang3.text.StrSubstitutor
。它完全做你想要它做的事。它是否轻量级可能取决于您是否也使用其他 commons-lang 的东西。
我最近发现JUEL非常符合描述。它是从JSP中取出的表达语言。它声称也非常快。
我即将在我自己的一个项目中尝试一下。
但是对于更轻量级的,这是你的变体,请尝试这个(包含在单元测试中):
public class TestInterpolation {
public static class NamedFormatter {
public final static Pattern pattern = Pattern.compile("#\\{(?<key>.*)}");
public static String format(final String format, Map<String, ? extends Object> kvs) {
final StringBuffer buffer = new StringBuffer();
final Matcher match = pattern.matcher(format);
while (match.find()) {
final String key = match.group("key");
final Object value = kvs.get(key);
if (value != null)
match.appendReplacement(buffer, value.toString());
else if (kvs.containsKey(key))
match.appendReplacement(buffer, "null");
else
match.appendReplacement(buffer, "");
}
match.appendTail(buffer);
return buffer.toString();
}
}
@Test
public void test() {
assertEquals("hello world", NamedFormatter.format("hello #{name}", map("name", "world")));
assertEquals("hello null", NamedFormatter.format("hello #{name}", map("name", null)));
assertEquals("hello ", NamedFormatter.format("hello #{name}", new HashMap<String, Object>()));
}
private Map<String, Object> map(final String key, final Object value) {
final Map<String, Object> kvs = new HashMap<>();
kvs.put(key, value);
return kvs;
}
}
我会扩展它以添加便捷的方法来快速键值对
format(format, key1, value1)
format(format, key1, value1, key2, value2)
format(format, key1, value1, key2, value2, key3, value3)
...
从 java 7+ 转换到 java 6-
应该不会太难StringTemplate 可能是您可能得到的轻量级插值引擎,尽管我不知道它如何在资源方面与 FreeMarker、Mustache 或 Velocity 之类的东西相比。
这是我的解决方案:
public class Template
{
private Pattern pattern;
protected Map<CharSequence, String> tokens;
private String template;
public Template(String template)
{
pattern = Pattern.compile("\\$\\{\\w+\\}");
tokens = new HashMap<CharSequence, String>();
this.template = template;
}
public void clearAllTokens()
{
tokens.clear();
}
public void setToken(String token, String replacement)
{
if(token == null)
{
throw new NullPointerException("Token can't be null");
}
if(replacement == null)
{
throw new NullPointerException("Replacement string can't be null");
}
tokens.put(token, Matcher.quoteReplacement(replacement));
}
public String getText()
{
final Matcher matcher = pattern.matcher(template);
final StringBuffer sb = new StringBuffer();
while(matcher.find())
{
final String entry = matcher.group();
final CharSequence key = entry.subSequence(2, entry.length() - 1);
if(tokens.containsKey(key))
{
matcher.appendReplacement(sb, tokens.get(key));
}
}
matcher.appendTail(sb);
return sb.toString();
}
public static void main(String[] args) {
Template template = new Template("Hello, ${name}.");
template.setToken("name", "Eldar");
System.out.println(template.getText());
}
}
我知道我的答案来得有点晚了,但如果你仍然需要这个功能,而不需要下载成熟的模板引擎,你可以看看aleph-formatter(我是作者之一):
Student student = new Student("Andrei", 30, "Male");
String studStr = template("#{id}\tName: #{st.getName}, Age: #{st.getAge}, Gender: #{st.getGender}")
.arg("id", 10)
.arg("st", student)
.format();
System.out.println(studStr);
或者你可以链接参数:
String result = template("#{x} + #{y} = #{z}")
.args("x", 5, "y", 10, "z", 15)
.format();
System.out.println(result);
// Output: "5 + 10 = 15"
在内部,它使用 StringBuilder 通过“解析”表达式创建结果,不执行字符串连接、正则表达式/替换。
我还在我的 str utils 中做了一个(未测试)
string.MapFormat("abcd {var}",map)
。
//util
public static String mapFormat(String template, HashMap<String, String> mapSet) {
String res = template;
for (String key : mapSet.keySet()) {
res = template.replace(String.format("{%s}", key), mapSet.get(key));
}
return res;
}
//use
public static void main(String[] args) {
boolean isOn=false;
HashMap<String, String> kvMap=new HashMap<String, String>();
kvMap.put("isOn", isOn+"");
String exp=StringUtils.mapFormat("http://localhost/api/go?isOn={isOn}", kvMap);
System.out.println(exp);
}
您可以使用Java的字符串模板功能。 它在 JEP 430 中进行了描述,并作为预览功能出现在 JDK 21 中。这是一个使用示例:
String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan"); // true
Java 的字符串模板比其他语言(例如 Python 的 f 字符串)中的插值更通用,也更安全。例如,字符串连接或插值使得 SQL 注入攻击成为可能:
String query = "SELECT * FROM Person p WHERE p.last_name = '" + name + "'";
ResultSet rs = conn.createStatement().executeQuery(query);
但是这个变体(来自 JEP 430)可以防止 SQL 注入:
PreparedStatement ps = DB."SELECT * FROM Person p WHERE p.last_name = \{name}";
ResultSet rs = ps.executeQuery();