Zastępowanie ciągów w javie, podobnie jak w szablonie prędkości

96

Czy Stringw Javie istnieje mechanizm zastępujący, w którym mogę przekazywać obiekty tekstem i zastępuje ciąg, gdy się pojawia.
Na przykład tekst to:

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

Obiekty, które mam, to "user"i "site". Chcę zamienić ciągi podane w środku na ${}ich odpowiedniki z obiektów. To samo, co zamieniamy obiekty w szablonie prędkości.

Joe
źródło
1
Zastąpić gdzie? Klasa? JSP? Ciąg ma metodę formatowania, jeśli po prostu:String.format("Hello %s", username);
Droo,
1
@Droo: W tym przykładzie string to like Hello ${user.name}, not like Hello %slub Hello {0}.
Adeel Ansari,
2
Jeśli potrzebujesz czegoś, co wygląda jak prędkość i pachnie jak prędkość, może to jest prędkość? :)
serg
@Droo: To nie jest klasa. Mam powyższy tekst w zmiennej „String” i chcę zamienić wszystkie wystąpienia ciągów wewnątrz $ {} na wartości w odpowiednich obiektach. na przykład zamień wszystkie $ {user.name} na właściwość name w obiekcie "user".
Joe,
@serg: Tak, to kod prędkości. i chcę usunąć prędkość z mojego kodu.
Joe,

Odpowiedzi:

142

Użyj StringSubstitutorz Apache Commons Text.

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

Zrobi to za Ciebie (i jego open source ...)

 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);
JH.
źródło
1
Powinien być Map<String, String> valuesMap = new HashMap<String, String>();.
andrewrjones
3
StrSubstitutorjest teraz przestarzała w https://commons.apache.org/proper/commons-lang/ . Zamiast tego użytkownik https://commons.apache.org/proper/commons-text/
Lukuluba
7
StrSubstitutorprzestarzałe od 1.3, użyj StringSubstitutorzamiast tego. Ta klasa zostanie usunięta w wersji 2.0. Zależność Gradle dla importowania StringSubstitutortoorg.apache.commons:commons-text:1.4
Yurii Rabeshko
czy pozwala na zastępowanie na podstawie warunków?
Gaurav
131

Przyjrzyj się java.text.MessageFormatklasie, MessageFormat pobiera zestaw obiektów, formatuje je, a następnie wstawia sformatowane ciągi do wzorca w odpowiednich miejscach.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
RealHowTo
źródło
10
Dzięki! Wiedziałem, że java powinna mieć wbudowany sposób na zrobienie tego bez konieczności korzystania z niesamowitego silnika szablonów, aby zrobić tak prostą rzecz!
Joseph Rajeev Motha
2
Wygląda na to, że String.format może zrobić wszystko, co to może zrobić - stackoverflow.com/questions/2809633/ ...
Noumenon
6
+1. Bądź świadomy formatbierze również Object...varargs więc można użyć tej składni bardziej lakoniczny gdzie preferowaneformat("{0} world {1}", "Hello", "!");
davnicwil
Należy zauważyć, że MessageFormatmoże być niezawodnie używany tylko do jego nazwy, wyświetlania komunikatów, a nie do wyjścia, w którym ważne jest formatowanie techniczne. Na przykład liczby zostaną sformatowane zgodnie z ustawieniami regionalnymi, co sprawi, że będą nieprawidłowe do zastosowań technicznych.
Marnes
23

Preferuję sposób, String.format()ponieważ jest to oneliner i nie wymaga bibliotek innych firm:

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

Używam tego regularnie, np. W komunikatach o wyjątkach, takich jak:

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

Wskazówka: możesz wprowadzić tyle zmiennych, ile chcesz, ponieważ format()używa Varargs

artgrohe
źródło
Jest to mniej przydatne, gdy trzeba powtórzyć ten sam argument więcej niż raz. Np 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);. : . Inne odpowiedzi wymagają podania każdego argumentu tylko raz.
asherbar
2
@asherbar możesz użyć specyfikatorów indeksu argumentów w ciągu formatu, np.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 Nigdy tego nie wiedziałem. Dzięki!
asherbar
20

Wrzuciłem razem małą testową implementację tego. Podstawową ideą jest wywołanie formati przekazanie ciągu formatu oraz mapy obiektów i nazw, które mają lokalnie.

Wynik następujących działań to:

Mój pies ma na imię fido, a Jane Doe jest jego właścicielem.

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

Uwaga: nie kompiluje się z powodu nieobsłużonych wyjątków. Ale dzięki temu kod jest znacznie łatwiejszy do odczytania.

Nie podoba mi się również to, że musisz sam konstruować mapę w kodzie, ale nie wiem, jak programowo uzyskać nazwy zmiennych lokalnych. Najlepszym sposobem na to jest pamiętanie o umieszczeniu obiektu na mapie zaraz po jego utworzeniu.

Poniższy przykład daje wyniki, które chcesz z przykładu:

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

Powinienem również wspomnieć, że nie mam pojęcia, czym jest Velocity, więc mam nadzieję, że ta odpowiedź jest odpowiednia.

jjnguy
źródło
To jest to, czego szukałem. Dziękujemy za udostępnienie realizacji. Próbowałem tego i otrzymałem nieprawidłowe wyniki. :RE. W każdym razie to rozwiązało mój problem.
Joe,
2
@Joe, cieszę się, że mogłem pomóc. To była dla mnie dobra wymówka, aby wreszcie poćwiczyć pisanie kodu wykorzystującego odbicie w Javie.
jjnguy,
6

Oto zarys tego, jak możesz się do tego zabrać. Wdrożenie go jako rzeczywistego kodu powinno być stosunkowo proste.

  1. Utwórz mapę wszystkich obiektów, do których będą odniesienia w szablonie.
  2. Użyj wyrażenia regularnego, aby znaleźć odniesienia do zmiennych w szablonie i zastąpić je ich wartościami (patrz krok 3). Matcher klasa będzie przydatna dla znalezienia i zamiany.
  3. Podziel nazwę zmiennej na kropkę. user.namestanie się useri name. Spójrz userna mapę, aby uzyskać obiekt i użyj odbicia, aby uzyskać wartość namez obiektu. Zakładając, że twoje obiekty mają standardowe metody pobierające, będziesz szukać metody getNamei wywołać ją.
Casablanka
źródło
Heh, właśnie zobaczyłem tę odpowiedź. Jest identyczny z moim. Daj mi znać, co myślisz o mojej realizacji.
jjnguy,
0

Nie ma nic poza pudełkiem, co byłoby porównywalne z prędkością, ponieważ prędkość została napisana, aby rozwiązać dokładnie ten problem. Najbliższą rzeczą, jaką możesz spróbować, jest zajrzenie do programu Formatter

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

Jednak program formatujący, o ile wiem, został stworzony, aby zapewnić opcje formatowania podobne do C w Javie, więc może nie zarysować dokładnie twojego swędzenia, ale możesz spróbować :).

Mike Milkin
źródło
0

Używam GroovyShell w java do parsowania szablonu z 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);
Kan
źródło