How to replace a set of tokens in a Java String? - java

I have the following template String: "Hello [Name] Please find attached [Invoice Number] which is due on [Due Date]".
I also have String variables for name, invoice number and due date - what's the best way to replace the tokens in the template with the variables?
(Note that if a variable happens to contain a token it should NOT be replaced).
EDIT
With thanks to #laginimaineb and #alan-moore, here's my solution:
public static String replaceTokens(String text,
Map<String, String> replacements) {
Pattern pattern = Pattern.compile("\\[(.+?)\\]");
Matcher matcher = pattern.matcher(text);
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
String replacement = replacements.get(matcher.group(1));
if (replacement != null) {
// matcher.appendReplacement(buffer, replacement);
// see comment
matcher.appendReplacement(buffer, "");
buffer.append(replacement);
}
}
matcher.appendTail(buffer);
return buffer.toString();
}

I really don't think you need to use a templating engine or anything like that for this. You can use the String.format method, like so:
String template = "Hello %s Please find attached %s which is due on %s";
String message = String.format(template, name, invoiceNumber, dueDate);

The most efficient way would be using a matcher to continually find the expressions and replace them, then append the text to a string builder:
Pattern pattern = Pattern.compile("\\[(.+?)\\]");
Matcher matcher = pattern.matcher(text);
HashMap<String,String> replacements = new HashMap<String,String>();
//populate the replacements map ...
StringBuilder builder = new StringBuilder();
int i = 0;
while (matcher.find()) {
String replacement = replacements.get(matcher.group(1));
builder.append(text.substring(i, matcher.start()));
if (replacement == null)
builder.append(matcher.group(0));
else
builder.append(replacement);
i = matcher.end();
}
builder.append(text.substring(i, text.length()));
return builder.toString();

Unfortunately the comfortable method String.format mentioned above is only available starting with Java 1.5 (which should be pretty standard nowadays, but you never know). Instead of that you might also use Java's class MessageFormat for replacing the placeholders.
It supports placeholders in the form '{number}', so your message would look like "Hello {0} Please find attached {1} which is due on {2}". These Strings can easily be externalized using ResourceBundles (e. g. for localization with multiple locales). The replacing would be done using the static'format' method of class MessageFormat:
String msg = "Hello {0} Please find attached {1} which is due on {2}";
String[] values = {
"John Doe", "invoice #123", "2009-06-30"
};
System.out.println(MessageFormat.format(msg, values));

You could try using a templating library like Apache Velocity.
http://velocity.apache.org/
Here is an example:
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;
import java.io.StringWriter;
public class TemplateExample {
public static void main(String args[]) throws Exception {
Velocity.init();
VelocityContext context = new VelocityContext();
context.put("name", "Mark");
context.put("invoiceNumber", "42123");
context.put("dueDate", "June 6, 2009");
String template = "Hello $name. Please find attached invoice" +
" $invoiceNumber which is due on $dueDate.";
StringWriter writer = new StringWriter();
Velocity.evaluate(context, writer, "TemplateName", template);
System.out.println(writer);
}
}
The output would be:
Hello Mark. Please find attached invoice 42123 which is due on June 6, 2009.

You can use template library for complex template replacement.
FreeMarker is a very good choice.
http://freemarker.sourceforge.net/
But for simple task, there is a simple utility class can help you.
org.apache.commons.lang3.text.StrSubstitutor
It is very powerful, customizable, and easy to use.
This class takes a piece of text and substitutes all the variables
within it. The default definition of a variable is ${variableName}.
The prefix and suffix can be changed via constructors and set methods.
Variable values are typically resolved from a map, but could also be
resolved from system properties, or by supplying a custom variable
resolver.
For example, if you want to substitute system environment variable into a template string,
here is the code:
public class SysEnvSubstitutor {
public static final String replace(final String source) {
StrSubstitutor strSubstitutor = new StrSubstitutor(
new StrLookup<Object>() {
#Override
public String lookup(final String key) {
return System.getenv(key);
}
});
return strSubstitutor.replace(source);
}
}

