Segnaposto con nome nella formattazione della stringa


175

In Python, durante la formattazione della stringa, posso riempire i segnaposto per nome anziché per posizione, in questo modo:

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

Mi chiedo se ciò è possibile in Java (si spera, senza librerie esterne)?


È possibile estendere MessageFormat e implementare la funzionalità di mappatura da variabili a indici.
vpram86,


1
Un po 'di storia: Java per lo più ha copiato C / C ++ su questa questione mentre cercava di attirare gli sviluppatori dal mondo C ++ dove %sera pratica comune. it.wikipedia.org/wiki/Printf_format_string#History Nota anche che alcuni IDE e FindBugs potrebbero rilevare automaticamente la mancata corrispondenza dei conteggi% se% d, ma preferirei comunque i campi con nome.
Christophe Roussy,

Risposte:


143

StrSubstitutor of jakarta commons lang è un modo leggero per farlo, a condizione che i tuoi valori siano già formattati correttamente.

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

Quanto sopra risulta in:

"C'è un valore errato '1' nella colonna # 2"

Quando usi Maven puoi aggiungere questa dipendenza al tuo pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>

2
Ho trovato deludente che la libreria non passi se non vengono trovate le chiavi, tuttavia, se si utilizza la sintassi predefinita ( ${arg}) invece di quella personalizzata sopra ( %(arg)), la regex non verrà compilata, che è l'effetto desiderato.
John Lehmann,

2
Puoi impostare un VariableResolver personalizzato e generare un'eccezione se la chiave non è presente nella mappa.
Mene,

7
Vecchio thread, ma a partire da 3.6, il pacchetto di testo era deprecato a favore di commons-text. commons.apache.org/proper/commons-text
Jeff Walker il

74

non del tutto, ma puoi usare MessageFormat per fare riferimento a un valore più volte:

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

Quanto sopra può essere fatto anche con String.format (), ma trovo il pulitore della sintassi messageFormat se è necessario creare espressioni complesse, inoltre non è necessario preoccuparsi del tipo di oggetto che si sta inserendo nella stringa


non sono sicuro del motivo per cui non è possibile, la posizione nella stringa non è importante, solo la posizione nell'elenco di args, il che la rende un problema di rinomina. Conosci il nome delle chiavi, il che significa che puoi decidere una posizione per una chiave nell'elenco degli argomenti. d'ora in poi il valore sarà noto come 0 e colonna come 1: MessageeFormat.format ("C'è un valore errato \" {0} \ "nella colonna # {1}, l'uso di {0} come valore può causare molti problemi", valueMap .get ('value'), valueMap.get ('column'));
Giladbu,

1
Grazie per un indizio, mi ha aiutato a scrivere una funzione semplice che fa esattamente quello che voglio (l'ho messo sotto).
Andy,

1
D'accordo, la sintassi è molto più pulita. Peccato che MessageFormat abbia una propria mente quando si tratta di formattare valori numerici.
Kees de Kooter,

E sembra ignorare i segnaposto racchiusi tra virgolette singole.
Kees de Kooter,

MessageFormatè fantastico ma ingombrante per contenuti json relativamente grandi
EliuX,

32

Un altro esempio di Apache Common StringSubstitutor per segnaposto con nome semplice.

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.

Se ti aspetti di caricare file molto grandi, ho scoperto che questa libreria supporta anche i replaceInvalori sostitutivi in ​​un buffer: StringBuilder o TextStringBuilder. Con questo approccio, l'intero contenuto del file non verrà caricato in memoria.
Edward Corrigall,

15

Puoi utilizzare la libreria StringTemplate , offre ciò che desideri e molto altro.

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());

Ho avuto problemi con il 'unexpected char: '''
personaggio

11

Per casi molto semplici puoi semplicemente usare una sostituzione String codificata, senza bisogno di una libreria lì:

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

ATTENZIONE : volevo solo mostrare il codice più semplice possibile. Ovviamente NON utilizzarlo per un serio codice di produzione in cui la sicurezza è importante, come indicato nei commenti: fuga, gestione degli errori e sicurezza sono un problema qui. Ma nel peggiore dei casi ora sai perché è richiesto l'uso di una lib "buona" :-)


1
questo è semplice e facile, ma il rovescio della medaglia è che fallisce silenziosamente quando il valore non è stato trovato. Lascia solo il segnaposto nella stringa originale.
kiedysktos,

