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.


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


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

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


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.


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ą.

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ć :).


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);
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.