System.out.println(MessageFormat.format("Hello {0}! You have {1} messages", "Join",10L));
Output:
Hello Join! You have 10 messages"

String.format("Hello %s Please find attached %s which is due on %s", name, invoice, date)

It depends of where the actual data that you want to replace is located. You might have a Map like this:
Map<String, String> values = new HashMap<String, String>();
containing all the data that can be replaced. Then you can iterate over the map and change everything in the String as follows:
String s = "Your String with [Fields]";
for (Map.Entry<String, String> e : values.entrySet()) {
s = s.replaceAll("\\[" + e.getKey() + "\\]", e.getValue());
}
You could also iterate over the String and find the elements in the map. But that is a little bit more complicated because you need to parse the String searching for the []. You could do it with a regular expression using Pattern and Matcher.

My solution for replacing ${variable} style tokens (inspired by the answers here and by the Spring UriTemplate):
public static String substituteVariables(String template, Map<String, String> variables) {
Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
Matcher matcher = pattern.matcher(template);
// StringBuilder cannot be used here because Matcher expects StringBuffer
StringBuffer buffer = new StringBuffer();
while (matcher.find()) {
if (variables.containsKey(matcher.group(1))) {
String replacement = variables.get(matcher.group(1));
// quote to work properly with $ and {,} signs
matcher.appendReplacement(buffer, replacement != null ? Matcher.quoteReplacement(replacement) : "null");
}
}
matcher.appendTail(buffer);
return buffer.toString();
}

With Apache Commons Library, you can simply use Stringutils.replaceEach:
public static String replaceEach(String text,
String[] searchList,
String[] replacementList)
From the documentation:
Replaces all occurrences of Strings within another String.
A null reference passed to this method is a no-op, or if any "search
string" or "string to replace" is null, that replace will be ignored.
This will not repeat. For repeating replaces, call the overloaded
method.
StringUtils.replaceEach(null, *, *) = null
StringUtils.replaceEach("", *, *) = ""
StringUtils.replaceEach("aba", null, null) = "aba"
StringUtils.replaceEach("aba", new String[0], null) = "aba"
StringUtils.replaceEach("aba", null, new String[0]) = "aba"
StringUtils.replaceEach("aba", new String[]{"a"}, null) = "aba"
StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""}) = "b"
StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"}) = "aba"
StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"}) = "wcte"
(example of how it does not repeat)
StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"}) = "dcte"

You can use Apache Commons StringSubstitutor:
For example:
// Build map
Map<String, String> valuesMap = new HashMap<>();
valuesMap.put("animal", "quick brown fox");
valuesMap.put("target", "lazy dog");
String templateString = "The ${animal} jumped over the ${target}.";
// Build StringSubstitutor
StringSubstitutor sub = new StringSubstitutor(valuesMap);
// Replace
String resolvedString = sub.replace(templateString);
yielding:
"The quick brown fox jumped over the lazy dog."
You can also customize the prefix and suffix delimiters (${ and } respectively in the example above) by using:
setVariablePrefix
setVariableSuffix
You can also specify a default value using syntax like below:
String templateString = "The ${animal:giraffe} jumped over the ${target}.";
which would yield "The giraffe jumped over the lazy dog." when no animal parameter was supplied.

http://github.com/niesfisch/tokenreplacer

FYI
In the new language Kotlin,
you can use "String Templates" in your source code directly,
no 3rd party library or template engine need to do the variable replacement.
It is a feature of the language itself.
See:
https://kotlinlang.org/docs/reference/basic-types.html#string-templates

In the past, I've solved this kind of problem with StringTemplate and Groovy Templates.
Ultimately, the decision of using a templating engine or not should be based on the following factors:
Will you have many of these templates in the application?
Do you need the ability to modify the templates without restarting the application?
Who will be maintaining these templates? A Java programmer or a business analyst involved on the project?
Will you need to the ability to put logic in your templates, like conditional text based on values in the variables?
Will you need the ability to include other templates in a template?
If any of the above applies to your project, I would consider using a templating engine, most of which provide this functionality, and more.

