Remplacement de chaîne en java, similaire à un modèle de vélocité

96

Existe-t-il un Stringmécanisme de remplacement en Java, où je peux passer des objets avec un texte, et il remplace la chaîne au fur et à mesure qu'elle se produit.
Par exemple, le texte est:

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

Les objets que j'ai sont "user"et "site". Je veux remplacer les chaînes données à l'intérieur ${}par ses valeurs équivalentes à partir des objets. C'est la même chose que nous remplaçons des objets dans un modèle de vitesse.

Joe
la source
1
Remplacer où? Une classe? Un JSP? La chaîne a une méthode de format si vous venez de:String.format("Hello %s", username);
Droo
1
@Droo: Dans l'exemple, la chaîne est comme Hello ${user.name}, pas comme, Hello %sou Hello {0}.
Adeel Ansari
2
Si vous avez besoin de quelque chose qui ressemble à la vélocité et qui sent la vélocité, c'est peut-être la vélocité? :)
serg
@Droo: Ce n'est pas une classe. J'ai le texte ci-dessus dans une variable "String" et souhaite remplacer toutes les occurrences des chaînes à l'intérieur de $ {} par des valeurs dans les objets correspondants. par exemple, remplacez tout $ {user.name} par la propriété name dans l'objet "user".
Joe le
@serg: Oui, c'est un code de vitesse. et je veux supprimer la vitesse de mon code.
Joe le

Réponses:

142

Utilisation à StringSubstitutorpartir du texte Apache Commons.

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

Il le fera pour vous (et son 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.
la source
1
Devrait être Map<String, String> valuesMap = new HashMap<String, String>();.
andrewrjones
3
StrSubstitutorest désormais obsolète dans https://commons.apache.org/proper/commons-lang/ . Utilisateur https://commons.apache.org/proper/commons-text/ à la place
Lukuluba
7
StrSubstitutorobsolète depuis la 1.3, utilisez à la StringSubstitutorplace. Cette classe sera supprimée dans la version 2.0. La dépendance Gradle pour l'importation StringSubstitutorestorg.apache.commons:commons-text:1.4
Yurii Rabeshko
permet-il une substitution basée sur des conditions?
Gaurav
130

Jetez un œil à la java.text.MessageFormatclasse, MessageFormat prend un ensemble d'objets, les met en forme, puis insère les chaînes formatées dans le modèle aux endroits appropriés.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
RealHowTo
la source
10
Merci! Je savais que java devrait avoir un moyen intégré de le faire sans avoir à utiliser un moteur de modèle effrayant pour faire une chose aussi simple!
Joseph Rajeev Motha
2
On dirait que String.format peut faire tout ce que cela peut faire - stackoverflow.com/questions/2809633/…
Noumenon
6
+1. Sachez que formatprend également un Object...varargs afin que vous puissiez utiliser cette syntaxe plus concise là où c'est préférableformat("{0} world {1}", "Hello", "!");
davnicwil
Il convient de noter que MessageFormatne peut être utilisé de manière fiable que pour son homonyme, afficher les messages, pas pour la sortie où le formatage technique est important. Les nombres, par exemple, seront formatés selon les paramètres régionaux, les rendant invalides pour des utilisations techniques.
Marnes
22

Ma méthode préférée est String.format()parce que c'est un oneliner et ne nécessite pas de bibliothèques tierces:

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

Je l'utilise régulièrement, par exemple dans des messages d'exception comme:

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

Astuce: vous pouvez insérer autant de variables que vous le souhaitez car format()utilise Varargs

artgrohe
la source
Ceci est moins utile lorsque vous devez répéter le même argument plusieurs fois. Par exemple: 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);. D'autres réponses ici nécessitent de spécifier chaque argument une seule fois.
asherbar
2
@asherbar vous pouvez utiliser des spécificateurs d'index d'argument dans la chaîne de format, par exempleString.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 Je n'ai jamais su ça. Merci!
asherbar
20

J'ai jeté ensemble un petit test de mise en œuvre de cela. L'idée de base est d'appeler formatet de transmettre la chaîne de format, une carte des objets et les noms qu'ils ont localement.

Le résultat de ce qui suit est:

Mon chien s'appelle fido et Jane Doe le possède.

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

Remarque: cela ne compile pas en raison d'exceptions non gérées. Mais cela rend le code beaucoup plus facile à lire.

De plus, je n'aime pas que vous deviez construire la carte vous-même dans le code, mais je ne sais pas comment obtenir les noms des variables locales par programme. La meilleure façon de le faire est de ne pas oublier de mettre l'objet dans la carte dès que vous le créez.

L'exemple suivant produit les résultats souhaités à partir de votre exemple:

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

Je devrais également mentionner que je n'ai aucune idée de ce qu'est Velocity, donc j'espère que cette réponse est pertinente.

jjnguy
la source
C'est ce que je cherchais. Merci d'avoir donné une mise en œuvre. J'essayais et j'obtenais des résultats incorrects. :RÉ. Quoi qu'il en soit, cela a résolu mon problème.
Joe le
2
@Joe, content d'avoir pu aider. C'était une bonne excuse pour moi de m'entraîner enfin à écrire du code qui utilise la réflexion en Java.
jjnguy
6

Voici un aperçu de la façon dont vous pourriez procéder. Il devrait être relativement simple de l'implémenter en tant que code réel.

  1. Créez une carte de tous les objets qui seront référencés dans le modèle.
  2. Utilisez une expression régulière pour rechercher des références de variable dans le modèle et remplacez-les par leurs valeurs (voir étape 3). La classe Matcher sera utile pour rechercher et remplacer.
  3. Divisez le nom de la variable au niveau du point. user.namedeviendrait useret name. Recherchez userdans votre carte pour obtenir l'objet et utilisez la réflexion pour obtenir la valeur de à namepartir de l'objet. En supposant que vos objets ont des getters standard, vous chercherez une méthode getNameet vous l'appelerez.
casablanca
la source
Hé, je viens de voir cette réponse. C'est identique au mien. Veuillez me faire savoir ce que vous pensez de ma mise en œuvre.
jjnguy
5

Il existe quelques implémentations de langage d'expression qui le font pour vous, cela pourrait être préférable à l'utilisation de votre propre implémentation au fur et à mesure que vos besoins augmentent, voir par exemple JUEL et MVEL

J'aime et j'ai utilisé avec succès MVEL dans au moins un projet.

Voir également le Stackflow post JSTL / JSP EL (Expression Language) dans un contexte non JSP (autonome)

Christoffer Soop
la source
0

Il n'y a rien hors de la boîte qui soit comparable à la vitesse puisque la vitesse a été écrite pour résoudre exactement ce problème. La chose la plus proche que vous pouvez essayer est de regarder dans le formateur

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

Cependant, pour autant que je sache, le formateur a été créé pour fournir des options de formatage de type C en Java, de sorte qu'il ne vous gratte peut-être pas exactement, mais vous êtes invités à essayer :).

Mike Milkin
la source
0

J'utilise GroovyShell en java pour analyser le modèle avec 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
la source