Benannte Platzhalter in der Zeichenfolgenformatierung

174

In Python kann ich beim Formatieren von Zeichenfolgen Platzhalter eher nach Namen als nach Position füllen:

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

Ich frage mich, ob das in Java möglich ist (hoffentlich ohne externe Bibliotheken).

Andy
quelle
Sie können MessageFormat erweitern und die Zuordnungsfunktionalität von Variablen zu Indizes implementieren.
vpram86
1
Etwas Geschichte: Java hat in dieser Angelegenheit meistens C / C ++ kopiert, um Entwickler aus der C ++ - Welt zu locken, in der %ses gängige Praxis war. en.wikipedia.org/wiki/Printf_format_string#History Beachten Sie auch, dass einige IDE- und FindBugs automatisch nicht übereinstimmende% s- und% d-Zählungen erkennen, aber ich würde trotzdem benannte Felder bevorzugen.
Christophe Roussy

Antworten:

143

StrSubstitutor von jakarta commons lang ist eine leichte Methode, um dies zu tun, vorausgesetzt, Ihre Werte sind bereits korrekt formatiert.

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

Das Obige führt zu:

"Es gibt einen falschen Wert '1' in Spalte 2"

Wenn Sie Maven verwenden, können Sie diese Abhängigkeit zu Ihrer pom.xml hinzufügen:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>
schup
quelle
2
Ich fand es enttäuschend, dass die Bibliothek nicht auslöst, wenn keine Schlüssel gefunden werden. Wenn Sie jedoch die Standardsyntax ( ${arg}) anstelle der benutzerdefinierten Syntax oben ( %(arg)) verwenden, wird der reguläre Ausdruck nicht kompiliert, was der gewünschte Effekt ist.
John Lehmann
2
Sie können einen benutzerdefinierten VariableResolver festlegen und eine Ausnahme auslösen, wenn der Schlüssel nicht in der Karte vorhanden ist.
Mene
7
Alter Thread, aber ab 3.6 war das Textpaket zugunsten von Commons-Text veraltet. commons.apache.org/proper/commons-text
Jeff Walker
73

nicht ganz, aber Sie können MessageFormat verwenden, um einen Wert mehrmals zu referenzieren:

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

Das Obige kann auch mit String.format () gemacht werden, aber ich finde messageFormat Syntax Cleaner, wenn Sie komplexe Ausdrücke erstellen müssen, und Sie müssen sich nicht um den Typ des Objekts kümmern, das Sie in den String einfügen

Giladbu
quelle
Ich bin mir nicht sicher, warum Sie das nicht können. Die Position in der Zeichenfolge ist nicht wichtig, nur die Position in der Liste der Argumente, was es zu einem Umbenennungsproblem macht. Sie kennen den Namen der Schlüssel, dh Sie können eine Position für einen Schlüssel in der Liste der Argumente festlegen. Von nun an wird der Wert als 0 und die Spalte als 1 bezeichnet: MessageeFormat.format ("Es gibt einen falschen Wert" {0} "in Spalte # {1}, die Verwendung von {0} als Wert kann viele Probleme verursachen", valueMap .get ('value'), valueMap.get ('column'));
Giladbu
1
Vielen Dank für einen Hinweis, es hat mir geholfen, eine einfache Funktion zu schreiben, die genau das tut, was ich will (ich habe es unten angegeben).
Andy
1
Einverstanden ist die Syntax viel sauberer. Schade, dass MessageFormat bei der Formatierung numerischer Werte einen eigenen Kopf hat.
Kees de Kooter
Und es scheint Platzhalter zu ignorieren, die von einfachen Anführungszeichen umgeben sind.
Kees de Kooter
MessageFormatist großartig, aber umständlich für relativ große
JSON-
32