I used
String template = "Hello %s Please find attached %s which is due on %s";
String message = String.format(template, name, invoiceNumber, dueDate);

The following replaces variables of the form <<VAR>>, with values looked up from a Map. You can test it online here
For example, with the following input string
BMI=(<<Weight>>/(<<Height>>*<<Height>>)) * 70
Hi there <<Weight>> was here
and the following variable values
Weight, 42
Height, HEIGHT 51
outputs the following
BMI=(42/(HEIGHT 51*HEIGHT 51)) * 70
Hi there 42 was here
Here's the code
static Pattern pattern = Pattern.compile("<<([a-z][a-z0-9]*)>>", Pattern.CASE_INSENSITIVE);
public static String replaceVarsWithValues(String message, Map<String,String> varValues) {
try {
StringBuffer newStr = new StringBuffer(message);
int lenDiff = 0;
Matcher m = pattern.matcher(message);
while (m.find()) {
String fullText = m.group(0);
String keyName = m.group(1);
String newValue = varValues.get(keyName)+"";
String replacementText = newValue;
newStr = newStr.replace(m.start() - lenDiff, m.end() - lenDiff, replacementText);
lenDiff += fullText.length() - replacementText.length();
}
return newStr.toString();
} catch (Exception e) {
return message;
}
}
public static void main(String args[]) throws Exception {
String testString = "BMI=(<<Weight>>/(<<Height>>*<<Height>>)) * 70\n\nHi there <<Weight>> was here";
HashMap<String,String> values = new HashMap<>();
values.put("Weight", "42");
values.put("Height", "HEIGHT 51");
System.out.println(replaceVarsWithValues(testString, values));
}
and although not requested, you can use a similar approach to replace variables in a string with properties from your application.properties file, though this may already be being done:
private static Pattern patternMatchForProperties =
Pattern.compile("[$][{]([.a-z0-9_]*)[}]", Pattern.CASE_INSENSITIVE);
protected String replaceVarsWithProperties(String message) {
try {
StringBuffer newStr = new StringBuffer(message);
int lenDiff = 0;
Matcher m = patternMatchForProperties.matcher(message);
while (m.find()) {
String fullText = m.group(0);
String keyName = m.group(1);
String newValue = System.getProperty(keyName);
String replacementText = newValue;
newStr = newStr.replace(m.start() - lenDiff, m.end() - lenDiff, replacementText);
lenDiff += fullText.length() - replacementText.length();
}
return newStr.toString();
} catch (Exception e) {
return message;
}
}

Related

Java regex to extract and replace by value

