簡體   English   中英

如何替換 Java 字符串中的一組標記?

[英]How to replace a set of tokens in a 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();
}

我真的不認為你需要為此使用模板引擎或類似的東西。 您可以使用String.format方法,如下所示:

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

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

最有效的方法是使用匹配器不斷查找表達式並替換它們,然后將文本附加到字符串構建器:

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}”形式的占位符,因此您的消息看起來像“你好{0}請找到附件的{1},它將於{2}到期”。 這些字符串可以使用 ResourceBundles 輕松地外部化(例如,用於具有多個語言環境的本地化)。 替換將使用類 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));

您可以嘗試使用像 Apache Velocity 這樣的模板庫。

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.

您可以使用模板庫進行復雜的模板替換。

FreeMarker 是一個非常好的選擇。

http://freemarker.sourceforge.net/

但是對於簡單的任務,有一個簡單的實用程序類可以幫助您。

org.apache.commons.lang3.text.StrSubstitutor

它非常強大、可定制且易於使用。

該類采用一段文本並替換其中的所有變量。 變量的默認定義是 ${variableName}。 可以通過構造函數和 set 方法更改前綴和后綴。

變量值通常從映射解析,但也可以從系統屬性解析,或通過提供自定義變量解析器。

例如,如果要將系統環境變量替換為模板字符串,則代碼如下:

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

輸出:你好加入! 你有 10 條消息”

這取決於您要替換的實際數據所在的位置。 你可能有這樣的地圖:

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

包含所有可以替換的數據。 然后您可以遍歷地圖並更改字符串中的所有內容,如下所示:

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

您還可以遍歷 String 並在地圖中查找元素。 但這有點復雜,因為您需要解析搜索 [] 的字符串。 您可以使用 Pattern 和 Matcher 使用正則表達式來完成。

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

我替換 ${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();
}

使用 Apache Commons Library,您可以簡單地使用Stringutils.replaceEach

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

文檔

替換另一個字符串中所有出現的字符串。

傳遞給此方法的空引用是空操作,或者如果任何“搜索字符串”或“要替換的字符串”為空,則該替換將被忽略。 這將不再重復。 對於重復替換,調用重載方法。

 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"

供參考

在新語言 Kotlin 中,您可以直接在源代碼中使用“字符串模板”,無需第三方庫或模板引擎進行變量替換。

這是語言本身的一個特點。

請參閱: https : //kotlinlang.org/docs/reference/basic-types.html#string-templates

我用了

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

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

下面用從 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

這是代碼

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

您可以使用 Apache Commons StringSubstitutor

例如:

 // 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}.";

這將產生"The quick brown giraffe jumped over the lazy dog." 當沒有提供animal參數時。

過去,我用StringTemplateGroovy Templates解決了這類問題。

最終,是否使用模板引擎的決定應基於以下因素:

  • 您的應用程序中會有很多這樣的模板嗎?
  • 您是否需要能夠在不重新啟動應用程序的情況下修改模板?
  • 誰來維護這些模板? 參與該項目的 Java 程序員或業務分析師?
  • 您是否需要能夠將邏輯放入模板中,例如基於變量中的值的條件文本?
  • 您是否需要能夠在模板中包含其他模板?

如果上述任何一項適用於您的項目,我會考慮使用模板引擎,其中大多數提供此功能,等等。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM