Заміна рядка в Java, подібно до шаблону швидкості


96

Чи існує якийсь Stringмеханізм заміни в Java, де я можу передавати об'єкти з текстом, і він замінює рядок у міру його виникнення.
Наприклад, текст:

Hello ${user.name},
    Welcome to ${site.name}. 

Предмети, які я маю, це "user"і "site". Я хочу замінити рядки, подані всередині, ${}на еквівалентні значення з об'єктів. Це те саме, що ми замінюємо об’єкти в шаблоні швидкості.


1
Замінити де? Клас? JSP? Рядок має метод форматування, якщо ви просто:String.format("Hello %s", username);
Droo

1
@Droo: У прикладі рядок подібний Hello ${user.name}, а не подібний Hello %sабо Hello {0}.
Adeel Ansari

2
Якщо вам потрібно щось, що схоже на швидкість і пахне швидкістю, можливо, це швидкість? :)
serg

@Droo: Це не клас. Я маю вищезазначений текст у змінній "String" і хочу замінити всі входження рядків всередині $ {} значеннями у відповідних об'єктах. наприклад, замінити всі $ {user.name} властивістю name в об'єкті "user".
Джо

@serg: Так, це код швидкості. і я хочу видалити швидкість зі свого коду.
Джо

Відповіді:


142

Використання StringSubstitutorз тексту Apache Commons.

https://commons.apache.org/proper/commons-text/

Він зробить це за вас (і його відкритий код ...)

 Map<String, String> valuesMap = new HashMap<String, String>();
 valuesMap.put("animal", "quick brown fox");
 valuesMap.put("target", "lazy dog");
 String templateString = "The ${animal} jumped over the ${target}.";
 StringSubstitutor sub = new StringSubstitutor(valuesMap);
 String resolvedString = sub.replace(templateString);


1
Повинно бути Map<String, String> valuesMap = new HashMap<String, String>();.
andrewrjones

3
StrSubstitutorтепер застаріло в https://commons.apache.org/proper/commons-lang/ . Користувач https://commons.apache.org/proper/commons-text/ замість цього
Lukuluba

7
StrSubstitutorзастарілий станом на 1.3, використовуйте StringSubstitutorзамість цього. Цей клас буде видалено через 2.0. Залежність від Gradle для імпорту StringSubstitutorєorg.apache.commons:commons-text:1.4
Юрій Рабешко

чи дозволяє це заміщення на основі умов?
Gaurav

130

Погляньте на java.text.MessageFormatклас, MessageFormat бере набір об’єктів, форматує їх, а потім вставляє відформатовані рядки в шаблон у відповідних місцях.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);

10
Дякую! Я знав, що у Java має бути вбудований спосіб зробити це без необхідності використовувати дивовижний механізм шаблонів, щоб зробити таку просту справу!
Джозеф Раджеєв Мота

2
Здається, String.format може робити все, що може - stackoverflow.com/questions/2809633/…
Noumenon

6
+1. Майте на увазі, formatтакож бере Object...varargs, щоб ви могли використовувати цей більш стислий синтаксис, де бажаноformat("{0} world {1}", "Hello", "!");
davnicwil

Слід зазначити, що MessageFormatможе бути надійно використаний лише для його тезки, відображення повідомлень, а не для виводу, коли технічне форматування має значення. Наприклад, номери будуть відформатовані відповідно до локальних налаштувань, роблячи їх недійсними для технічного використання.
Marnes

22

Я String.format()віддаю перевагу тому, що це однолінійний і не вимагає сторонніх бібліотек:

String message = String.format("Hello! My name is %s, I'm %s.", name, age); 

Я використовую це регулярно, наприклад, у повідомленнях про винятки, таких як:

throw new Exception(String.format("Unable to login with email: %s", email));

Підказка: Ви можете помістити як багато змінних , як вам подобається , тому що format()використання списки параметри


Це менш корисно, коли вам потрібно повторити один і той же аргумент більше одного разу. Наприклад: String.format("Hello! My name is %s, I'm %s. Why is my name %s you ask? Well I'm only %s years old so I don't know", name, age, name, age);. Інші відповіді тут вимагають вказувати кожен аргумент лише один раз.
ашербар

2
@asherbar ви можете використовувати специфікатори індексу аргументів у рядку формату, наприкладString.format("Hello! My name is %1$s, I'm %2$s. Why is my name %1$s you ask? Well I'm only %2$s years old so I don't know", name, age)
jazzpi

@jazzpi Я ніколи цього не знав. Дякую!
ашербар

20

Я зібрав невеличку тестову реалізацію цього. Основна ідея полягає у виклику formatта передачі у форматі рядка, і карти об’єктів, і імен, які вони мають локально.

