web-dev-qa-db-fra.com

Comment remplacer un ensemble de jetons dans une chaîne Java?

J'ai le modèle de chaîne suivant: "Hello [Name] Please find attached [Invoice Number] which is due on [Due Date]".

J'ai également des variables de chaîne pour le nom, le numéro de facture et la date d'échéance. Quelle est la meilleure façon de remplacer les jetons du modèle par les variables? 

(Notez que si une variable contient un jeton, elle ne doit PAS être remplacée).


MODIFIER

Avec merci à @laginimaineb et @ alan-moore, voici ma solution:

public static String replaceTokens(String text, 
                                   Map<String, String> replacements) {
    Pattern pattern = Pattern.compile("\\[(.+?)\\]");
    Matcher matcher = pattern.matcher(text);
    StringBuffer buffer = new StringBuffer();

    while (matcher.find()) {
        String replacement = replacements.get(matcher.group(1));
        if (replacement != null) {
            // matcher.appendReplacement(buffer, replacement);
            // see comment 
            matcher.appendReplacement(buffer, "");
            buffer.append(replacement);
        }
    }
    matcher.appendTail(buffer);
    return buffer.toString();
}
100
Mark

La méthode la plus efficace consiste à utiliser un matcher pour rechercher en permanence les expressions et les remplacer, puis ajouter le texte à un constructeur de chaînes:

Pattern pattern = Pattern.compile("\\[(.+?)\\]");
Matcher matcher = pattern.matcher(text);
HashMap<String,String> replacements = new HashMap<String,String>();
//populate the replacements map ...
StringBuilder builder = new StringBuilder();
int i = 0;
while (matcher.find()) {
    String replacement = replacements.get(matcher.group(1));
    builder.append(text.substring(i, matcher.start()));
    if (replacement == null)
        builder.append(matcher.group(0));
    else
        builder.append(replacement);
    i = matcher.end();
}
builder.append(text.substring(i, text.length()));
return builder.toString();
63
laginimaineb

Je ne pense vraiment pas que vous ayez besoin d'utiliser un moteur de template ou quelque chose comme ça pour ça. Vous pouvez utiliser la méthode String.format , comme suit:

String template = "Hello %s Please find attached %s which is due on %s";

String message = String.format(template, name, invoiceNumber, dueDate);
94
Paul Morie

Malheureusement, la méthode confortable String.format mentionnée ci-dessus n'est disponible qu'à partir de Java 1.5 (qui devrait être plutôt standard de nos jours, mais on ne sait jamais). Au lieu de cela, vous pouvez également utiliser la classe MessageFormat de Java pour remplacer les espaces réservés.

Il prend en charge les espaces réservés sous la forme '{numéro}', afin que votre message ressemble à "Bonjour {0}. Veuillez trouver le fichier attaché {1} qui doit être reçu le {2}". Ces chaînes peuvent facilement être externalisées à l'aide de ResourceBundles (par exemple, pour la localisation avec plusieurs paramètres régionaux). Le remplacement se ferait à l'aide de la méthode static'format 'de la classe MessageFormat:

String msg = "Hello {0} Please find attached {1} which is due on {2}";
String[] values = {
  "John Doe", "invoice #123", "2009-06-30"
};
System.out.println(MessageFormat.format(msg, values));
41
A. Knauf

Vous pouvez essayer d'utiliser une bibliothèque de modèles comme Apache Velocity.

http://velocity.Apache.org/

Voici un exemple:

import org.Apache.velocity.VelocityContext;
import org.Apache.velocity.app.Velocity;

import Java.io.StringWriter;

public class TemplateExample {
    public static void main(String args[]) throws Exception {
        Velocity.init();

        VelocityContext context = new VelocityContext();
        context.put("name", "Mark");
        context.put("invoiceNumber", "42123");
        context.put("dueDate", "June 6, 2009");

        String template = "Hello $name. Please find attached invoice" +
                          " $invoiceNumber which is due on $dueDate.";
        StringWriter writer = new StringWriter();
        Velocity.evaluate(context, writer, "TemplateName", template);

        System.out.println(writer);
    }
}

La sortie serait:

Bonjour Mark. Veuillez trouver ci-joint la facture 42123, qui est due le 6 juin 2009.
40
hallidave

Vous pouvez utiliser la bibliothèque de modèles pour le remplacement de modèles complexes.

FreeMarker est un très bon choix.

http://freemarker.sourceforge.net/

Mais pour une tâche simple, il existe une classe utilitaire simple qui peut vous aider.

org.Apache.commons.lang3.text.StrSubstitutor

Il est très puissant, personnalisable et facile à utiliser.

Cette classe prend un morceau de texte et remplace toutes les variables à l'intérieur. La définition par défaut d'une variable est $ {nomVariable} . Le préfixe et le suffixe peuvent être modifiés via des constructeurs et des méthodes set.