Input String
${abc.xzy}/demo/${ttt.bbb}
test${kkk.mmm}
RESULT
World/demo/Hello
testSystem
The text inside the curly brackets are keys to my properties. I want to replace those properties with run time values.
I can do the following to get the regex match but what should i put in the replace logic to change the ${..} matched with the respective run time value in the input string.
Pattern p = Pattern.compile("\\{([^}]*)\\}");
Matcher m = p.matcher(s);
while (m.find()) {
// replace logic comes here
}
An alternative may be using a third-party lib such as Apache Commons Text.
They have StringSubstitutor class looks very promising.
Map valuesMap = HashMap();
valuesMap.put("abc.xzy", "World");
valuesMap.put("ttt.bbb", "Hello");
valuesMap.put("kkk.mmm", "System");
String templateString = "${abc.xzy}/demo/${ttt.bbb} test${kkk.mmm}"
StringSubstitutor sub = new StringSubstitutor(valuesMap);
String resolvedString = sub.replace(templateString);
For more info check out Javadoc https://commons.apache.org/proper/commons-text/javadocs/api-release/org/apache/commons/text/StringSubstitutor.html
You may use the following solution:
String s = "${abc.xzy}/demo/${ttt.bbb}\ntest${kkk.mmm}";
Map<String, String> map = new HashMap<String, String>();
map.put("abc.xzy", "World");
map.put("ttt.bbb", "Hello");
map.put("kkk.mmm", "System");
StringBuffer result = new StringBuffer();
Matcher m = Pattern.compile("\\$\\{([^{}]+)\\}").matcher(s);
while (m.find()) {
String value = map.get(m.group(1));
m.appendReplacement(result, value != null ? value : m.group());
}
m.appendTail(result);
System.out.println(result.toString());
See the Java demo online, output:
World/demo/Hello
testSystem
The regex is
\$\{([^{}]+)\}
See the regex demo. It matches a ${ string, then captures any 1+ chars other than { and } into Group 1 and then matches }. If Group 1 value is present in the Map as a key, the replacement is the key value, else, the matched text is pasted back where it was in the input string.
Your regex needs to include the dollar. Also making the inner group lazy is sufficient to not include any } in the resulting key String.
String regex = "\\$\\{(.+?)\\}";
Pattern p = Pattern.compile(regex);
Matcher m = p.matcher(s);
while (m.find()) {
String key = m.group(1); // This is your matching group (the one in braces).
String value = someMap.get(key);
s.replaceFirst(regex, value != null ? value : "missingKey");
m = p.matcher(s); // you could alternatively reset the existing Matcher, but just create a new one, for simplicity's sake.
}
You could streamline this, by extracting the cursor position, and doing the replacement yourself, for the string. But either way, you need to reset your matcher, because otherwise it will parse on the old String.
The_Cute_Hedgehog's answer is good, but includes a dependency.
Wiktor Stribiżew's answer is missing a special case.
My answer aim to using java build-in regex and try to improve from Wiktor Stribiżew's answer. (Improve in Java code only, the regex is Ok)
Improvements:
Using StringBuilder is faster than StringBuffer
Initial StringBuilder capable to (int)(s.length()*1.2), avoid relocating memory many times in case of large input template s.
Avoid the case of regex special characters make wrong result by appendReplacement (like "cost: $100"). You can fix this problem in Wiktor Stribiżew's code by escape $ character in the replacement String like this value.replaceAll("\\$", "\\\\\\$")
Here is the improved code:
String s = "khj${abc.xzy}/demo/${ttt.bbb}\ntest${kkk.mmm}{kkk.missing}string";
Map<String, String> map = new HashMap<>();
map.put("abc.xzy", "World");
map.put("ttt.bbb", "cost: $100");
map.put("kkk.mmm", "System");
StringBuilder result = new StringBuilder((int)(s.length()*1.2));
Matcher m = Pattern.compile("\\$\\{([^}]+)\\}").matcher(s);
int nonCaptureIndex = 0;
while (m.find()) {
String value = map.get(m.group(1));
if (value != null) {
int index = m.start();
if (index > nonCaptureIndex) {
result.append(s.substring(nonCaptureIndex, index));
}
result.append(value);
nonCaptureIndex = m.end();
}
}
result.append(s.substring(nonCaptureIndex, s.length()));
System.out.println(result.toString());

Java: Replace RegEx with method outcome

