Как заменить токены в строке без StringTokenizer - PullRequest
9 голосов
/ 16 июля 2009

С учетом такой строки:

 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 действительно говорят, что вы должны предпочесть использование подхода regex. Как бы вы сделали это в решении на основе регулярных выражений?

Ответы [ 10 ]

11 голосов
/ 16 июля 2009

Спасибо всем за ответы!

Ответ Gizmo был определенно из коробки и отличным решением, но, к сожалению, не уместным, поскольку формат не может быть ограничен тем, что делает класс Formatter в этом случае.

Адам Пейнтер действительно понял суть дела, с правильным образцом.

У Питера Никса и Шона Брайта был отличный обходной путь, чтобы избежать всех сложностей регулярного выражения, но мне нужно было поднять некоторые ошибки, если были плохие токены, чего не произошло.

Но с точки зрения выполнения регулярных выражений и разумного цикла замены, это ответ, который я придумал (с небольшой помощью от Google и существующим ответом, включая комментарий Шона Брайта о том, как использовать group (1) против группа ()):

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 получает значение из карты, преобразует его в строку и выдает исключение, если его там нет.

Обратите внимание, что я изменил шаблон, чтобы найти пустые скобки (т. Е. {}), Так как это условие ошибки, явно проверенное.

EDIT: Обратите внимание, что appendReplacement не зависит от содержимого строки. Согласно javadocs, он распознает $ и обратную косую черту как специальный символ, поэтому я добавил некоторые экранирующие символы для обработки этого в приведенном выше примере. Не сделано с максимальной эффективностью, но в моем случае это не такая уж большая проблема, чтобы стоить пытаться микрооптимизировать создание струн.

Благодаря комментарию Алана М, это можно сделать еще проще, чтобы избежать проблем с специальными символами appendReplacement.

8 голосов
/ 16 июля 2009

Ну, я бы лучше использовал String.format () или лучше MessageFormat .

6 голосов
/ 16 июля 2009
String.replaceAll("{FIRST_NAME}", actualName);

Проверьте javadocs для этого здесь .

4 голосов
/ 16 июля 2009

Попробуйте это:

Примечание: Окончательное решение автора основано на этом образце и является гораздо более кратким.

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

}
3 голосов
/ 16 июля 2009

С импортом 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 снаружи, чтобы вы могли заменить все линии. Шаблон никогда не нужно перекомпилировать, и более эффективно избегать этого без необходимости.

2 голосов
/ 16 июля 2009

В зависимости от того, насколько нелепо сложна ваша строка, вы можете попробовать использовать более серьезный язык шаблонов строк, такой как 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());

Но это, вероятно, излишне, если вы хотите заменить только одно или два значения.

2 голосов
/ 16 июля 2009

Казалось бы, самое прямое - что-то вроде этого:

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

Он просматривает все ваши токены и заменяет каждый токен тем, что вам нужно, и использует стандартный метод String для замены, таким образом пропуская все разочарования RegEx.

1 голос
/ 16 июля 2009
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);
  }

}
0 голосов
/ 17 июля 2009

Обычно в таком случае мы использовали бы MessageFormat в сочетании с загрузкой фактического текста сообщения из ResourceBundle. Это дает вам дополнительное преимущество дружелюбия к G10N.

0 голосов
/ 16 июля 2009

Документы означают, что вы предпочитаете писать токенизатор на основе регулярных выражений, IIRC. Что может быть лучше для вас, так это стандартный поиск-замена регулярных выражений.

...