Результатом є:

Мою собаку звуть фідо, і Джейн Доу є її власником.

public class StringFormatter {

    private static final String fieldStart = "\\$\\{";
    private static final String fieldEnd = "\\}";

    private static final String regex = fieldStart + "([^}]+)" + fieldEnd;
    private static final Pattern pattern = Pattern.compile(regex);

    public static String format(String format, Map<String, Object> objects) {
        Matcher m = pattern.matcher(format);
        String result = format;
        while (m.find()) {
            String[] found = m.group(1).split("\\.");
            Object o = objects.get(found[0]);
            Field f = o.getClass().getField(found[1]);
            String newVal = f.get(o).toString();
            result = result.replaceFirst(regex, newVal);
        }
        return result;
    }

    static class Dog {
        public String name;
        public String owner;
        public String gender;
    }

    public static void main(String[] args) {
        Dog d = new Dog();
        d.name = "fido";
        d.owner = "Jane Doe";
        d.gender = "him";
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("d", d);
        System.out.println(
           StringFormatter.format(
                "My dog is named ${d.name}, and ${d.owner} owns ${d.gender}.", 
                map));
    }
}

Примітка: Це не компілюється через необроблені винятки. Але це значно полегшує читання коду.

Крім того, мені не подобається, що вам доводиться самостійно будувати карту в коді, але я не знаю, як програмно отримувати імена локальних змінних. Найкращий спосіб зробити це - пам’ятати про те, щоб помістити об’єкт на карту, як тільки ви його створите.

Наступний приклад дає результати, які ви хочете від свого прикладу:

public static void main(String[] args) {
    Map<String, Object> map = new HashMap<String, Object>();
    Site site = new Site();
    map.put("site", site);
    site.name = "StackOverflow.com";
    User user = new User();
    map.put("user", user);
    user.name = "jjnguy";
    System.out.println(
         format("Hello ${user.name},\n\tWelcome to ${site.name}. ", map));
}

Слід також зазначити, що я поняття не маю, що таке швидкість, тому сподіваюся, що ця відповідь є доречною.


Це те, що я шукав. Дякуємо за реалізацію. Я намагався це отримати і отримати неправильні результати. : D. У будь-якому випадку це вирішило мою проблему.
Джо

2
@Joe, рада, що могла допомогти. Для мене це був хороший привід нарешті потренуватися в написанні коду, який використовує відображення на Java.
jjnguy

6

Ось схема того, як ви могли б робити це. Це має бути відносно просто застосовувати його як фактичний код.

  1. Створіть карту всіх об’єктів, на які буде посилатися в шаблоні.
  2. Використовуйте регулярний вираз, щоб знайти посилання на змінні в шаблоні та замінити їх на їх значення (див. Крок 3). Клас Matcher стане в нагоді для пошуку та заміни.
  3. Розділіть ім'я змінної на крапку. user.nameстане userі name. Подивіться userна свою карту, щоб отримати об’єкт, і використовуйте відображення, щоб отримати значення nameоб’єкта. Припускаючи, що ваші об'єкти мають стандартні геттери, ви будете шукати метод getNameі викликати його.

Хе, щойно побачив цю відповідь. Він ідентичний моєму. Будь ласка, повідомте мені, що ви думаєте про моє впровадження.
jjnguy

5

Існує кілька реалізацій мови виразів, яка робить це для вас, і це може бути кращим, ніж використання власної реалізації, якщо ваші вимоги зростають, див., Наприклад, JUEL та MVEL

Мені подобається і успішно використовую MVEL принаймні в одному проекті.

Також див. Пост Stackflow JSTL / JSP EL (мова виразів) у не JSP (автономному) контексті


0

Немає нічого звичайного, що можна порівняти зі швидкістю, оскільки швидкість була написана для вирішення саме цієї задачі. Найближче, що ви можете спробувати, це заглянути у Форматтер

http://cupi2.uniandes.edu.co/site/images/recursos/javadoc/j2se/1.5.0/docs/api/java/util/Formatter.html

Однак форматор, наскільки мені відомо, був створений, щоб забезпечити варіанти форматування на зразок C на Java, тому він може точно не подряпати ваш свербіж, але ви можете спробувати :).


0

Я використовую GroovyShell в Java, щоб проаналізувати шаблон за допомогою Groovy GString:

Binding binding = new Binding();
GroovyShell gs = new GroovyShell(binding);
// this JSONObject can also be replaced by any Java Object
JSONObject obj = new JSONObject();
obj.put("key", "value");
binding.setProperty("obj", obj)
String str = "${obj.key}";
String exp = String.format("\"%s\".toString()", str);
String res = (String) gs.evaluate(exp);
// value
System.out.println(str);
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.