I have the following scenario in my current Java project:
A properties file:
animal1=cat
animal2=dog
A Java method:
public String replace(String input) {
return input.replaceAll("%(.*?)%", properties.getProperty("$1"));
}
The part that says properties.getProperty("$1") obviously doesn't work because it will return the property for the key "$1" but not for the actual value for $1.
Is there any simple method to replace for example "%animal1%" with "cat"?
The properties file will contain a few hundred entries, so searching after a substring that could be replaced for every value in the properties file is not an option.
Don't try to do it as oneliner. If you use a loop to check for all the patterns that might match
Here's some code that will do the trick for you (this should compile and run as-is)
package org.test.stackoverflow;
import java.util.Properties;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class PatternReplacer {
private final Pattern keyPattern = Pattern.compile("%([^%]*)%");
private final Properties properties;
public PatternReplacer(Properties propertySeed) {
properties = propertySeed;
}
public String replace(String input) {
int start = 0;
while(true) {
Matcher match = keyPattern.matcher(input);
if(!match.find(start)) break;
String group = match.group(1);
if(properties.containsKey(group)) {
input = input.replaceAll("%" + group + "%", properties.getProperty(group));
} else {
start = match.start() + group.length();
}
}
return input;
}
public static void main(String... args) {
Properties p = new Properties();
p.put("animal1", "cat");
p.put("animal2", "dog");
PatternReplacer test = new PatternReplacer(p);
String result = test.replace("foo %animal1% %bar% %animal2%baz %animal1% qu%ux");
System.out.println(result);
}
}
Output:
foo cat %bar% dogbaz cat qu%ux
If I understand you correctly you will need to use manually use appendReplacement and appendTail methods from Matcher class. This will allow you to pass result of properties.getProperty(matcher.group(1))
Here is basic example of how you can use it. In this example I'm searching for some keyword like string or secret to replace them. Replacement is decided dynamically based on mapping like
string->foo,
secret->whatever
and is determined by simply calling get(keyword) from Map which stores this mapping.
String data = "some string with some secret data";
Map<String,String> properties = new HashMap<>();
properties.put("string", "foo");
properties.put("secret", "whatever");
Pattern p = Pattern.compile("string|secret");
Matcher m = p.matcher(data);
StringBuffer sb = new StringBuffer();
while (m.find()){
m.appendReplacement(sb, properties.get(m.group()));//replace found match
//with result based on group
}
m.appendTail(sb);//append rest of text after last match, in our case " data"
String result = sb.toString();
System.out.println("Original: " + data);
System.out.println("Replaced: " + result);
Result:
Original: some string with some secret data
Replaced: some foo with some whatever data

Search and replace formatted properties inside Java string

I will be given Strings that contain formatted "properties"; that is, Strings encapsulated inside the standard "${" and "}" tokens:
"This is an ${example} of a ${string} that I may be ${given}."
I will also have a HashMap<String,String> containing substitutions for each possible formatted property:
HashMap Keys HashMapValues
===========================================
bacon eggs
ham salads
So that, given the following String:
"I like to eat ${bacon} and ${ham}."
I can send this to a Java method that will transform it into:
"I like to eat eggs and salads."
Here's my best attempt:
System.out.println("Starting...");
String regex = "$\\{*\\}";
Map<String,String> map = new HashMap<String, String>();
map.put("bacon", "eggs");
map.put("ham", "salads");
String sampleString = "I like ${bacon} and ${ham}.";
Pattern pattern = Pattern.compile(regex);
Matcher matcher = pattern.matcher(sampleString);
while(matcher.find()) {
System.out.println("Found " + matcher.group());
// Strip leading "${" and trailing "}" off.
String property = matcher.group();
if(property.startsWith("${"))
property = property.substring(2);
if(property.endsWith("}"))
property = property.substring(0, property.length() - 1);
System.out.println("Before being replaced, property is: " + property);
if(map.containsKey(property))
property = map.get(property);
// Now, not sure how to locate the original property ("${bacon}", etc.)
// inside the sampleString so that I can replace "${bacon}" with
// "eggs".
}
System.out.println("Ending...");
When I execute this, I get no errors, but just see the "Starting..." and "Ending..." outputs. This tells me that my regex is incorrect, and so the Matcher isn't able to match any properties.
So my first question is: what should this regex be?
Once I'm past that, I'm not sure how to perform the string replace once I've changed "${bacon}" into "eggs", etc. Any ideas? Thanks in advance!
Why don't use a .properties file?, that way you could get all your messages from that file and could be separate from your code, something like (file example.properties):
message1=This is a {0} with format markers on it {1}
And then in your class load your bundle and use it like this:
ResourceBundle bundle = ResourceBundle.getBundle("example.properties", Locale.getDefault());
MessageFormat.format(bundle.getString('message1'), "param0", "param1"); // This gonna be your formatted String "This is a param0 with format markers on it param1"
You could use the MessageFormat (is a java.util library) without the bundle (just use the String directly), but again, having a bundle makes your code clear (and gives easy internationalization)
Use this instead:
String regex = "\\$\\{([^}]*)\\}";
Then you obtain only the content between ${ and } that is inside the capture group 1.
Note that the $ has a special meaning in a pattern: end of the string
Thus it musts be escaped to be seen as literal (as curly brackets).
Better use StrSubstitutor from apache commons lang. It can also substitute System props.
Since commons-lang 3.6 StrSubstitutor has been deprecated in favour of commons-text StringSubstitutor. Example:
import org.apache.commons.text.StringSubstitutor;
Properties props = new Properties();
props.setProperty("bacon", "eggs");
props.setProperty("ham", "salads");
String sampleString = "I like ${bacon} and ${ham}.";
String replaced = StringSubstitutor.replace(sampleString, props);
For completion, here is a working solution:
static final Pattern EXPRESSION_PATTERN = Pattern.compile("\\$\\{([^}]*)\\}");
/**
* Replace ${properties} in an expression
* #param expression expression string
* #param properties property map
* #return resolved expression string
*/
static String resolveExpression(String expression, Map<String, String> properties) {
StringBuilder result = new StringBuilder(expression.length());
int i = 0;
Matcher matcher = EXPRESSION_PATTERN.matcher(expression);
while(matcher.find()) {
// Strip leading "${" and trailing "}" off.
result.append(expression.substring(i, matcher.start()));
String property = matcher.group();
property = property.substring(2, property.length() - 1);
if(properties.containsKey(property)) {
//look up property and replace
property = properties.get(property);
} else {
//property not found, don't replace
property = matcher.group();
}
result.append(property);
i = matcher.end();
}
result.append(expression.substring(i));
return result.toString();
}

