9

给定这样的字符串:

 Hello {FIRST_NAME}, this is a personalized message for you.

其中 FIRST_NAME 是一个任意标记(传递给方法的映射中的键),编写一个例程将该字符串转换为:

Hello Jim, this is a personalized message for you.

给定一个带有条目 FIRST_NAME -> Jim 的地图。

看起来 StringTokenizer 是最直接的方法,但 Javadocs 确实说您应该更喜欢使用正则表达式方法。您将如何在基于正则表达式的解决方案中做到这一点?

4

10 回答 10

11

谢谢大家的回答!

Gizmo 的答案绝对是开箱即用的,并且是一个很好的解决方案,但不幸的是,格式不适合 Formatter 类在这种情况下所做的事情。

亚当佩恩特用正确的模式真正抓住了问题的核心。

Peter Nix 和 Sean Bright 有一个很好的解决方法来避免正则表达式的所有复杂性,但是如果有错误的标记,我需要提出一些错误,但没有这样做。

但就做一个正则表达式和一个合理的替换循环而言,这是我想出的答案(在谷歌和现有答案的帮助下,包括 Sean Bright 关于如何使用 group(1) vs group() ):

private static Pattern tokenPattern = Pattern.compile("\\{([^}]*)\\}");

public static String process(String template, Map<String, Object> params) {
    StringBuffer sb = new StringBuffer();
    Matcher myMatcher = tokenPattern.matcher(template);
    while (myMatcher.find()) {
        String field = myMatcher.group(1);
        myMatcher.appendReplacement(sb, "");
        sb.append(doParameter(field, params));
   }
    myMatcher.appendTail(sb);
    return sb.toString();
}

doParameter 从地图中获取值并将其转换为字符串,如果不存在则抛出异常。

另请注意,我更改了模式以查找空括号(即 {}),因为这是明确检查的错误条件。

编辑:请注意, appendReplacement 与字符串的内容无关。根据 javadocs,它将 $ 和反斜杠识别为特殊字符,因此我在上面的示例中添加了一些转义来处理它。没有以最注重性能的方式完成,但在我的情况下,这还不够大,不值得尝试对字符串创建进行微优化。

感谢 Alan M 的评论,这可以更简单地避免 appendReplacement 的特殊字符问题。

于 2009-07-16T17:57:33.560 回答
8

好吧,我宁愿使用 String.format(),或者更好的MessageFormat

于 2009-07-16T16:50:36.923 回答
6
String.replaceAll("{FIRST_NAME}", actualName);

在这里查看 javadocs 。

于 2009-07-16T16:47:04.950 回答
4

试试这个:

注意:作者的最终解决方案基于此示例,并且更加简洁。

public class TokenReplacer {

    private Pattern tokenPattern;

    public TokenReplacer() {
        tokenPattern = Pattern.compile("\\{([^}]+)\\}");
    }

    public String replaceTokens(String text, Map<String, String> valuesByKey) {
        StringBuilder output = new StringBuilder();
        Matcher tokenMatcher = tokenPattern.matcher(text);

        int cursor = 0;
        while (tokenMatcher.find()) {
            // A token is defined as a sequence of the format "{...}".
            // A key is defined as the content between the brackets.
            int tokenStart = tokenMatcher.start();
            int tokenEnd = tokenMatcher.end();
            int keyStart = tokenMatcher.start(1);
            int keyEnd = tokenMatcher.end(1);

            output.append(text.substring(cursor, tokenStart));

            String token = text.substring(tokenStart, tokenEnd);
            String key = text.substring(keyStart, keyEnd);

            if (valuesByKey.containsKey(key)) {
                String value = valuesByKey.get(key);
                output.append(value);
            } else {
                output.append(token);
            }

            cursor = tokenEnd;
        }
        output.append(text.substring(cursor));

        return output.toString();
    }

}
于 2009-07-16T16:52:08.387 回答
3

使用导入 java.util.regex.*:

Pattern p = Pattern.compile("{([^{}]*)}");
Matcher m = p.matcher(line);  // line being "Hello, {FIRST_NAME}..."
while (m.find) {
  String key = m.group(1);
  if (map.containsKey(key)) {
    String value= map.get(key);
    m.replaceFirst(value);
  }
}

因此,推荐使用正则表达式,因为它可以轻松识别字符串中需要替换的位置,以及提取键名进行替换。它比破坏整个字符串更有效。

您可能希望在内部循环使用 Matcher 线,在外部使用 Pattern 线,这样您就可以替换所有线。该模式永远不需要重新编译,并且避免不必要地这样做会更有效。

于 2009-07-16T16:53:23.440 回答
2

最直接的方法似乎是这样的:

public static void main(String[] args) {
    String tokenString = "Hello {FIRST_NAME}, this is a personalized message for you.";
    Map<String, String> tokenMap = new HashMap<String, String>();
    tokenMap.put("{FIRST_NAME}", "Jim");
    String transformedString = tokenString;
    for (String token : tokenMap.keySet()) {
        transformedString = transformedString.replace(token, tokenMap.get(token));
    }
    System.out.println("New String: " + transformedString);
}

它循环遍历您的所有标记并用您需要的内容替换每个标记,并使用标准字符串方法进行替换,从而跳过整个 RegEx 的挫折。

于 2009-07-16T16:52:50.773 回答
2

根据您的字符串的复杂程度,您可以尝试使用更严肃的字符串模板语言,例如 Velocity。在 Velocity 的情况下,你会做这样的事情:

Velocity.init();
VelocityContext context = new VelocityContext();
context.put( "name", "Bob" );
StringWriter output = new StringWriter();
Velocity.evaluate( context, output, "", 
      "Hello, #name, this is a personalized message for you.");
System.out.println(output.toString());

但是,如果您只想替换一两个值,这可能是矫枉过正。

于 2009-07-16T16:56:44.010 回答
1
import java.util.HashMap;

public class ReplaceTest {

  public static void main(String[] args) {
    HashMap<String, String> map = new HashMap<String, String>();

    map.put("FIRST_NAME", "Jim");
    map.put("LAST_NAME",  "Johnson");
    map.put("PHONE",      "410-555-1212");

    String s = "Hello {FIRST_NAME} {LAST_NAME}, this is a personalized message for you.";

    for (String key : map.keySet()) {
      s = s.replaceAll("\\{" + key + "\\}", map.get(key));
    }

    System.out.println(s);
  }

}
于 2009-07-16T17:06:16.347 回答
0

文档意味着您应该更喜欢编写基于正则表达式的标记器 IIRC。对您来说更好的是标准的正则表达式搜索替换。

于 2009-07-16T16:42:15.223 回答
0

通常我们会在这种情况下使用 MessageFormat,并从 ResourceBundle 加载实际的消息文本。这为您提供了对 G10N 友好的额外好处。

于 2009-07-17T04:47:10.960 回答