@kiedysktos, puoi migliorarlo facendo un controllo, ma se vuoi tutto, usa una lib :)
Christophe Roussy,

2
Avvertenza: poiché questa tecnica tratta i risultati della sostituzione intermedia come stringhe di formattazione proprie, questa soluzione è vulnerabile agli attacchi di stringa di formattazione . Qualsiasi soluzione corretta dovrebbe effettuare un singolo passaggio attraverso la stringa di formato.
200_successo

@ 200_success Sì, va bene parlare di sicurezza, ovviamente questo codice non è per un serio utilizzo della produzione ...
Christophe Roussy,

8

Grazie per tutto il vostro aiuto! Usando tutti i tuoi indizi, ho scritto la routine per fare esattamente quello che voglio - la formattazione di stringhe simile a Python usando il dizionario. Da quando sono un novizio di Java, tutti i suggerimenti sono apprezzati.

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

A differenza della risposta di Lombo, questo non può rimanere bloccato in un ciclo infinito, poiché formatPosnon può contenere formatKey.
Aaron Dufour,

6
Avvertenza: poiché il ciclo tratta i risultati della sostituzione intermedia come stringhe di formattazione proprie, questa soluzione è vulnerabile agli attacchi di stringa di formattazione . Qualsiasi soluzione corretta dovrebbe effettuare un singolo passaggio attraverso la stringa di formato.
200_successo

6

Questo è un vecchio thread, ma solo per la cronaca, potresti anche usare lo stile Java 8, in questo modo:

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

Uso:

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

L'output sarà:

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.

Questo è brillante, ma purtroppo viola le specifiche qui docs.oracle.com/javase/8/docs/api/java/util/stream/… La funzione combinatore deve restituire il secondo parametro se il primo parametro è l'identità. Quello sopra invece restituirebbe l'identità. Inoltre viola questa regola: combiner.apply (u, accumulator.apply (identity, t)) == accumulator.apply (u, t)
Ali Cheaito,

Interessante ... ma solo se proponi un modo migliore per passare la mappa, anche se possibile dopo il modello come la maggior parte del codice di formattazione.
Christophe Roussy,

4
Avvertenza: poiché i .reduce()risultati della sostituzione intermedia vengono considerati stringhe di formato proprie, questa soluzione è vulnerabile agli attacchi di stringa di formattazione . Qualsiasi soluzione corretta dovrebbe effettuare un singolo passaggio attraverso la stringa di formato.
200_successo

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

Esempio:

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.

2
Questa dovrebbe essere la risposta, poiché evita gli attacchi alle stringhe di formato a cui la maggior parte delle altre soluzioni qui sono vulnerabili. Si noti che Java 9 lo rende molto più semplice, con il supporto per .replaceAll()i callback di sostituzione delle stringhe .
200_successo

Questa dovrebbe essere la risposta, per questo non utilizza librerie esterne.
Bohao LI,

3

Sono l'autore di una piccola biblioteca che fa esattamente quello che vuoi:

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

Oppure puoi concatenare gli argomenti:

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

// Output: "5 + 10 = 15"

è possibile eseguire la formattazione basata sulle condizioni con la propria libreria?
gaurav,

@gaurav non proprio. Se è necessario, è necessaria una libreria di modelli completa.
Andrei Ciobanu,

2

Apache Commons Lang sostituisce Ogni metodo può tornare utile a seconda delle tue esigenze specifiche. Puoi facilmente usarlo per sostituire i segnaposto per nome con questa singola chiamata di metodo:

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

Dato un po 'di testo di input, questo sostituirà tutte le occorrenze dei segnaposto nel primo array di stringhe con i valori corrispondenti nel secondo.


1

Potresti avere qualcosa del genere in una classe helper di stringa