Getting paramValue for paramName in specifed querystring using regex

I like to write a java utility method that returns paramValue for paramName in specified query string
Pattern p = Pattern.compile("\\&?(\\w+)\\= (I don't know what to put here) ");
public String getParamValue(String entireQueryString, String paramName)
{
Matcher m = p.matcher(entireQueryString);
while(m.find()) {
if(m.group(1).equals(paramName)) {
return m.group(2);
}
}
return null;
}
I will be invoking this method from my servlet,
String qs = request.getQueryString(); //action=initASDF&requestId=9078-32&redirect=http://www.mydomain.com?actionId=4343
System.out.println(getParamValue(qs, "requestId"));
The output should be, 9078-32
you can use a regex negated group. See this other SO question: Regular Expressions and negating a whole character group
You'll need to get everything except a &.
Use the proper API to do it: request.getParameter("requestId")
Could you split the string based on ampersands (&) and then search the resulting array for the key (look upto the equals sign).
Here's a link to String.split(): http://docs.oracle.com/javase/7/docs/api/java/lang/String.html#split%28java.lang.String%29
Here's the type of thing I'm talking about:
private static final String KEY_VALUE_SEPARATOR = "=";
private static final String QUERY_STRING_SEPARATOR = "&";
public String getParamValue(String entireQueryString, String paramName) {
String[] fragments = entireQueryString.split(QUERY_STRING_SEPARATOR);
for (String fragment : fragments){
if (fragment.substring(0, fragment.indexOf(KEY_VALUE_SEPARATOR)).equalsIgnoreCase(paramName)){
return fragment.substring(fragment.indexOf(KEY_VALUE_SEPARATOR)+1);
}
}
throw new RuntimeException("can't find value");
}
The Exception at the end is a pretty rubbish idea but that's not really the important part of this.

How to format message with argument names instead of numbers?

