Reputation: 17104
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
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
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
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
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
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
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
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