带有命名参数的 Java 字符串模板化器/格式化器

问题描述 投票:0回答:8

是否有类似

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 string-formatting template-engine
8个回答
10
投票

如果无法选择 Java 7,您也可以尝试

org.apache.commons.lang3.text.StrSubstitutor
。它完全做你想要它做的事。它是否轻量级可能取决于您是否也使用其他 commons-lang 的东西。


4
投票

4
投票

我最近发现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-

应该不会太难

2
投票

StringTemplate 可能是您可能得到的轻量级插值引擎,尽管我不知道它如何在资源方面与 FreeMarkerMustacheVelocity 之类的东西相比。

另一个选择可能是 EL 引擎,例如 MVEL,它具有 模板引擎


0
投票

这是我的解决方案:

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());
    }
}

0
投票

我知道我的答案来得有点晚了,但如果你仍然需要这个功能,而不需要下载成熟的模板引擎,你可以看看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 通过“解析”表达式创建结果,不执行字符串连接、正则表达式/替换。


0
投票

我还在我的 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);
}

0
投票

您可以使用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();
© www.soinside.com 2019 - 2024. All rights reserved.