Espaces réservés nommés dans la mise en forme de chaîne

175

En Python, lors du formatage d'une chaîne, je peux remplir les espaces réservés par nom plutôt que par position, comme ça:

print "There's an incorrect value '%(value)s' in column # %(column)d" % \
  { 'value': x, 'column': y }

Je me demande si cela est possible en Java (espérons-le, sans bibliothèques externes)?

Andy
la source
Vous pouvez étendre MessageFormat et implémenter la fonctionnalité de mappage des variables aux index.
vpram86
1
Un peu d'histoire: Java a principalement copié C / C ++ à ce sujet alors qu'il tentait d'attirer les développeurs du monde C ++ où %sétait une pratique courante. fr.wikipedia.org/wiki/Printf_format_string#History Notez également que certains IDE et FindBugs peuvent détecter automatiquement les décomptes de% s et% d, mais je préférerais toujours les champs nommés.
Christophe Roussy

Réponses:

143

StrSubstitutor of jakarta commons lang est un moyen léger de le faire à condition que vos valeurs soient déjà formatées correctement.

http://commons.apache.org/proper/commons-lang/javadocs/api-3.1/org/apache/commons/lang3/text/StrSubstitutor.html

Map<String, String> values = new HashMap<String, String>();
values.put("value", x);
values.put("column", y);
StrSubstitutor sub = new StrSubstitutor(values, "%(", ")");
String result = sub.replace("There's an incorrect value '%(value)' in column # %(column)");

Ce qui précède entraîne:

"Il y a une valeur incorrecte '1' dans la colonne n ° 2"

Lorsque vous utilisez Maven, vous pouvez ajouter cette dépendance à votre pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>
schup
la source
2
J'ai trouvé décevant que la bibliothèque ne lance pas si les clés ne sont pas trouvées, cependant, si vous utilisez la syntaxe par défaut ( ${arg}) au lieu de la syntaxe personnalisée ci-dessus ( %(arg)), le regex ne se compilera pas, ce qui est l'effet souhaité.
John Lehmann
2
Vous pouvez définir un VariableResolver personnalisé et lancer une exception si la clé n'est pas présente dans la carte.
Mene
7
Ancien fil de discussion, mais à partir de la version 3.6, le paquetage de texte était obsolète au profit de commons-text. commons.apache.org/proper/commons-text
Jeff Walker
74

pas tout à fait, mais vous pouvez utiliser MessageFormat pour référencer une valeur plusieurs fois:

MessageFormat.format("There's an incorrect value \"{0}\" in column # {1}", x, y);

Ce qui précède peut également être fait avec String.format (), mais je trouve un nettoyeur de syntaxe messageFormat si vous avez besoin de créer des expressions complexes, et vous n'avez pas besoin de vous soucier du type de l'objet que vous mettez dans la chaîne

Giladbu
la source
Je ne sais pas pourquoi vous ne pouvez pas, la position dans la chaîne n'est pas importante, seulement la position dans la liste des arguments, ce qui en fait un problème de changement de nom. Vous connaissez le nom des clés, ce qui signifie que vous pouvez décider d'une position pour une clé dans la liste des arguments. à partir de maintenant, la valeur sera appelée 0 et la colonne 1: MessageeFormat.format ("Il y a une valeur incorrecte \" {0} \ "dans la colonne # {1}, l'utilisation de {0} comme valeur peut causer de nombreux problèmes", valueMap .get ('valeur'), ​​valueMap.get ('colonne'));
giladbu
1
Merci pour un indice, cela m'a aidé à écrire une fonction simple qui fait exactement ce que je veux (je l'ai mis ci-dessous).
Andy
1
D'accord, la syntaxe est beaucoup plus propre. Dommage que MessageFormat ait son propre esprit en ce qui concerne le formatage des valeurs numériques.
Kees de Kooter
Et il semble ignorer les espaces réservés entourés de guillemets simples.
Kees de Kooter
MessageFormatest génial mais encombrant pour un contenu json relativement volumineux
EliuX
32

Un autre exemple d'Apache Common StringSubstitutor pour un espace réservé nommé simple.

