Sostituzione di stringhe in java, simile a un modello di velocità


96

Esiste un Stringmeccanismo di sostituzione in Java, in cui posso passare oggetti con un testo e sostituisce la stringa non appena si verifica.
Ad esempio, il testo è:

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

Gli oggetti che ho sono "user"e "site". Voglio sostituire le stringhe fornite all'interno ${}con i suoi valori equivalenti dagli oggetti. È lo stesso che sostituiamo gli oggetti in un modello di velocità.


1
Sostituisci dove? Una classe? Un JSP? La stringa ha un metodo di formattazione se:String.format("Hello %s", username);
Droo

1
@Droo: nell'esempio, la stringa è come Hello ${user.name}, non come Hello %so Hello {0}.
Adeel Ansari

2
Se hai bisogno di qualcosa che assomigli alla velocità e odori di velocità, forse è la velocità? :)
serg

@Droo: non è una classe. Ho il testo sopra in una variabile "String" e voglio sostituire tutte le occorrenze delle stringhe all'interno di $ {} con valori negli oggetti corrispondenti. per esempio, sostituisci tutto $ {user.name} con la proprietà name nell'oggetto "user".
Joe

@serg: Sì, è un codice di velocità. e voglio rimuovere la velocità dal mio codice.
Joe

Risposte:


142

Usa StringSubstitutorda Apache Commons Text.

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

Lo farà per te (e il suo 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
Dovrebbe essere Map<String, String> valuesMap = new HashMap<String, String>();.
andrewrjones


7
StrSubstitutordeprecato dalla 1.3, usa StringSubstitutorinvece. Questa classe verrà rimossa nella 2.0. La dipendenza StringSubstitutororg.apache.commons:commons-text:1.4
graduale

consente la sostituzione basata sulle condizioni?
Gaurav

130

Dai un'occhiata alla java.text.MessageFormatclasse, MessageFormat prende un insieme di oggetti, li formatta, quindi inserisce le stringhe formattate nel pattern nei punti appropriati.

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

10
Grazie! Sapevo che java avrebbe dovuto avere un modo integrato per farlo senza dover utilizzare un fottuto motore di modelli per fare una cosa così semplice!
Joseph Rajeev Motha

2
Sembra String.Format può fare qualsiasi cosa questo può fare - stackoverflow.com/questions/2809633/...
Noumeno

6
+1. Tieni presente che formatrichiede anche un Object...vararg, quindi puoi usare questa sintassi più format("{0} world {1}", "Hello", "!");
concisa

Va notato che MessageFormatpuò essere utilizzato in modo affidabile solo per il suo omonimo, messaggi di visualizzazione, non per l'output in cui la formattazione tecnica è importante. I numeri, ad esempio, verranno formattati in base alle impostazioni locali, rendendoli non validi per usi tecnici.
Marnes

22

Il mio modo preferito è String.format()perché è un oneliner e non richiede librerie di terze parti:

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

Lo uso regolarmente, ad esempio in messaggi di eccezione come:

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

Suggerimento: puoi inserire tutte le variabili che desideri perché format() utilizza Varargs


Ciò è meno utile quando è necessario ripetere lo stesso argomento più di una volta. Ad esempio: 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);. Altre risposte qui richiedono di specificare ogni argomento solo una volta.
asherbar

2
@asherbar puoi utilizzare gli specificatori di indice degli argomenti nella stringa di formato, ad esempioString.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 non l'ho mai saputo. Grazie!
Asherbar

20

Ho messo insieme una piccola implementazione di prova di questo. L'idea di base è chiamare formate passare la stringa di formato, una mappa di oggetti e i nomi che hanno localmente.

L'output di quanto segue è:

Il mio cane si chiama fido e Jane Doe lo possiede.

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

Nota: non viene compilato a causa di eccezioni non gestite. Ma rende il codice molto più facile da leggere.

Inoltre, non mi piace che tu debba costruire la mappa da solo nel codice, ma non so come ottenere i nomi delle variabili locali in modo programmatico. Il modo migliore per farlo è ricordarsi di mettere l'oggetto nella mappa non appena lo si crea.

L'esempio seguente produce i risultati desiderati dal tuo esempio:

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

Dovrei anche dire che non ho idea di cosa sia Velocity, quindi spero che questa risposta sia pertinente.


Questo è quello che stavo cercando. Grazie per aver fornito un'implementazione. Ci stavo provando e ottenendo risultati errati. : D. Comunque ha risolto il mio problema.
Joe

2
@ Joe, felice di aver potuto aiutare. È stata una buona scusa per me per esercitarmi finalmente a scrivere del codice che utilizza la riflessione in Java.
jjnguy

6

Ecco uno schema di come potresti fare questo. Dovrebbe essere relativamente semplice implementarlo come codice effettivo.

  1. Crea una mappa di tutti gli oggetti a cui verrà fatto riferimento nel modello.
  2. Utilizzare un'espressione regolare per trovare i riferimenti alle variabili nel modello e sostituirli con i relativi valori (vedere il passaggio 3). La classe Matcher tornerà utile per trovare e sostituire.
  3. Dividi il nome della variabile in corrispondenza del punto. user.namediventerebbe usere name. Cerca usernella mappa per ottenere l'oggetto e usa la riflessione per ottenere il valore di namedall'oggetto. Supponendo che i tuoi oggetti abbiano getter standard, cercherai un metodo getNamee lo invocherai.

Eh, ho appena visto questa risposta. È identico al mio. Per favore fatemi sapere cosa ne pensate della mia implementazione.
jjnguy


0

Non c'è niente fuori dagli schemi che sia paragonabile alla velocità poiché la velocità è stata scritta per risolvere esattamente quel problema. La cosa più vicina che puoi provare è guardare nel Formatter

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

Tuttavia, per quanto ne so, il formattatore è stato creato per fornire opzioni di formattazione simili a C in Java, quindi potrebbe non graffiare esattamente il tuo prurito ma sei il benvenuto a provare :).


0

Uso GroovyShell in java per analizzare il modello con 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);
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.