Ein weiteres Beispiel für Apache Common StringSubstitutor für einfach benannte Platzhalter.

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
quelle
Wenn Sie erwarten, sehr große Dateien zu laden, unterstützt diese Bibliothek auch replaceIndas Ersetzen von Werten in einen Puffer: StringBuilder oder TextStringBuilder. Bei diesem Ansatz wird nicht der gesamte Inhalt der Datei in den Speicher geladen.
Edward Corrigall
15

Sie können die StringTemplate- Bibliothek verwenden, sie bietet, was Sie wollen und vieles mehr.

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());
Fixpunkt
quelle
Hatte Probleme mit dem 'Saibling:unexpected char: '''
AlikElzin-kilaka
11

In sehr einfachen Fällen können Sie einfach einen fest codierten String-Ersatz verwenden, ohne dass dort eine Bibliothek erforderlich ist:

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

WARNUNG : Ich wollte nur den einfachsten Code zeigen, der möglich ist. Verwenden Sie dies natürlich NICHT für seriösen Produktionscode, wenn Sicherheit wichtig ist, wie in den Kommentaren angegeben: Escape, Fehlerbehandlung und Sicherheit sind hier ein Problem. Aber im schlimmsten Fall wissen Sie jetzt, warum die Verwendung einer "guten" Bibliothek erforderlich ist :-)

Christophe Roussy
quelle
1
Dieser ist einfach und leicht, aber der Nachteil ist, dass er lautlos fehlschlägt, wenn der Wert nicht gefunden wurde. Der Platzhalter bleibt nur in der ursprünglichen Zeichenfolge.
Kiedysktos
@kiedysktos, Sie könnten es verbessern, indem Sie eine Überprüfung durchführen, aber wenn Sie das Ganze wollen, verwenden Sie eine Bibliothek :)
Christophe Roussy
2
Warnung: Da diese Technik Zwischensubstitutionsergebnisse als eigene Formatzeichenfolgen behandelt, ist diese Lösung anfällig für Formatzeichenfolgenangriffe . Jede richtige Lösung sollte einen einzigen Durchgang durch die Formatzeichenfolge machen.
200_erfolg
@ 200_success Ja, guter Punkt, um über Sicherheit zu sprechen, natürlich ist dieser Code nicht für ernsthafte Produktionszwecke bestimmt ...
Christophe Roussy
8

Danke für deine Hilfe! Mit all Ihren Hinweisen habe ich eine Routine geschrieben, um genau das zu tun, was ich will - eine pythonähnliche Zeichenfolgenformatierung mit einem Wörterbuch. Da ich Java-Neuling bin, sind alle Hinweise willkommen.

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
quelle
Anders als in Lombos Antwort kann dies nicht in einer Endlosschleife stecken bleiben, da formatPoses nicht enthalten kann formatKey.
Aaron Dufour
6
Warnung: Da die Schleife Zwischenersetzungsergebnisse als eigene Formatzeichenfolgen behandelt, ist diese Lösung anfällig für Formatzeichenfolgenangriffe . Jede richtige Lösung sollte einen einzigen Durchgang durch die Formatzeichenfolge machen.
200_erfolg
6

Dies ist ein alter Thread, aber nur für den Datensatz können Sie auch den Java 8-Stil wie folgt verwenden:

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

Verwendung:

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

Die Ausgabe wird sein:

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
quelle
Dies ist brillant, verstößt jedoch leider gegen die hier angegebenen Spezifikationen. Docs.oracle.com/javase/8/docs/api/java/util/stream/… Die Kombiniererfunktion muss den zweiten Parameter zurückgeben, wenn der erste Parameter die Identität ist. Die obige würde stattdessen die Identität zurückgeben. Es verstößt auch gegen diese Regel: combiner.apply (u, accumulator.apply (Identität, t)) == accumulator.apply (u, t)
Ali Cheaito
Interessant ... aber nur, wenn Sie einen schöneren Weg vorschlagen, um die Karte zu übergeben, auch wenn möglich nach der Vorlage, wie die meisten Formatierungscodes.
Christophe Roussy
4
Warnung: Da die .reduce()Ergebnisse der Zwischenersetzung als eigene Formatzeichenfolgen behandelt werden, ist diese Lösung anfällig für Formatzeichenfolgenangriffe . Jede richtige Lösung sollte einen einzigen Durchgang durch die Formatzeichenfolge machen.
200_erfolg
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());
}