String template = "Welcome to {theWorld}. My name is {myName}.";

Map<String, String> values = new HashMap<>();
values.put("theWorld", "Stackoverflow");
values.put("myName", "Thanos");

String message = StringSubstitutor.replace(template, values, "{", "}");

System.out.println(message);

// Welcome to Stackoverflow. My name is Thanos.
Ninh Pham
la source
Si vous prévoyez de charger des fichiers très volumineux, j'ai trouvé que cette bibliothèque prend également en charge replaceInles valeurs de remplacement dans un tampon: StringBuilder ou TextStringBuilder. Avec cette approche, tout le contenu du fichier ne sera pas chargé en mémoire.
Edward Corrigall
15

Vous pouvez utiliser la bibliothèque StringTemplate , elle offre ce que vous voulez et bien plus encore.

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());
Point fixe
la source
J'ai eu des problèmes avec le 'char:unexpected char: '''
AlikElzin-kilaka
11

Pour les cas très simples, vous pouvez simplement utiliser un remplacement de chaîne codé en dur, pas besoin d'une bibliothèque là-bas:

    String url = "There's an incorrect value '%(value)' in column # %(column)";
    url = url.replace("%(value)", x); // 1
    url = url.replace("%(column)", y); // 2

ATTENTION : je voulais juste montrer le code le plus simple possible. Bien sûr, NE l'utilisez PAS pour du code de production sérieux où la sécurité est importante, comme indiqué dans les commentaires: l'échappement, la gestion des erreurs et la sécurité sont un problème ici. Mais dans le pire des cas, vous savez maintenant pourquoi l'utilisation d'une `` bonne '' bibliothèque est requise :-)

Christophe Roussy
la source
1
celui-ci est simple et facile, mais l'inconvénient est qu'il échoue silencieusement lorsque la valeur n'a pas été trouvée. Il laisse simplement l'espace réservé dans la chaîne d'origine.
kiedysktos
@kiedysktos, vous pouvez l'améliorer en faisant une vérification, mais si vous voulez tout, utilisez une lib :)
Christophe Roussy
2
Avertissement: étant donné que cette technique traite les résultats de substitution intermédiaires comme des chaînes de format à part entière, cette solution est vulnérable aux attaques de chaînes de format . Toute solution correcte doit effectuer un seul passage dans la chaîne de format.
200_success
@ 200_success Oui bon point en parlant de sécurité, bien sûr ce code n'est pas destiné à une utilisation sérieuse en production ...
Christophe Roussy
8

Merci pour votre aide! En utilisant tous vos indices, j'ai écrit une routine pour faire exactement ce que je veux - un formatage de chaîne de type python à l'aide d'un dictionnaire. Puisque je suis novice en Java, tous les indices sont appréciés.

public static String dictFormat(String format, Hashtable<String, Object> values) {
    StringBuilder convFormat = new StringBuilder(format);
    Enumeration<String> keys = values.keys();
    ArrayList valueList = new ArrayList();
    int currentPos = 1;
    while (keys.hasMoreElements()) {
        String key = keys.nextElement(),
        formatKey = "%(" + key + ")",
        formatPos = "%" + Integer.toString(currentPos) + "$";
        int index = -1;
        while ((index = convFormat.indexOf(formatKey, index)) != -1) {
            convFormat.replace(index, index + formatKey.length(), formatPos);
            index += formatPos.length();
        }
        valueList.add(values.get(key));
        ++currentPos;
    }
    return String.format(convFormat.toString(), valueList.toArray());
}
Andy
la source
Contrairement à la réponse de Lombo, cela ne peut pas rester coincé dans une boucle infinie, car formatPosne peut pas contenir formatKey.
Aaron Dufour
6
Avertissement: étant donné que la boucle traite les résultats de substitution intermédiaires comme des chaînes de format à part entière, cette solution est vulnérable aux attaques de chaînes de format . Toute solution correcte doit effectuer un seul passage dans la chaîne de format.
200_success
6

Ceci est un ancien thread, mais juste pour mémoire, vous pouvez également utiliser le style Java 8, comme ceci:

public static String replaceParams(Map<String, String> hashMap, String template) {
    return hashMap.entrySet().stream().reduce(template, (s, e) -> s.replace("%(" + e.getKey() + ")", e.getValue()),
            (s, s2) -> s);
}

Usage:

public static void main(String[] args) {
    final HashMap<String, String> hashMap = new HashMap<String, String>() {
        {
            put("foo", "foo1");
            put("bar", "bar1");
            put("car", "BMW");
            put("truck", "MAN");
        }
    };
    String res = replaceParams(hashMap, "This is '%(foo)' and '%(foo)', but also '%(bar)' '%(bar)' indeed.");
    System.out.println(res);
    System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(foo)', but also '%(bar)' '%(bar)' indeed."));
    System.out.println(replaceParams(hashMap, "This is '%(car)' and '%(truck)', but also '%(foo)' '%(bar)' + '%(truck)' indeed."));
}

La sortie sera:

This is 'foo1' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'foo1', but also 'bar1' 'bar1' indeed.
This is 'BMW' and 'MAN', but also 'foo1' 'bar1' + 'MAN' indeed.
gil.fernandes
la source
C'est génial, mais malheureusement, cela enfreint les spécifications ici docs.oracle.com/javase/8/docs/api/java/util/stream/... La fonction de combinaison doit retourner le deuxième paramètre si le premier paramètre est l'identité. Celui ci-dessus renverrait l'identité à la place. Il enfreint également cette règle: combiner.apply (u, accumulator.apply (identity, t)) == accumulator.apply (u, t)
Ali Cheaito
Intéressant ... mais seulement si vous proposez une manière plus agréable de passer la carte, également si possible après le modèle comme la plupart des codes de formatage.
Christophe Roussy
4
Avertissement: étant donné que le .reduce()traitement traite les résultats de substitution intermédiaires comme des chaînes de format à part entière, cette solution est vulnérable aux attaques de chaînes de format . Toute solution correcte doit effectuer un seul passage dans la chaîne de format.
200_success
6
public static String format(String format, Map<String, Object> values) {
    StringBuilder formatter = new StringBuilder(format);
    List<Object> valueList = new ArrayList<Object>();

    Matcher matcher = Pattern.compile("\\$\\{(\\w+)}").matcher(format);

    while (matcher.find()) {
        String key = matcher.group(1);

        String formatKey = String.format("${%s}", key);
        int index = formatter.indexOf(formatKey);

        if (index != -1) {
            formatter.replace(index, index + formatKey.length(), "%s");
            valueList.add(values.get(key));
        }
    }

    return String.format(formatter.toString(), valueList.toArray());
}

Exemple:

String format = "My name is ${1}. ${0} ${1}.";

Map<String, Object> values = new HashMap<String, Object>();
values.put("0", "James");
values.put("1", "Bond");

System.out.println(format(format, values)); // My name is Bond. James Bond.
kayz1
la source
2
Cela devrait être la réponse, car cela évite les attaques de chaînes de format auxquelles la plupart des autres solutions ici sont vulnérables. Notez que Java 9 le rend beaucoup plus simple, avec la prise en charge des .replaceAll()rappels de substitution de chaîne .
200_success
Cela devrait être la réponse, car il n'utilise aucune bibliothèque externe.
Bohao LI
3

Je suis l'auteur d' une petite bibliothèque qui fait exactement ce que vous voulez:

Student student = new Student("Andrei", 30, "Male");

String studStr = template("#{id}\tName: #{st.getName}, Age: #{st.getAge}, Gender: #{st.getGender}")
                    .arg("id", 10)
                    .arg("st", student)
                    .format();
System.out.println(studStr);

Ou vous pouvez enchaîner les arguments:

String result = template("#{x} + #{y} = #{z}")
                    .args("x", 5, "y", 10, "z", 15)
                    .format();
System.out.println(result);

// Output: "5 + 10 = 15"
Andrei Ciobanu
la source
est-il possible de faire un formatage basé sur des conditions avec votre bibliothèque?
gaurav
@gaurav pas tout à fait. Si vous en avez besoin, vous avez besoin d'une bibliothèque de modèles complète.
Andrei Ciobanu
2

La méthode replaceEach d' Apache Commons Lang peut s'avérer utile en fonction de vos besoins spécifiques. Vous pouvez facilement l'utiliser pour remplacer les espaces réservés par leur nom avec cet appel de méthode unique:

StringUtils.replaceEach("There's an incorrect value '%(value)' in column # %(column)",
            new String[] { "%(value)", "%(column)" }, new String[] { x, y });

Compte tenu du texte d'entrée, cela remplacera toutes les occurrences des espaces réservés dans le premier tableau de chaînes par les valeurs correspondantes dans le second.

Pyves
la source
1

Vous pourriez avoir quelque chose comme ça sur une classe d'assistance de chaîne

/**
 * An interpreter for strings with named placeholders.
 *
 * For example given the string "hello %(myName)" and the map <code>
 *      <p>Map<String, Object> map = new HashMap<String, Object>();</p>
 *      <p>map.put("myName", "world");</p>
 * </code>
 *
 * the call {@code format("hello %(myName)", map)} returns "hello world"
 *
 * It replaces every occurrence of a named placeholder with its given value
 * in the map. If there is a named place holder which is not found in the
 * map then the string will retain that placeholder. Likewise, if there is
 * an entry in the map that does not have its respective placeholder, it is
 * ignored.
 *
 * @param str
 *            string to format
 * @param values
 *            to replace
 * @return formatted string
 */
public static String format(String str, Map<String, Object> values) {

    StringBuilder builder = new StringBuilder(str);

    for (Entry<String, Object> entry : values.entrySet()) {

        int start;
        String pattern = "%(" + entry.getKey() + ")";
        String value = entry.getValue().toString();

        // Replace every occurence of %(key) with value
        while ((start = builder.indexOf(pattern)) != -1) {
            builder.replace(start, start + pattern.length(), value);
        }
    }

    return builder.toString();
}
Lombo
la source
Merci beaucoup, il fait presque ce que je veux, mais la seule chose est qu'il ne tient pas compte des modificateurs (considérez "% (key) 08d")
Andy
1
Notez également que cela va dans une boucle infinie si l'une des valeurs utilisées contient l'entrée correspondante.
Aaron Dufour
1
Avertissement: étant donné que la boucle traite les résultats de substitution intermédiaires comme des chaînes de format à part entière, cette solution est vulnérable aux attaques de chaînes de format . Toute solution correcte doit effectuer un seul passage dans la chaîne de format.
200_success
1

Ma réponse est de:

a) utilisez StringBuilder lorsque cela est possible

b) garder (sous n'importe quelle forme: l'entier est le meilleur, spéciall char comme la macro dollar, etc.) la position de "placeholder" et ensuite utiliser StringBuilder.insert()(quelques versions d'arguments).

L'utilisation de bibliothèques externes semble exagérée et je pense que les performances sont considérablement dégradées lorsque StringBuilder est converti en String en interne.

Jacek Cz
la source
1

Sur la base de la réponse, j'ai créé la MapBuilderclasse:

public class MapBuilder {

    public static Map<String, Object> build(Object... data) {
        Map<String, Object> result = new LinkedHashMap<>();

        if (data.length % 2 != 0) {
            throw new IllegalArgumentException("Odd number of arguments");
        }

        String key = null;
        Integer step = -1;

        for (Object value : data) {
            step++;
            switch (step % 2) {
                case 0:
                    if (value == null) {
                        throw new IllegalArgumentException("Null key value");
                    }
                    key = (String) value;
                    continue;
                case 1:
                    result.put(key, value);
                    break;
            }
        }

        return result;
    }

}

puis j'ai créé une classe StringFormatpour le formatage de chaîne:

public final class StringFormat {

    public static String format(String format, Object... args) {
        Map<String, Object> values = MapBuilder.build(args);

        for (Map.Entry<String, Object> entry : values.entrySet()) {
            String key = entry.getKey();
            Object value = entry.getValue();
            format = format.replace("$" + key, value.toString());
        }

        return format;
    }

}

que vous pourriez utiliser comme ça:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);
Rafal Enden
la source
1
Avertissement: étant donné que la boucle traite les résultats de substitution intermédiaires comme des chaînes de format à part entière, cette solution est vulnérable aux attaques de chaînes de format . Toute solution correcte doit effectuer un seul passage dans la chaîne de format.
200_success
1

J'ai également créé une classe util / helper (en utilisant jdk 8) qui peut formater une chaîne et remplacer les occurrences de variables.

Pour cela, j'ai utilisé la méthode Matchers "appendReplacement" qui effectue toute la substitution et effectue des boucles uniquement sur les parties affectées d'une chaîne de format.

La classe d'assistance n'est actuellement pas bien documentée avec javadoc. Je vais changer cela à l'avenir;) Quoi qu'il en soit, j'ai commenté les lignes les plus importantes (j'espère).

    public class FormatHelper {

    //Prefix and suffix for the enclosing variable name in the format string.
    //Replace the default values with any you need.
    public static final String DEFAULT_PREFIX = "${";
    public static final String DEFAULT_SUFFIX = "}";

    //Define dynamic function what happens if a key is not found.
    //Replace the defualt exception with any "unchecked" exception type you need or any other behavior.
    public static final BiFunction<String, String, String> DEFAULT_NO_KEY_FUNCTION =
            (fullMatch, variableName) -> {
                throw new RuntimeException(String.format("Key: %s for variable %s not found.",
                                                         variableName,
                                                         fullMatch));
            };
    private final Pattern variablePattern;
    private final Map<String, String> values;
    private final BiFunction<String, String, String> noKeyFunction;
    private final String prefix;
    private final String suffix;

    public FormatHelper(Map<String, String> values) {
        this(DEFAULT_NO_KEY_FUNCTION, values);
    }

    public FormatHelper(
            BiFunction<String, String, String> noKeyFunction, Map<String, String> values) {
        this(DEFAULT_PREFIX, DEFAULT_SUFFIX, noKeyFunction, values);
    }

    public FormatHelper(String prefix, String suffix, Map<String, String> values) {
        this(prefix, suffix, DEFAULT_NO_KEY_FUNCTION, values);
    }

    public FormatHelper(
            String prefix,
            String suffix,
            BiFunction<String, String, String> noKeyFunction,
            Map<String, String> values) {
        this.prefix = prefix;
        this.suffix = suffix;
        this.values = values;
        this.noKeyFunction = noKeyFunction;

        //Create the Pattern and quote the prefix and suffix so that the regex don't interpret special chars.
        //The variable name is a "\w+" in an extra capture group.
        variablePattern = Pattern.compile(Pattern.quote(prefix) + "(\\w+)" + Pattern.quote(suffix));
    }

    public static String format(CharSequence format, Map<String, String> values) {
        return new FormatHelper(values).format(format);
    }

    public static String format(
            CharSequence format,
            BiFunction<String, String, String> noKeyFunction,
            Map<String, String> values) {
        return new FormatHelper(noKeyFunction, values).format(format);
    }

    public static String format(
            String prefix, String suffix, CharSequence format, Map<String, String> values) {
        return new FormatHelper(prefix, suffix, values).format(format);
    }

    public static String format(
            String prefix,
            String suffix,
            BiFunction<String, String, String> noKeyFunction,
            CharSequence format,
            Map<String, String> values) {
        return new FormatHelper(prefix, suffix, noKeyFunction, values).format(format);
    }

    public String format(CharSequence format) {

        //Create matcher based on the init pattern for variable names.
        Matcher matcher = variablePattern.matcher(format);

        //This buffer will hold all parts of the formatted finished string.
        StringBuffer formatBuffer = new StringBuffer();

        //loop while the matcher finds another variable (prefix -> name <- suffix) match
        while (matcher.find()) {

            //The root capture group with the full match e.g ${variableName}
            String fullMatch = matcher.group();

            //The capture group for the variable name resulting from "(\w+)" e.g. variableName
            String variableName = matcher.group(1);

            //Get the value in our Map so the Key is the used variable name in our "format" string. The associated value will replace the variable.
            //If key is missing (absent) call the noKeyFunction with parameters "fullMatch" and "variableName" else return the value.
            String value = values.computeIfAbsent(variableName, key -> noKeyFunction.apply(fullMatch, key));

            //Escape the Map value because the "appendReplacement" method interprets the $ and \ as special chars.
            String escapedValue = Matcher.quoteReplacement(value);

            //The "appendReplacement" method replaces the current "full" match (e.g. ${variableName}) with the value from the "values" Map.
            //The replaced part of the "format" string is appended to the StringBuffer "formatBuffer".
            matcher.appendReplacement(formatBuffer, escapedValue);
        }

        //The "appendTail" method appends the last part of the "format" String which has no regex match.
        //That means if e.g. our "format" string has no matches the whole untouched "format" string is appended to the StringBuffer "formatBuffer".
        //Further more the method return the buffer.
        return matcher.appendTail(formatBuffer)
                      .toString();
    }

    public String getPrefix() {
        return prefix;
    }

    public String getSuffix() {
        return suffix;
    }

    public Map<String, String> getValues() {
        return values;
    }
}

Vous pouvez créer une instance de classe pour une carte spécifique avec des valeurs (ou un préfixe de suffixe ou noKeyFunction) comme:

    Map<String, String> values = new HashMap<>();
    values.put("firstName", "Peter");
    values.put("lastName", "Parker");


    FormatHelper formatHelper = new FormatHelper(values);
    formatHelper.format("${firstName} ${lastName} is Spiderman!");
    // Result: "Peter Parker is Spiderman!"
    // Next format:
    formatHelper.format("Does ${firstName} ${lastName} works as photographer?");
    //Result: "Does Peter Parker works as photographer?"

De plus, vous pouvez définir ce qui se passe si une clé dans la carte des valeurs est manquante (fonctionne dans les deux sens, par exemple un nom de variable erroné dans la chaîne de format ou une clé manquante dans la carte). Le comportement par défaut est une exception non vérifiée lancée (non cochée car j'utilise la fonction jdk8 par défaut qui ne peut pas gérer les exceptions cochées) comme:

    Map<String, String> map = new HashMap<>();
    map.put("firstName", "Peter");
    map.put("lastName", "Parker");


    FormatHelper formatHelper = new FormatHelper(map);
    formatHelper.format("${missingName} ${lastName} is Spiderman!");
    //Result: RuntimeException: Key: missingName for variable ${missingName} not found.

Vous pouvez définir un comportement personnalisé dans l'appel du constructeur comme:

Map<String, String> values = new HashMap<>();
values.put("firstName", "Peter");
values.put("lastName", "Parker");


FormatHelper formatHelper = new FormatHelper(fullMatch, variableName) -> variableName.equals("missingName") ? "John": "SOMETHING_WRONG", values);
formatHelper.format("${missingName} ${lastName} is Spiderman!");
// Result: "John Parker is Spiderman!"

ou déléguez-le au comportement par défaut sans clé:

...
    FormatHelper formatHelper = new FormatHelper((fullMatch, variableName) ->   variableName.equals("missingName") ? "John" :
            FormatHelper.DEFAULT_NO_KEY_FUNCTION.apply(fullMatch,
                                                       variableName), map);
...

Pour une meilleure gestion, il existe également des représentations de méthodes statiques telles que:

Map<String, String> values = new HashMap<>();
values.put("firstName", "Peter");
values.put("lastName", "Parker");

FormatHelper.format("${firstName} ${lastName} is Spiderman!", map);
// Result: "Peter Parker is Spiderman!"
schlegel11
la source
1

Il n'y a rien de intégré à Java au moment d'écrire ceci. Je suggérerais d'écrire votre propre implémentation. Ma préférence va pour une interface de générateur simple et fluide au lieu de créer une carte et de la transmettre à la fonction - vous vous retrouvez avec un joli morceau de code contigu, par exemple:

String result = new TemplatedStringBuilder("My name is {{name}} and I from {{town}}")
   .replace("name", "John Doe")
   .replace("town", "Sydney")
   .finish();

Voici une implémentation simple:

class TemplatedStringBuilder {

    private final static String TEMPLATE_START_TOKEN = "{{";
    private final static String TEMPLATE_CLOSE_TOKEN = "}}";

    private final String template;
    private final Map<String, String> parameters = new HashMap<>();

    public TemplatedStringBuilder(String template) {
        if (template == null) throw new NullPointerException();
        this.template = template;
    }

    public TemplatedStringBuilder replace(String key, String value){
        parameters.put(key, value);
        return this;
    }

    public String finish(){

        StringBuilder result = new StringBuilder();

        int startIndex = 0;

        while (startIndex < template.length()){

            int openIndex  = template.indexOf(TEMPLATE_START_TOKEN, startIndex);

            if (openIndex < 0){
                result.append(template.substring(startIndex));
                break;
            }

            int closeIndex = template.indexOf(TEMPLATE_CLOSE_TOKEN, openIndex);

            if(closeIndex < 0){
                result.append(template.substring(startIndex));
                break;
            }

            String key = template.substring(openIndex + TEMPLATE_START_TOKEN.length(), closeIndex);

            if (!parameters.containsKey(key)) throw new RuntimeException("missing value for key: " + key);

            result.append(template.substring(startIndex, openIndex));
            result.append(parameters.get(key));

            startIndex = closeIndex + TEMPLATE_CLOSE_TOKEN.length();
        }

        return result.toString();
    }
}
Jason
la source
0

Essayez Freemarker , bibliothèque de modèles.

texte alternatif

Boris Pavlović
la source
4
Freemarker? Je suppose qu'il est prêt à savoir, comment faire cela en java simple. Quoi qu'il en soit, si Freemarker est la réponse probable, puis-je dire que JSP sera également la bonne réponse?
Rakesh Juyal
1
Merci, mais pour ma tâche à accomplir, cela semble être un peu exagéré. Mais merci.
Andy
1
@Rakesh JSP est une chose très spécifique à "vue / FE". J'ai utilisé FreeMarker dans le passé pour générer des fichiers XML et parfois même des fichiers JAVA. Andy a peur que vous deviez écrire un utilitaire vous-même (ou comme celui prescrit ci-dessus)
Kannan Ekanath
@Boris, lequel est le meilleur freemarker vs vitesse vs stringtemplate?
gaurav
1
@gaurav jetez un œil à stackoverflow.com/questions/3741618/… et dzone.com/articles/…
Boris Pavlović
0

Vous devriez jeter un œil à la bibliothèque officielle ICU4J . Il fournit une classe MessageFormat similaire à celle disponible avec le JDK, mais cette ancienne prend en charge les espaces réservés nommés.

Contrairement aux autres solutions proposées sur cette page. ICU4j fait partie du projet ICU qui est maintenu par IBM et régulièrement mis à jour. En outre, il prend en charge des cas d'utilisation avancés tels que la pluralisation et bien plus encore.

Voici un exemple de code:

MessageFormat messageFormat =
        new MessageFormat("Publication written by {author}.");

Map<String, String> args = Map.of("author", "John Doe");

System.out.println(messageFormat.format(args));
Laurent
la source
0

Il existe un plugin Java pour utiliser l'interpolation de chaîne en Java (comme dans Kotlin, JavaScript). Prise en charge Java 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Utilisation de variables dans les littéraux de chaîne:

int a = 3;
int b = 4;
System.out.println("${a} + ${b} = ${a+b}");

Utilisation d'expressions:

int a = 3;
int b = 4;
System.out.println("pow = ${a * a}");
System.out.println("flag = ${a > b ? true : false}");

Utilisation des fonctions:

@Test
void functionCall() {
    System.out.println("fact(5) = ${factorial(5)}");
}

long factorial(int n) {
    long fact = 1;
    for (int i = 2; i <= n; i++) {
        fact = fact * i;
    }
    return fact;
}

Pour plus d'informations, veuillez lire le projet README.

hermeslm
la source