10

是否有类似的标准或至少广泛的实现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 行实现- 这就是我所需要的:

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;
}
4

7 回答 7

10

org.apache.commons.lang3.text.StrSubstitutor如果 Java 7 不是一个选项,您也可以尝试。它完全符合您的要求。它是否轻量级可能取决于您是否也使用了其他的 commons-lang。

于 2012-07-13T11:24:26.427 回答
4

Matcher#appendReplacement()会有所帮助

于 2012-06-29T15:29:28.027 回答
3

我最近发现了非常符合描述的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- 应该不会太难

于 2012-10-21T08:14:16.350 回答
2

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

另一种选择可能是像MVEL这样​​的 EL 引擎,它有一个模板引擎

于 2012-06-29T15:07:49.550 回答
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 通过“解析”表达式来创建结果,不进行字符串连接,执行正则表达式/替换。

于 2017-03-29T19:56:20.783 回答
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());
    }
}
于 2015-12-04T06:37:19.903 回答
0

我还在我的 str utils (nottested) 中做了一个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);
}
于 2019-11-05T14:54:18.617 回答