Beispiel:

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
quelle
2
Dies sollte die Antwort sein, da dadurch die Format-String-Angriffe vermieden werden, für die die meisten anderen Lösungen hier anfällig sind. Beachten Sie, dass Java 9 es viel einfacher macht und .replaceAll()Rückrufe zum Ersetzen von Zeichenfolgen unterstützt .
200_erfolg
Dies sollte die Antwort sein, da keine externen Bibliotheken verwendet werden.
Bohao LI
3

Ich bin der Autor einer kleinen Bibliothek , die genau das tut, was Sie wollen:

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

Oder Sie können die Argumente verketten:

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

// Output: "5 + 10 = 15"
Andrei Ciobanu
quelle
Ist es möglich, bedingungsbasierte Formatierungen mit Ihrer Bibliothek durchzuführen?
Gaurav
@ Gaurav nicht ganz. Wenn Sie das brauchen, brauchen Sie eine voll funktionsfähige Vorlagenbibliothek.
Andrei Ciobanu
2

Die replaceEach- Methode von Apache Commons Lang kann je nach Ihren spezifischen Anforderungen nützlich sein. Sie können damit einfach Platzhalter durch Namen durch diesen einzelnen Methodenaufruf ersetzen:

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

Bei einem bestimmten Eingabetext werden dadurch alle Vorkommen der Platzhalter im ersten Zeichenfolgenarray durch die entsprechenden Werte im zweiten ersetzt.

Pyves
quelle
1

Sie könnten so etwas in einer String-Helfer-Klasse haben

