【问题标题】:Java string templatizer / formatter with named arguments带有命名参数的 Java 字符串模板器/格式化器
【发布时间】:2012-06-29 15:03:09
【问题描述】:

是否有类似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;
}

【问题讨论】:

  • 您不只是使用“#” + args.get("PATH") + "/ls #" + args.get("file") 的任何原因?
  • 我有一堆模板文件,我有一个参数映射,我需要从每个模板文件中获取填充字符串。

标签: java string-formatting template-engine


【解决方案1】:

我最近发现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 应该不会太难-

【讨论】:

  • 我想提一下,它也不是真正的轻量级:它基本上允许执行 Java 子集,插入到一些字符串中。它有 138K 的压缩类,里面有 114 个类。
  • 是的,我用手机回答了这个问题,我一拿到键盘就会添加另一个实现。
  • 现在添加了我自己的实现。
【解决方案2】:

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

【讨论】:

  • 谢谢!看起来这正是我想要的:)
【解决方案3】:

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

另一个选项可能是像MVEL 这样的EL 引擎,它有一个templating engine

【讨论】:

  • 咳咳...“属性引用”、“模板引用(如#include 或宏扩展)”、“子模板的条件包含”、“模板应用到属性列表”... 221K 的压缩的 jar 类,里面有 124 个类——这绝对不是轻量级的。不过,我会检查其他的,谢谢!
  • 是的,我已经确认所有 FreeMarker、Mustache 和 Velocity 都比我需要的要重得多。不管怎样,谢谢你的建议!
【解决方案4】:

Matcher#appendReplacement() 会有所帮助

【讨论】:

  • 我总是很高兴看到 Java 7 参考资料。
【解决方案5】:

这是我的解决方案:

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

【讨论】:

    【解决方案6】:

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

    【讨论】:

      【解决方案7】:

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

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2011-01-27
        • 1970-01-01
        • 2019-02-20
        • 2013-07-27
        • 1970-01-01
        • 1970-01-01
        • 2022-11-22
        • 2013-06-17
        相关资源
        最近更新 更多