GreyCat
GreyCat

Reputation: 17104

Java string templatizer / formatter with named arguments

Is there a standard or at least widespread implementation of something like String.format, but with named arguments?

I'd like to format a templatized string in a way like that:

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"

Technically, it's almost the same as:

String[] args = new String[] { "/usr/bin", "foo" };
String s = String.format("%1$s/ls %2$s", args);
// "/usr/bin/ls foo"

but with named arguments.

I'm aware of:

but all of them use ordered or at least numbered arguments, not named ones. I know it's trivial to implement one, but is there a mechanism I'm looking for in standard Java libraries or at least in Apache Commons / Guava / something similar, without bringing in high-profile template engines?

NOTE: I'm not really interested in full-blown template engines, with features like some imperative / functional logic, flow control, modifiers, sub-templates / inclusions, iterators, etc. Generally the following method is a working 4-line implementation - that's all I need:

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

Upvotes: 12

Views: 10678

Answers (8)

mernst
mernst

Reputation: 8117

You can use Java's String Templates feature. It is described in JEP 430, and it appears in JDK 21 as a preview feature. Here is an example use:

String name = "Joan";
String info = STR."My name is \{name}";
assert info.equals("My name is Joan");   // true

Java's string templates are more versatile, and much safer, than the interpolation found in other languagues such as Python's f-strings. For example, string concatenation or interpolation makes SQL injection attacks possible:

String query = "SELECT * FROM Person p WHERE p.last_name = '" + name + "'";
ResultSet rs = conn.createStatement().executeQuery(query);

but this variant (from JEP 430) prevents SQL injection:

PreparedStatement ps = DB."SELECT * FROM Person p WHERE p.last_name = \{name}";
ResultSet rs = ps.executeQuery();

Upvotes: 2

Davut G&#252;rb&#252;z
Davut G&#252;rb&#252;z

Reputation: 5716

I also made one in my str utils (not tested) 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);
}

Upvotes: 0

Andrei Ciobanu
Andrei Ciobanu

Reputation: 12838

I know my answer comes a little late, but if you still need this functionality, without the need to download a full-fledged template engine you can take a look at aleph-formatter (I am one of the authors):

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

Or you can chain the arguments:

String result = template("#{x} + #{y} = #{z}")
                    .args("x", 5, "y", 10, "z", 15)
                    .format();
System.out.println(result);

// Output: "5 + 10 = 15"

Internally it works using a StringBuilder creating the result by "parsing" the expression, no string concatenation, regex/replace is performed.

Upvotes: 0

Eldar Budagov
Eldar Budagov

Reputation: 311

Here is my solution:

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

Upvotes: 0

Michael Deardeuff
Michael Deardeuff

Reputation: 10697

I recently discovered JUEL which fits the description nicely. It is the expression language taken out of JSP. It claims to be very fast, too.

I'm about to try it out in one of my own projects.

But for a lighter-weight, which is a variant of yours, try this (wrapped in a unit test):

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

I'd extend it to add convenience methods to for quick key-value pairs

format(format, key1, value1)
format(format, key1, value1, key2, value2)
format(format, key1, value1, key2, value2, key3, value3)
...

And it shouldn't be too hard to convert from java 7+ to java 6-

Upvotes: 4

Michael Piefel
Michael Piefel

Reputation: 19968

You might also try org.apache.commons.lang3.text.StrSubstitutor if Java 7 is not an option. It does exactly what you want it to do. Whether it’s light-weight might depend on whether you use something else of commons-lang as well.

Upvotes: 10

Pavel K.
Pavel K.

Reputation: 436

Matcher#appendReplacement() would help

Upvotes: 4

Dave Newton
Dave Newton

Reputation: 160191

StringTemplate may be as light-weight an interpolation engine as you're likely to get, although I don't know how it stacks up resource-wise against things like FreeMarker, Mustache, or Velocity.

Another option might be an EL engine like MVEL, which has a templating engine.

Upvotes: 2

Related Questions