如何替换 Java 字符串中的一组标记?

我有以下模板 String: "Hello [Name] Please find attached [Invoice Number] which is due on [Due Date]"

我也有字符串变量的名称,发票号码和到期日-什么是最好的方式来替换模板中的标记与变量?

(注意,如果一个变量恰好包含一个标记,它不应该被替换)。


剪辑

感谢@laginimaineb 和@alan-moore,我的解决办法是:

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();
}
169655 次浏览

您可以尝试使用类似 ApacheVelocity 的模板库。

http://velocity.apache.org/

这里有一个例子:

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

产出将是:

Hello Mark. Please find attached invoice 42123 which is due on June 6, 2009.

这取决于要替换的实际数据位于何处。你可能有这样一张地图:

Map<String, String> values = new HashMap<String, String>();

包含所有可以替换的数据。然后,您可以在映射上进行迭代,并按如下方式更改 String 中的所有内容:

String s = "Your String with [Fields]";
for (Map.Entry<String, String> e : values.entrySet()) {
s = s.replaceAll("\\[" + e.getKey() + "\\]", e.getValue());
}

您还可以遍历 String 并在映射中找到元素。但是这有点复杂,因为您需要解析搜索[]的 String。您可以使用使用 Pattern 和 Matcher 的正则表达式来完成此操作。

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

我真的不认为您需要使用模板引擎或类似的东西。您可以使用 String.format方法,如下所示:

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

不幸的是,上面提到的方法 String.format 只能从 Java 1.5开始使用(现在应该是相当标准的,但是你永远不知道)。您也可以使用 Java 的 类 MessageFormat来替换占位符。

它支持表单“{ number }”中的占位符,因此您的消息看起来像“ Hello {0} Please find  {1} ,它在{2}上到期”。这些字符串可以很容易地使用 ResourceBundles 进行外部化(例如,使用多个语言环境进行本地化)。替换将使用类 MessageFormat 的静态‘ format’方法完成:

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

在过去,我用 字符串模板Groovy Template解决了这类问题。

最终,决定是否使用模板引擎应该基于以下因素:

  • 在应用程序中是否有许多这样的模板?
  • 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?
  • 您是否需要将逻辑放入模板中的能力,比如基于变量中的值的条件文本?
  • 您是否需要在模板中包含其他模板的能力?

如果上述任何一种方法适用于您的项目,我会考虑使用模板引擎,其中大多数都提供了此功能,等等。

我用过

String template = "Hello %s Please find attached %s which is due on %s";


String message = String.format(template, name, invoiceNumber, dueDate);

可以使用模板库替换复杂的模板。

FreeMarker 是一个很好的选择。

Http://freemarker.sourceforge.net/

但是对于简单的任务,有一个简单的实用工具类可以帮助您。

org.apache.commons.lang3.text.StrSubstitutor

It is very powerful, customizable, and easy to use.

这个类接受一段文本并替换所有变量 变量的默认定义是 ${ variableName }。 前缀和后缀可以通过构造函数和 set 方法进行更改。

变量值通常从映射中解析,但也可以是 从系统属性解析,或者通过提供自定义变量解析 resolver.

For example, if you want to substitute system environment variable into a template string, 这是密码:

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

我的替换 ${ variable }样式标记的解决方案(灵感来自于这里的答案和 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();
}
System.out.println(MessageFormat.format("Hello {0}! You have {1} messages", "Join",10L));

产出: 您好加入! 您有10条留言”

With Apache Commons Library, you can simply use Stringutils.replace each:

public static String replaceEach(String text,
String[] searchList,
String[] replacementList)

来自 文件:

替换另一个字符串中所有出现的字符串。

传递给这个方法的空引用是 no-op,或者如果有“ search” 字符串”或“字符串替换”为空,该替换将被忽略。 这不会重复。要重复替换,请调用重载的 方法。

 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"

仅供参考

用科特林的新语言来说, 您可以在源代码中直接使用“字符串模板”, 没有第三方库或模板引擎需要做的变量替换。

这是语言本身的一个特点。

参见: https://kotlinlang.org/docs/reference/basic-types.html#string-templates

下面用从 Map 查找的值替换形式 <<VAR>>的变量

例如,使用以下输入字符串

BMI=(<<Weight>>/(<<Height>>*<<Height>>)) * 70
Hi there <<Weight>> was here

以及下列变量值

Weight, 42
Height, HEIGHT 51

输出以下内容

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

尽管没有被请求,但是您可以使用类似的方法用 application.properties 文件中的属性替换字符串中的变量,尽管这可能已经在进行了:

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

You can use Apache Commons 字符串替换程序:

例如:

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

产出:

 "The quick brown fox jumped over the lazy dog."

您还可以使用以下方法自定义前缀和后缀分隔符(上例中分别为 ${}) :

您还可以使用如下语法指定默认值:

String templateString = "The ${animal:giraffe} jumped over the ${target}.";

当没有提供 animal参数时,它将产生 "The giraffe jumped over the lazy dog."