String-Ersetzung in Java, ähnlich einer Velocity-Vorlage

96

Gibt es einen StringErsetzungsmechanismus in Java, bei dem ich Objekte mit einem Text übergeben kann und der die Zeichenfolge ersetzt, sobald sie auftritt?
Der Text lautet beispielsweise:

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

Die Objekte, die ich habe, sind "user"und "site". Ich möchte die darin angegebenen Zeichenfolgen ${}durch die entsprechenden Werte aus den Objekten ersetzen . Dies entspricht dem Ersetzen von Objekten in einer Geschwindigkeitsvorlage.

Joe
quelle
1
Wo ersetzen? Eine Klasse? Eine JSP? String hat eine Formatierungsmethode, wenn Sie nur:String.format("Hello %s", username);
Droo
1
@Droo: Im Beispiel ist string like Hello ${user.name}, not like Hello %soder Hello {0}.
Adeel Ansari
2
Wenn Sie etwas brauchen, das nach Geschwindigkeit aussieht und nach Geschwindigkeit riecht, ist es vielleicht Geschwindigkeit? :)
Serg
@Droo: Es ist keine Klasse. Ich habe den obigen Text in einer "String" -Variablen und möchte alle Vorkommen der Strings in $ {} durch Werte in den entsprechenden Objekten ersetzen. Ersetzen Sie beispielsweise alle $ {user.name} durch die Eigenschaft name im Objekt "user".
Joe
@serg: Ja, es ist ein Geschwindigkeitscode. und ich möchte die Geschwindigkeit aus meinem Code entfernen.
Joe

Antworten:

142

Verwendung StringSubstitutoraus Apache Commons Text.

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

Es wird es für Sie tun (und seine 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.
quelle
1
Sollte sein Map<String, String> valuesMap = new HashMap<String, String>();.
Andrewrjones
3
StrSubstitutorist jetzt in https://commons.apache.org/proper/commons-lang/ veraltet . Benutzer https://commons.apache.org/proper/commons-text/ stattdessen
Lukuluba
7
StrSubstitutorveraltet ab 1.3, StringSubstitutorstattdessen verwenden. Diese Klasse wird in 2.0 entfernt. Gradle Abhängigkeit für den Import StringSubstitutoristorg.apache.commons:commons-text:1.4
Yurii Rabeshko
erlaubt es eine bedingungsbasierte Substitution?
Gaurav
129

Schauen Sie sich die java.text.MessageFormatKlasse an. MessageFormat nimmt eine Reihe von Objekten, formatiert sie und fügt die formatierten Zeichenfolgen an den entsprechenden Stellen in das Muster ein.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
RealHowTo
quelle
10
Vielen Dank! Ich wusste, dass Java eine eingebaute Möglichkeit haben sollte, dies zu tun, ohne die verdammte Template-Engine verwenden zu müssen, um so eine einfache Sache zu tun!
Joseph Rajeev Motha
2
Scheint, als ob String.format alles kann, was dies kann - stackoverflow.com/questions/2809633/…
Noumenon
6
+1. Seien Sie sich bewusst, dass Sie formatauch Object...Varargs verwenden, damit Sie diese knappere Syntax verwenden können, wo dies vorzuziehen istformat("{0} world {1}", "Hello", "!");
davnicwil
Es sollte beachtet werden, dass MessageFormatnur für seinen Namensvetter, die Anzeige von Nachrichten und nicht für die Ausgabe, wenn die technische Formatierung wichtig ist, zuverlässig verwendet werden kann. Zahlen werden beispielsweise nach Gebietsschemaeinstellungen formatiert, wodurch sie für technische Zwecke ungültig werden.
Marnes
22

Mein bevorzugter Weg ist, String.format()weil es ein Oneliner ist und keine Bibliotheken von Drittanbietern erfordert:

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

Ich benutze dies regelmäßig, zB in Ausnahmemeldungen wie:

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

Hinweis: Sie können beliebig viele Variablen eingeben, da Varargsformat() verwendet wird

artgrohe
quelle
Dies ist weniger nützlich, wenn Sie dasselbe Argument mehrmals wiederholen müssen. ZB : 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);. Bei anderen Antworten muss jedes Argument nur einmal angegeben werden.
Ascherbar
2
@asherbar Sie können Argumentindexspezifizierer in der 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)
Formatzeichenfolge verwenden
@jazzpi Das habe ich nie gewusst. Vielen Dank!
Ascherbar
20