/**
 * 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();
}

Grazie mille, fa quasi quello che voglio, ma l'unica cosa è che non tiene conto dei modificatori (considera "% (chiave) 08d")
Andy,

1
Si noti inoltre che questo va in un ciclo infinito se uno qualsiasi dei valori utilizzati contiene la voce corrispondente.
Aaron Dufour,

1
Avvertenza: poiché il ciclo tratta i risultati della sostituzione intermedia come stringhe di formattazione proprie, questa soluzione è vulnerabile agli attacchi di stringa di formattazione . Qualsiasi soluzione corretta dovrebbe effettuare un singolo passaggio attraverso la stringa di formato.
200_successo

1

La mia risposta è a:

a) utilizzare StringBuilder quando possibile

b) mantenere (in qualsiasi forma: intero è il migliore, carattere speciale come la macro del dollaro ecc.) posizione di "segnaposto" e quindi usare StringBuilder.insert()(poche versioni di argomenti).

L'uso di librerie esterne sembra eccessivo e credo che le prestazioni degradate siano significative, quando StringBuilder viene convertito in String internamente.


1

Sulla base della risposta che ho creato 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;
    }

}

poi ho creato la classe StringFormatper la formattazione delle stringhe:

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

}

che potresti usare così:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);

1
Avvertenza: poiché il ciclo tratta i risultati della sostituzione intermedia come stringhe di formattazione proprie, questa soluzione è vulnerabile agli attacchi di stringa di formattazione . Qualsiasi soluzione corretta dovrebbe effettuare un singolo passaggio attraverso la stringa di formato.
200_successo

1

Ho anche creato una classe util / helper (usando jdk 8) che può formattare una stringa e sostituire le occorrenze di variabili.

A tale scopo ho usato il metodo "appendReplacement" dei Matcher che esegue tutta la sostituzione e esegue il loop solo sulle parti interessate di una stringa di formato.

La classe helper non è attualmente ben documentata da javadoc. Lo cambierò in futuro;) Comunque ho commentato le righe più importanti (spero).

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

È possibile creare un'istanza di classe per una mappa specifica con valori (o prefisso suffisso o noKeyFunction) come:

    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?"

Inoltre puoi definire cosa succede se manca una chiave nei valori di Map (funziona in entrambi i modi, ad es. Nome di variabile errato nella stringa di formato o chiave mancante in Map). Il comportamento predefinito è un'eccezione non selezionata generata (non selezionata perché utilizzo la funzione jdk8 predefinita che non è in grado di gestire le eccezioni verificate) come:

    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.

È possibile definire un comportamento personalizzato nella chiamata del costruttore come:

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!"

o delegarlo al comportamento predefinito senza chiave:

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

Per una migliore gestione ci sono anche rappresentazioni di metodi statici come:

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!"

1

Non c'è nulla di incorporato in Java al momento della stesura di questo. Suggerirei di scrivere la tua implementazione. La mia preferenza è per una semplice interfaccia per il builder fluida invece di creare una mappa e passarla per funzionare - si finisce con un bel pezzo contiguo di codice, ad esempio:

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

Ecco una semplice implementazione:

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

0

Prova Freemarker , libreria di modelli.

testo alternativo


4
Freemarker? immagino che sia disposto a sapere come fare in java. Ad ogni modo, se Freemarker è la risposta probabile, allora posso dire che anche JSP sarà la risposta corretta?
Rakesh Juyal,

1
Grazie, ma per il mio compito a portata di mano questo sembra essere un po 'eccessivo. Ma grazie.
Andy,

1
@Rakesh JSP è una cosa molto specifica "view / FE". Ho usato FreeMarker in passato per generare XML e talvolta persino generare file JAVA. Andy temo che dovrai scrivere tu stesso un'utilità (o come quella prescritta sopra)
Kannan Ekanath,

@Boris quale è il miglior freemarker vs velocity vs stringtemplate?
gaurav,



0

Dovresti dare un'occhiata alla libreria ufficiale ICU4J . Fornisce una classe MessageFormat simile a quella disponibile con JDK ma questa prima supporta segnaposto con nome.

A differenza di altre soluzioni fornite in questa pagina. ICU4j fa parte del progetto ICU che è gestito da IBM e regolarmente aggiornato. Inoltre, supporta casi d'uso avanzati come la pluralizzazione e molto altro.

Ecco un esempio di codice:

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

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

System.out.println(messageFormat.format(args));

0

Esiste un plug-in Java per utilizzare l'interpolazione di stringhe in Java (come in Kotlin, JavaScript). Supporta Java 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Utilizzo delle variabili nei letterali stringa:

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

Usando le espressioni:

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

Utilizzando le funzioni:

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

Per maggiori informazioni, leggi il progetto README.

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.