Как заменить набор токенов в строке Java?
у меня есть следующая строка шаблона:"Hello [Name] Please find attached [Invoice Number] which is due on [Due Date]"
.
У меня также есть строковые переменные для имени, номера счета и даты оплаты - каков наилучший способ заменить токены в шаблоне переменными?
(заметим, что если переменная содержит маркер, он не должен быть заменен).
EDIT
С благодарностью @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();
}
14 ответов:
наиболее эффективным способом было бы использовать сопоставитель, чтобы постоянно находить выражения и заменять их, а затем добавлять текст в строковый конструктор:
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
метод, например, так:String template = "Hello %s Please find attached %s which is due on %s"; String message = String.format(template, name, invoiceNumber, dueDate);
вы можете попробовать использовать библиотеку шаблонов, такую как Apache Velocity.
вот пример:
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.
к сожалению, удобная строка метода.формат, упомянутый выше, доступен только начиная с Java 1.5 (который должен быть довольно стандартным в настоящее время, но вы никогда не знаете). Вместо этого вы также можете использовать Java класс MessageFormat для замены заполнителей.
Он поддерживает заполнители в форме " {number}", поэтому ваше сообщение будет выглядеть как"Привет {0} пожалуйста, найдите прикрепленный {1}, который должен быть на {2}". Эти строки могут быть легко воплощены с помощью ResourceBundles (например, для локализации с несколькими локалями). Замена будет выполнена с использованием статического метода '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));
вы можете использовать библиотеку шаблонов для сложной замены шаблонов.
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); } }
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()); }
вы также можете перебирать строку и находить элементы на карте. Но это немного сложнее, потому что вам нужно разобрать строку, ищущую []. Вы могли бы сделать это с регулярное выражение с использованием шаблона и Сопоставителя.
мое решение для замены токенов стиля ${variable} (вдохновленное ответами здесь и весенним 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(); }
FYI
на новом языке Котлин, вы можете использовать" шаблоны строк " в исходном коде напрямую, никакая сторонняя библиотека или шаблонный движок не должны делать замену переменной.
Это особенность самого языка.
посмотреть: https://kotlinlang.org/docs/reference/basic-types.html#string-templates
в прошлом, я решил эту проблему с StringTemplate и Шаблонов Groovy.
в конечном счете, решение об использовании шаблонного движка или нет должно основываться на следующих факторах:
- будет ли у вас много таких шаблонов в приложении?
- вам нужна возможность изменять шаблоны без перезагрузки приложения?
- кто будет поддерживать эти шаблоны? Один Java-программист или бизнес-аналитик, участвующий в проекте?
- вам понадобится возможность поместить логику в ваши шаблоны, например, условный текст, основанный на значениях в переменных?
- вам понадобится возможность включать другие шаблоны в шаблон?
Если что-либо из вышеперечисленного относится к вашему проекту, я бы рассмотрел использование механизма шаблонов, большинство из которых обеспечивают эту функциональность и многое другое.
Я
String template = "Hello %s Please find attached %s which is due on %s"; String message = String.format(template, name, invoiceNumber, dueDate);
С Apache Commons Library, вы можете просто использовать Stringutils.replaceEach:
public static String replaceEach(String text, String[] searchList, String[] replacementList)
С документация:
заменяет все вхождения строки внутри другой строки.
нулевая ссылка, переданная этому методу, является no-op, или если есть " поиск строка " или "строка для замены" - это 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"