Как заменить набор токенов в Java String?

У меня есть следующий шаблон String: "Hello [Name] Please find attached [Invoice Number] which is due on [Due Date]".

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

(Обратите внимание, что если переменная содержит токен, она НЕ должна быть заменена).


ИЗМЕНИТЬ

Благодаря @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();
}

Ответ 1

Самый эффективный способ - использовать помощник, чтобы постоянно находить выражения и заменять их, а затем добавлять текст в построитель строк:

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

Ответ 2

Я действительно не думаю, что вам нужно использовать механизм шаблонов или что-то в этом роде для этого. Вы можете использовать метод String.format, например:

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

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

Ответ 3

Вы можете попробовать использовать библиотеку шаблонов, такую ​​как скорость Apache.

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.

Ответ 4

К сожалению, удобный метод String.format, упомянутый выше, доступен только с Java 1.5 (который в наши дни должен быть довольно стандартным, но вы никогда не знаете). Вместо этого вы можете также использовать Java класс MessageFormat для замены заполнителей.

Он поддерживает заполнители в форме '{number}', поэтому ваше сообщение будет выглядеть так: "Привет {0}. Приложите {1}, который должен быть {2}". Эти строки могут быть легко реализованы с помощью ResourceBundles (например, для локализации с несколькими локалями). Замена будет выполнена с использованием метода static'format класса 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));

Ответ 5

Вы можете использовать библиотеку шаблонов для сложной замены шаблона.

FreeMarker - очень хороший выбор.

http://freemarker.sourceforge.net/

Но для простой задачи вам может помочь простой класс утилиты.

org.apache.commons.lang3.text.StrSubstitutor

Он очень мощный, настраиваемый и простой в использовании.

Этот класс берет фрагмент текста и заменяет все переменные внутри. Определение переменной по умолчанию - ${variableName}. Префикс и суффикс могут быть изменены с помощью конструкторов и методов набора.

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

Например, если вы хотите заменить переменную системной среды на строку шаблона, вот код:

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

Ответ 6

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

Вывод: Привет, присоединяйтесь! У вас есть 10 сообщений "

Ответ 7

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

Ответ 8

Это зависит от того, где находятся фактические данные, которые вы хотите заменить. У вас может быть такая Карта:

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.

Ответ 9

Мое решение для замены токенов стиля ${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();
}

Ответ 11

FYI

В новом языке Котлин, вы можете использовать "String Templates" в вашем исходном коде напрямую, никакая сторонняя библиотека или механизм шаблонов не должны выполнять замену переменных.

Это особенность самого языка.

См: https://kotlinlang.org/docs/reference/basic-types.html#string-templates

Ответ 12

В прошлом я решил эту проблему с StringTemplate и Groovy Шаблоны.

В конечном счете, решение использовать механизм шаблонов или нет должно основываться на следующих факторах:

  • Будет ли у вас много таких шаблонов в приложении?
  • Вам нужна возможность изменять шаблоны без перезапуска приложения?
  • Кто будет поддерживать эти шаблоны? Java-программист или бизнес-аналитик, участвующие в проекте?
  • Вам понадобится возможность поместить логику в свои шаблоны, например условный текст, основанный на значениях в переменных?
  • Вам нужно включить другие шаблоны в шаблон?

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

Ответ 13

Я использовал

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

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

Ответ 14

С библиотекой Apache Commons вы можете просто использовать Stringutils.replaceEach:

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

Из документация:

Заменяет все вхождения строк в другой строке.

Нулевая ссылка, переданная этому методу, - это no-op или if any search строка "или" строка для замены "имеет значение null, эта замена будет проигнорирована. Это не повторится. Для повторения замещений вызовите перегруженный Метод.

 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"