/**
 * 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
quelle
Vielen Dank, es macht fast das, was ich will, aber das einzige ist, dass es keine Modifikatoren berücksichtigt (siehe "% (Schlüssel) 08d")
Andy
1
Beachten Sie auch, dass dies in eine Endlosschleife geht, wenn einer der verwendeten Werte den entsprechenden Eintrag enthält.
Aaron Dufour
1
Warnung: Da die Schleife Zwischenersetzungsergebnisse als eigene Formatzeichenfolgen behandelt, ist diese Lösung anfällig für Formatzeichenfolgenangriffe . Jede richtige Lösung sollte einen einzigen Durchgang durch die Formatzeichenfolge machen.
200_erfolg
1

Meine Antwort lautet:

a) Verwenden Sie nach Möglichkeit StringBuilder

b) Behalten Sie (in irgendeiner Form: Ganzzahl ist die beste, insbesondere Zeichen wie Dollar-Makro usw.) die Position des "Platzhalters" bei und verwenden Sie dann StringBuilder.insert()(wenige Versionen von Argumenten).

Die Verwendung externer Bibliotheken scheint übertrieben und ich glaube, dass die Leistung erheblich beeinträchtigt wird, wenn StringBuilder intern in String konvertiert wird.

Jacek Cz
quelle
1

Basierend auf der Antwort habe ich eine MapBuilderKlasse erstellt:

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

}

dann habe ich eine Klasse StringFormatfür die String-Formatierung erstellt:

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

}

was du so gebrauchen könntest:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);
Rafal Enden
quelle
1
Warnung: Da die Schleife Zwischenersetzungsergebnisse als eigene Formatzeichenfolgen behandelt, ist diese Lösung anfällig für Formatzeichenfolgenangriffe . Jede richtige Lösung sollte einen einzigen Durchgang durch die Formatzeichenfolge machen.
200_erfolg
1

Ich habe auch eine util / helper-Klasse (mit jdk 8) erstellt, die eine Zeichenfolge formatieren und das Auftreten von Variablen ersetzen kann.

Zu diesem Zweck habe ich die Matchers-Methode "appendReplacement" verwendet, die alle Ersetzungen vornimmt und nur die betroffenen Teile einer Formatzeichenfolge durchläuft.

Die Helferklasse ist derzeit nicht gut javadoc dokumentiert. Ich werde dies in Zukunft ändern;) Wie auch immer, ich habe die wichtigsten Zeilen kommentiert (hoffe ich).

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

Sie können eine Klasseninstanz für eine bestimmte Map mit Werten (oder Suffixpräfix oder noKeyFunction) wie folgt erstellen:

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

Außerdem können Sie definieren, was passiert, wenn ein Schlüssel in den Werten Map fehlt (funktioniert auf beide Arten, z. B. falscher Variablenname in Formatzeichenfolge oder fehlender Schlüssel in Map). Das Standardverhalten ist eine ausgelöste, nicht aktivierte Ausnahme (nicht aktiviert, da ich die Standardfunktion jdk8 verwende, die geprüfte Ausnahmen nicht verarbeiten kann) wie:

    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.

Sie können ein benutzerdefiniertes Verhalten im Konstruktoraufruf wie folgt definieren:

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

oder delegieren Sie es zurück an das Standardverhalten ohne Schlüssel:

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

Zur besseren Handhabung gibt es auch statische Methodendarstellungen wie:

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
quelle
1

Im Moment ist nichts in Java eingebaut. Ich würde vorschlagen, Ihre eigene Implementierung zu schreiben. Ich bevorzuge eine einfache, flüssige Builder-Oberfläche, anstatt eine Karte zu erstellen und sie an die Funktion zu übergeben. Am Ende erhalten Sie einen schönen zusammenhängenden Codeabschnitt, zum Beispiel:

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

Hier ist eine einfache Implementierung:

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
quelle
0

Probieren Sie Freemarker , Template-Bibliothek.

Alt-Text

Boris Pavlović
quelle
4
Freemarker? Ich denke, er ist bereit zu wissen, wie man das in einfachem Java macht. Wie auch immer, wenn Freemarker die wahrscheinliche Antwort ist, kann ich dann sagen, dass auch JSP die richtige Antwort ist?
Rakesh Juyal
1
Danke, aber für meine Aufgabe scheint dies eine Art Overkill zu sein. Aber danke.
Andy
1
@ Rakesh JSP ist eine sehr "view / FE" -spezifische Sache. Ich habe FreeMarker in der Vergangenheit zum Generieren von XML verwendet und manchmal sogar JAVA-Dateien generiert. Andy hat Angst, dass Sie selbst ein Dienstprogramm schreiben müssen (oder wie das oben vorgeschriebene)
Kannan Ekanath
@Boris welches ist besser Freemarker vs Velocity vs Stringtemplate?
Gaurav
1
@ Gaurav werfen Sie einen Blick auf stackoverflow.com/questions/3741618/… und dzone.com/articles/…
Boris Pavlović
0

Sie sollten sich die offizielle ICU4J-Bibliothek ansehen . Es bietet ein MessageFormat Klasse, die der mit dem JDK verfügbaren ähnelt, diese jedoch benannte Platzhalter unterstützt.

Im Gegensatz zu anderen auf dieser Seite bereitgestellten Lösungen. ICU4j ist Teil des ICU-Projekts , das von IBM verwaltet und regelmäßig aktualisiert wird. Darüber hinaus werden erweiterte Anwendungsfälle wie Pluralisierung und vieles mehr unterstützt.

Hier ist ein Codebeispiel:

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

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

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

Es gibt ein Java-Plugin zur Verwendung der String-Interpolation in Java (wie in Kotlin, JavaScript). Unterstützt Java 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Verwenden von Variablen in Zeichenfolgenliteralen:

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

Verwenden von Ausdrücken:

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

Funktionen verwenden:

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

Für weitere Informationen lesen Sie bitte das Projekt README.

Hermeslm
quelle