Ich habe eine kleine Testimplementierung davon zusammengestellt. Die Grundidee besteht darin, formatdie Formatzeichenfolge sowie eine Karte der Objekte und die Namen, die sie lokal haben , aufzurufen und zu übergeben.

Die Ausgabe der folgenden ist:

Mein Hund heißt Fido und Jane Doe besitzt ihn.

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

Hinweis: Dies wird aufgrund nicht behandelter Ausnahmen nicht kompiliert. Aber es macht den Code viel einfacher zu lesen.

Ich mag es auch nicht, dass Sie die Map selbst im Code erstellen müssen, aber ich weiß nicht, wie ich die Namen der lokalen Variablen programmgesteuert abrufen kann. Der beste Weg, dies zu tun, besteht darin, daran zu denken, das Objekt in die Karte einzufügen, sobald Sie es erstellen.

Das folgende Beispiel erzeugt die gewünschten Ergebnisse aus Ihrem Beispiel:

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

Ich sollte auch erwähnen, dass ich keine Ahnung habe, was Geschwindigkeit ist, also hoffe ich, dass diese Antwort relevant ist.

jjnguy
quelle
Das habe ich gesucht. Vielen Dank für eine Implementierung. Ich habe es versucht und falsche Ergebnisse erzielt. : D. Jedenfalls hat es mein Problem gelöst.
Joe
2
@ Joe, ich bin froh, dass ich helfen konnte. Es war eine gute Ausrede für mich, endlich das Schreiben von Code zu üben, der Reflexion in Java verwendet.
jjnguy
6

Hier ist ein Überblick darüber, wie Sie dies tun können. Es sollte relativ einfach sein, es als tatsächlichen Code zu implementieren.

  1. Erstellen Sie eine Karte aller Objekte, auf die in der Vorlage verwiesen wird.
  2. Verwenden Sie einen regulären Ausdruck, um Variablenreferenzen in der Vorlage zu finden und durch ihre Werte zu ersetzen (siehe Schritt 3). Die Matcher- Klasse ist praktisch zum Suchen und Ersetzen.
  3. Teilen Sie den Variablennamen am Punkt. user.namewürde werden userund name. Suchen Sie userin Ihrer Karte nach dem Objekt und verwenden Sie die Reflexion , um den Wert des nameObjekts zu ermitteln. Angenommen, Ihre Objekte haben Standard-Getter, suchen Sie nach einer Methode getNameund rufen sie auf.
casablanca
quelle
Heh, habe gerade diese Antwort gesehen. Es ist identisch mit meinem. Bitte teilen Sie mir Ihre Meinung zu meiner Implementierung mit.
jjnguy
5

Es gibt einige Expression Language-Implementierungen, die dies für Sie erledigen. Sie können der Verwendung Ihrer eigenen Implementierung vorzuziehen sein, wenn Ihre Anforderungen steigen, siehe beispielsweise JUEL und MVEL

Ich mag und habe MVEL in mindestens einem Projekt erfolgreich eingesetzt.

Siehe auch den Stackflow-Beitrag JSTL / JSP EL (Expression Language) in einem Nicht-JSP-Kontext (Standalone)

Christoffer Soop
quelle
0

Es gibt nichts, was mit der Geschwindigkeit vergleichbar wäre, da die Geschwindigkeit geschrieben wurde, um genau dieses Problem zu lösen. Das nächste, was Sie versuchen können, ist einen Blick in den Formatierer

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

Soweit ich weiß, wurde der Formatierer jedoch erstellt, um C-ähnliche Formatierungsoptionen in Java bereitzustellen, sodass er möglicherweise nicht genau Ihren Juckreiz zerkratzt, aber Sie können es gerne versuchen :).

Mike Milkin
quelle
0

Ich verwende GroovyShell in Java, um Vorlagen mit Groovy GString zu analysieren:

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
quelle