I have something like:
String text = "The user {0} has email address {1}."
// params = { "Robert", "myemailaddr#gmail.com" }
String msg = MessageFormat.format(text, params);
This isn't great for me, because sometimes my translators are not sure what goes in the {0} and {1}, also it would be nice to be able to reword the messages without worrying about the order of the args.
I'd like to replace the arguments with readable names instead of numbers. Something like this:
String text = "The user {USERNAME} has email address {EMAILADDRESS}."
// Map map = new HashMap( ... [USERNAME="Robert", EMAILADDRESS="myemailaddr#gmail.com"]
String msg = MessageFormat.format(text, map);
Is there an easy way to do this?
Thanks!
rob
You can use MapFormat for this. Find out the details here:
http://www.java2s.com/Code/Java/I18N/AtextformatsimilartoMessageFormatbutusingstringratherthannumerickeys.htm
String text = "The user {name} has email address {email}.";
Map map = new HashMap();
map.put("name", "Robert");
map.put("email", "rhume55#gmail.com");
System.out.println("1st : " + MapFormat.format(text, map));
OUTPUT:
1st : The user Robert has email address rhume55#gmail.com.
See StrSubstitutor from org.apache.commons.lang3:
Map valuesMap = HashMap();
valuesMap.put("animal", "quick brown fox");
valuesMap.put("target", "lazy dog");
String templateString = "The ${animal} jumped over the ${target}.";
StrSubstitutor sub = new StrSubstitutor(valuesMap);
String resolvedString = sub.replace(templateString);
// resolvedString: "The quick brown fox jumped over the lazy dog."
Easy to make one yourself. This is what I use (the main() function is just for test code):
import java.util.HashMap;
import java.util.Map;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
public class StringTemplate {
final private String template;
final private Matcher m;
static final private Pattern keyPattern =
Pattern.compile("\\$\\{([a-zA-Z][a-zA-Z0-9_]*(\\.[a-zA-Z][a-zA-Z0-9_]*)*)\\}");
private boolean blanknull=false;
public StringTemplate(String template) {
this.template=template;
this.m = keyPattern.matcher(template);
}
/**
* #param map substitution map
* #return substituted string
*/
public String substitute(Map<String, ? extends Object> map)
{
this.m.reset();
StringBuffer sb = new StringBuffer();
while (this.m.find())
{
String k0 = this.m.group();
String k = this.m.group(1);
Object vobj = map.get(k);
String v = (vobj == null)
? (this.blanknull ? "" : k0)
: vobj.toString();
this.m.appendReplacement(sb, Matcher.quoteReplacement(v));
}
this.m.appendTail(sb);
return sb.toString();
}
public StringTemplate setBlankNull()
{
this.blanknull=true;
return this;
}
static public void main(String[] args)
{
StringTemplate t1 = new StringTemplate("${this} is a ${test} of the ${foo} bar=${bar} ${emergency.broadcasting.system}");
t1.setBlankNull();
Map<String, String> m = new HashMap<String, String>();
m.put("this", "*This*");
m.put("test", "*TEST*");
m.put("foo", "$$$aaa\\\\111");
m.put("emergency.broadcasting.system", "EBS");
System.out.println(t1.substitute(m));
}
}
Your question is closely related to: How to replace a set of tokens in a Java String
You could use velocity or another template library. But there will be some pain because Java does not have any kind of Map literals.
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.
static final Pattern REPLACE_PATTERN = Pattern.compile("\\x24\\x7B([a-zA-Z][\\w\\x2E].*?)\\x7D");
/**
* Check for unresolved environment
*
* #param str
* #return origin if all substitutions resolved
*/
public static String checkReplacement(String str) {
Matcher matcher = REPLACE_PATTERN.matcher(str);
if (matcher.find()) {
throw LOG.getIllegalArgumentException("Environment variable '" + matcher.group(1) + "' is not defined");
}
return str;
}
// replace in str ${key} to value
public static String resolveReplacement(String str, Map<String, String> replacements) {
Matcher matcher = REPLACE_PATTERN.matcher(str);
while (matcher.find()) {
String value = replacements.get(matcher.group(1));
if (value != null) {
str = matcher.replaceFirst(replaceWindowsSlash(value));
}
}
return str;
}
But you loose all format options (like ##.#)

Categories

Resources