117 votes

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

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

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

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


EDIT

Avec les remerciements de @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();
}

0 votes

Une chose à noter, cependant, est que StringBuffer est le même que StringBuilder juste synchronisé. Cependant, puisque dans cet exemple vous n'avez pas besoin de synchroniser la construction de la chaîne, il est préférable d'utiliser StringBuilder (même si l'acquisition de verrous est une opération dont le coût est presque nul).

1 votes

Malheureusement, vous devez utiliser StringBuffer dans ce cas ; c'est ce que les méthodes appendXXX() attendent. Elles existent depuis Java 4, et StringBuilder n'a pas été ajouté avant Java 5. Comme vous l'avez dit, ce n'est pas un problème majeur, juste ennuyeux.

4 votes

Une dernière chose : appendReplacement(), comme les méthodes replaceXXX(), recherche les références de groupes de capture comme $1, $2, etc. et les remplace par le texte des groupes de capture associés. et les remplace par le texte des groupes de capture associés. Si votre texte de remplacement contient des signes de dollar ou des barres obliques inverses (qui sont utilisées pour échapper aux signes de dollar), vous pourriez avoir un problème. Le moyen le plus simple d'y remédier est de diviser l'opération d'ajout en deux étapes, comme je l'ai fait dans le code ci-dessus.

117voto

Paul Morie Points 6956

Je ne pense vraiment pas que vous ayez besoin d'utiliser un moteur de modèles ou quelque chose de ce genre pour cela. Vous pouvez utiliser le 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);

7 votes

L'inconvénient est que vous devez placer les paramètres dans l'ordre correct.

2 votes

Un autre problème est que vous ne pouvez pas spécifier votre propre format de jeton de remplacement.

0 votes

Un autre problème est qu'il ne fonctionne pas de manière dynamique, en étant capable d'avoir un ensemble de données de clés/valeurs et de l'appliquer à n'importe quelle chaîne.

74voto

laginimaineb Points 3049

Le moyen le plus efficace serait d'utiliser un matcheur pour trouver continuellement les expressions et les remplacer, puis d'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();

10 votes

C'est ainsi que je le ferais, sauf que j'utiliserais les méthodes appendReplacement() et appendTail() de Matcher pour copier le texte non apparié ; il n'est pas nécessaire de le faire à la main.

6 votes

En fait, les méthodes appendReplacement() et appentTail() nécessitent un StringBuffer, qui est snychronisé (ce qui n'est d'aucune utilité ici). La réponse donnée utilise un StringBuilder, qui est 20% plus rapide dans mes tests.

52voto

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

Il prend en charge les caractères de remplacement de la forme "{numéro}", de sorte que votre message pourrait ressembler à "Bonjour {0} Veuillez trouver ci-joint {1} qui est dû le {2}". Ces chaînes de caractères peuvent facilement être externalisées à l'aide de ResourceBundles (par exemple, pour la localisation avec plusieurs langues). Le remplacement se ferait à l'aide de la méthode statique "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));

44voto

hallidave Points 3887

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 :

Hello Mark. Please find attached invoice 42123 which is due on June 6, 2009.

0 votes

J'ai utilisé la vélocité dans le passé. Cela fonctionne très bien.

4 votes

D'accord, pourquoi réinventer la roue

6 votes

Il est un peu exagéré d'utiliser une bibliothèque entière pour une tâche aussi simple que celle-ci. Velocity a beaucoup d'autres fonctionnalités, et je crois fermement qu'il n'est pas adapté à une tâche aussi simple que celle-ci.

26voto

Li Ying Points 111

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 les tâches simples, 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 qu'il contient. La définition par défaut d'une variable est ${variableName}. Le préfixe et le suffixe peuvent être modifiés via les constructeurs et les méthodes set.

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

Par exemple, si vous voulez substituer une variable d'environnement du 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);
    }
}

2 votes

Org.apache.commons.lang3.text.StrSubstitutor fonctionne très bien pour moi.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X