Les valeurs de variables sont généralement résolues à partir d'une carte, mais pourraient également être résolus à partir des propriétés du système, ou en fournissant une variable personnalisée résolveur.

Par exemple, si vous souhaitez substituer la variable d'environnement système dans une chaîne de modèle, Voici le code:

public class SysEnvSubstitutor {
    public static final String replace(final String source) {
        StrSubstitutor strSubstitutor = new StrSubstitutor(
                new StrLookup<Object>() {
                    @Override
                    public String lookup(final String key) {
                        return System.getenv(key);
                    }
                });
        return strSubstitutor.replace(source);
    }
}
23
Li Ying
System.out.println(MessageFormat.format("Hello {0}! You have {1} messages", "Join",10L));

Sortie: Hello Join! Vous avez 10 messages "

16
user2845137
String.format("Hello %s Please find attached %s which is due on %s", name, invoice, date)
9
Bruno Ranschaert

Cela dépend de l'emplacement des données réelles que vous souhaitez remplacer. Vous pourriez avoir une carte comme celle-ci:

Map<String, String> values = new HashMap<String, String>();

contenant toutes les données pouvant être remplacées. Ensuite, vous pouvez parcourir la carte et tout modifier dans la chaîne comme suit:

String s = "Your String with [Fields]";
for (Map.Entry<String, String> e : values.entrySet()) {
  s = s.replaceAll("\\[" + e.getKey() + "\\]", e.getValue());
}

Vous pouvez également parcourir la chaîne et rechercher les éléments dans la carte. Mais c'est un peu plus compliqué parce que vous devez analyser la chaîne à la recherche du []. Vous pouvez le faire avec une expression régulière en utilisant Pattern et Matcher.

8
Ricardo Marimon

Ma solution pour remplacer les jetons de style $ {variable} (inspirés des réponses fournies ici et par le Spring UriTemplate):

public static String substituteVariables(String template, Map<String, String> variables) {
    Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
    Matcher matcher = pattern.matcher(template);
    // StringBuilder cannot be used here because Matcher expects StringBuffer
    StringBuffer buffer = new StringBuffer();
    while (matcher.find()) {
        if (variables.containsKey(matcher.group(1))) {
            String replacement = variables.get(matcher.group(1));
            // quote to work properly with $ and {,} signs
            matcher.appendReplacement(buffer, replacement != null ? Matcher.quoteReplacement(replacement) : "null");
        }
    }
    matcher.appendTail(buffer);
    return buffer.toString();
}
3
mihu86

FYI

Dans la nouvelle langue Kotlin, , Vous pouvez directement utiliser des "Modèles de chaîne" dans votre code source.

C'est une caractéristique de la langue elle-même.

Voir: https://kotlinlang.org/docs/reference/basic-types.html#string-templates

1
Li Ying
1
Marcel

J'ai utilisé

String template = "Hello %s Please find attached %s which is due on %s";

String message = String.format(template, name, invoiceNumber, dueDate);
0
mtwom

Dans le passé, j'avais résolu ce genre de problème avec StringTemplate et Groovy Templates

En fin de compte, la décision d'utiliser ou non un moteur de gabarit devrait être basée sur les facteurs suivants:

  • Aurez-vous beaucoup de ces modèles dans l'application?
  • Avez-vous besoin de pouvoir modifier les modèles sans redémarrer l’application?
  • Qui gérera ces modèles? Un programmeur Java ou un analyste métier impliqué dans le projet?
  • Aurez-vous besoin de la capacité de mettre de la logique dans vos modèles, comme du texte conditionnel basé sur des valeurs dans les variables? 
  • Aurez-vous besoin d'inclure d'autres modèles dans un modèle?

Si l’une des situations décrites ci-dessus s’applique à votre projet, j’envisagerais d’utiliser un moteur de modélisation, dont la plupart fournissent cette fonctionnalité, et plus encore.

0
Francois Gravel

Avec Apache Commons Library, vous pouvez simplement utiliser Stringutils.replaceEach :

public static String replaceEach(String text,
                             String[] searchList,
                             String[] replacementList)

De la documentation :

Remplace toutes les occurrences de chaînes dans une autre chaîne.

Une référence null transmise à cette méthode est une opération no-op ou, si une "recherche Chaîne" ou une "chaîne à remplacer" est null, cette substitution sera ignorée . Cela ne se répète pas. Pour répéter remplace, appelez le surchargé méthode.

 StringUtils.replaceEach(null, *, *)        = null

  StringUtils.replaceEach("", *, *)          = ""

  StringUtils.replaceEach("aba", null, null) = "aba"

  StringUtils.replaceEach("aba", new String[0], null) = "aba"

  StringUtils.replaceEach("aba", null, new String[0]) = "aba"

  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"

  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"

  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"

  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
  (example of how it does not repeat)

StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"
0
AR1