Nazwane symbole zastępcze w formatowaniu ciągów

175

W Pythonie, podczas formatowania łańcucha, mogę wypełniać symbole zastępcze nazwą, a nie pozycją, na przykład:

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

Zastanawiam się, czy jest to możliwe w Javie (miejmy nadzieję, bez zewnętrznych bibliotek)?

Andy
źródło
Możesz rozszerzyć MessageFormat i zaimplementować w tym funkcję mapowania ze zmiennych na indeksy.
vpram86
1
Trochę historii: Java głównie kopiowała C / C ++ w tej sprawie, próbując zwabić programistów ze świata C ++, gdzie %sbyła powszechna praktyka. en.wikipedia.org/wiki/Printf_format_string#History Należy również zauważyć, że niektóre IDE i FindBugs mogą automatycznie wykrywać niezgodne liczby% si% d, ale nadal wolałbym nazwane pola.
Christophe Roussy,

Odpowiedzi:

143

StrSubstitutor of jakarta commons lang to lekki sposób na zrobienie tego, pod warunkiem, że twoje wartości są już poprawnie sformatowane.

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

Powyższe skutkuje:

„W kolumnie 2 jest nieprawidłowa wartość„ 1 ””

Używając Mavena, możesz dodać tę zależność do swojego pom.xml:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>
schup
źródło
2
Zauważyłem rozczarowanie, że biblioteka nie wyrzuca, jeśli nie znaleziono kluczy, jednak jeśli użyjesz domyślnej składni ( ${arg}) zamiast niestandardowej powyżej ( %(arg)), to wyrażenie regularne nie zostanie skompilowane, co jest pożądanym efektem.
John Lehmann
2
Możesz ustawić niestandardowy VariableResolver i zgłosić wyjątek, jeśli klucza nie ma na mapie.
Mene
7
Stary wątek, ale od wersji 3.6 pakiet tekstowy został zastąpiony zwykłym tekstem. commons.apache.org/proper/commons-text
Jeff Walker
74

niezupełnie, ale możesz użyć MessageFormat do wielokrotnego odwoływania się do jednej wartości:

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

Powyższe można również zrobić za pomocą String.format (), ale uważam, że czyścisz składnię messageFormat, jeśli potrzebujesz zbudować złożone wyrażenia, a ponadto nie musisz przejmować się typem obiektu, który umieszczasz w ciągu

giladbu
źródło
Nie jestem pewien, dlaczego nie możesz, pozycja w łańcuchu nie jest ważna, tylko pozycja na liście argumentów, co sprawia, że ​​jest to problem ze zmianą nazwy. Znasz nazwy kluczy, co oznacza, że ​​możesz zdecydować o pozycji klucza na liście argumentów. od teraz wartość będzie znana jako 0, a kolumna jako 1: MessageeFormat.format ("W kolumnie nr {1} jest nieprawidłowa wartość \" {0} \ ", użycie {0} jako wartości może powodować wiele problemów", valueMap .get ('wartość'), wartośćMap.get ('kolumna'));
giladbu,
1
Dzięki za wskazówkę, pomogła mi napisać prostą funkcję, która robi dokładnie to, czego chcę (umieściłem to poniżej).
Andy,
1
Zgoda, składnia jest znacznie czystsza. Szkoda, że ​​MessageFormat ma własny umysł, jeśli chodzi o formatowanie wartości liczbowych.
Kees de Kooter
I wydaje się, że ignoruje symbole zastępcze otoczone pojedynczymi cudzysłowami.
Kees de Kooter
MessageFormatjest świetny, ale uciążliwy dla stosunkowo dużej zawartości json
EliuX
32

Kolejny przykład Apache Common StringSubstitutor dla prostego nazwanego symbolu zastępczego.

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
źródło
Jeśli spodziewasz się załadować bardzo duże pliki, zauważyłem, że ta biblioteka obsługuje również replaceInzastępowanie wartości w buforze: StringBuilder lub TextStringBuilder. Przy takim podejściu cała zawartość pliku nie zostanie załadowana do pamięci.
Edward Corrigall
15

Możesz użyć biblioteki StringTemplate , oferuje to, czego chcesz i wiele więcej.

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());
Punkt stały
źródło
Miałem problemy z 'char:unexpected char: '''
AlikElzin-kilaka
11

W bardzo prostych przypadkach możesz po prostu użyć zakodowanej na stałe zamiany String, nie ma potrzeby posiadania tam biblioteki:

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

UWAGA : chciałem tylko pokazać najprostszy możliwy kod. Oczywiście NIE używaj tego do poważnego kodu produkcyjnego, w którym bezpieczeństwo ma znaczenie, jak stwierdzono w komentarzach: ucieczka, obsługa błędów i bezpieczeństwo są tutaj problemem. Ale w najgorszym przypadku już wiesz, dlaczego użycie `` dobrej '' biblioteki jest wymagane :-)

Christophe Roussy
źródło
1
ten jest prosty i łatwy, ale wadą jest to, że cicho zawodzi, gdy wartość nie została znaleziona. Po prostu pozostawia symbol zastępczy w oryginalnym ciągu.
kiedysktos
@kiedysktos, możesz to poprawić wykonując check, ale jeśli chcesz mieć pełną wersję, użyj lib :)
Christophe Roussy
2
Ostrzeżenie: ponieważ ta technika traktuje pośrednie wyniki podstawiania jako własne ciągi formatujące, to rozwiązanie jest podatne na ataki na ciągi formatujące . Każde poprawne rozwiązanie powinno wykonać jedno przejście przez ciąg formatu.
200_success
@ 200_success Tak, słuszna uwaga, mówiąc o bezpieczeństwie, oczywiście ten kod nie jest przeznaczony do poważnego użytku produkcyjnego ...
Christophe Roussy
8

Dzięki za pomoc! Korzystając ze wszystkich wskazówek, napisałem procedurę, która robi dokładnie to, czego chcę - formatowanie ciągów w stylu Pythona za pomocą słownika. Ponieważ jestem nowicjuszem w Javie, wszelkie wskazówki są mile widziane.

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
źródło
W przeciwieństwie do odpowiedzi Lombo, nie może to utknąć w nieskończonej pętli, ponieważ formatPosnie może zawierać formatKey.
Aaron Dufour
6
Ostrzeżenie: ponieważ pętla traktuje pośrednie wyniki podstawiania jako własne ciągi formatujące, to rozwiązanie jest podatne na ataki na ciągi formatujące . Każde poprawne rozwiązanie powinno wykonać jedno przejście przez ciąg formatu.
200_success
6

To jest stary wątek, ale tak dla przypomnienia, możesz również użyć stylu Java 8, na przykład:

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

Stosowanie:

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

Wynik będzie:

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
źródło
Jest to genialne, ale niestety narusza specyfikacje tutaj docs.oracle.com/javase/8/docs/api/java/util/stream/… Funkcja sumatora musi zwrócić drugi parametr, jeśli pierwszy parametr jest tożsamością. Ten powyżej zwróciłby zamiast tego tożsamość. To również narusza tę zasadę: combiner.apply (u, accumulator.apply (identity, t)) == accumulator.apply (u, t)
Ali Cheaito
Ciekawe ... ale tylko jeśli zaproponujesz ładniejszy sposób przekazania mapy, także jeśli to możliwe po szablonie, jak większość kodu formatującego.
Christophe Roussy,
4
Ostrzeżenie: ponieważ .reduce()pośrednie wyniki podstawiania traktują jako własne ciągi formatujące, to rozwiązanie jest podatne na ataki na ciągi formatujące . Każde poprawne rozwiązanie powinno wykonać jedno przejście przez ciąg formatu.
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());
}

Przykład:

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
źródło
2
To powinno być odpowiedzią, ponieważ pozwala uniknąć ataków typu string string, na które narażona jest większość innych rozwiązań. Zauważ, że Java 9 sprawia, że ​​jest to znacznie prostsze, z obsługą .replaceAll()wywołań zwrotnych zastępowania ciągów .
200_success
To powinna być odpowiedź, ponieważ nie korzysta z żadnych zewnętrznych bibliotek.
Bohao LI
3

Jestem autorem małej biblioteki, która robi dokładnie to, co chcesz:

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

Lub możesz połączyć argumenty:

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

// Output: "5 + 10 = 15"
Andrei Ciobanu
źródło
czy w bibliotece można formatować na podstawie warunków?
gaurav
@gaurav niezupełnie. Jeśli potrzebujesz, potrzebujesz w pełni funkcjonalnej biblioteki szablonów.
Andrei Ciobanu
2

Metoda replaceEach z Apache Commons Lang może się przydać w zależności od twoich konkretnych potrzeb. Możesz go łatwo użyć do zastąpienia symboli zastępczych według nazwy za pomocą tego pojedynczego wywołania metody:

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

Biorąc pod uwagę pewien tekst wejściowy, spowoduje to zastąpienie wszystkich wystąpień symboli zastępczych w pierwszej tablicy ciągów odpowiednimi wartościami w drugiej.

Pyves
źródło
1

Możesz mieć coś takiego w klasie pomocniczej napisów

/**
 * 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
źródło
Wielkie dzięki, robi prawie to, co chcę, ale jedyną rzeczą jest to, że nie uwzględnia modyfikatorów (weź pod uwagę „% (key) 08d”)
Andy,
1
Zauważ również, że przechodzi to do nieskończonej pętli, jeśli którakolwiek z używanych wartości zawiera odpowiedni wpis.
Aaron Dufour
1
Ostrzeżenie: ponieważ pętla traktuje pośrednie wyniki podstawiania jako własne ciągi formatujące, to rozwiązanie jest podatne na ataki na ciągi formatujące . Każde poprawne rozwiązanie powinno wykonać jedno przejście przez ciąg formatu.
200_success
1

Moja odpowiedź brzmi:

a) użyj StringBuilder, jeśli to możliwe

b) zachowaj (w dowolnej postaci: liczba całkowita jest najlepszym, konkretnym znakiem, np. makro dolara itp.) pozycję "symbolu zastępczego", a następnie użyj StringBuilder.insert()(kilka wersji argumentów).

Korzystanie z zewnętrznych bibliotek wydaje się przesadą i uważam, że znacznie obniża wydajność, gdy StringBuilder jest wewnętrznie konwertowany na String.

Jacek Cz
źródło
1

Na podstawie odpowiedzi stworzyłem MapBuilderklasę:

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

}

następnie stworzyłem klasę StringFormatdo formatowania String:

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

}

którego możesz użyć w ten sposób:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);
Rafal Enden
źródło
1
Ostrzeżenie: ponieważ pętla traktuje pośrednie wyniki podstawiania jako własne ciągi formatujące, to rozwiązanie jest podatne na ataki na ciągi formatujące . Każde poprawne rozwiązanie powinno wykonać jedno przejście przez ciąg formatu.
200_success
1

Stworzyłem również klasę util / helper (przy użyciu jdk 8), która może formatować ciąg znaków i zastępować wystąpienia zmiennych.

W tym celu użyłem metody „appendReplacement” dopasowujących, która wykonuje wszystkie podstawienia i wykonuje pętlę tylko na odpowiednich częściach ciągu formatu.

Klasa pomocnicza nie jest obecnie dobrze udokumentowana w javadoc. Zmienię to w przyszłości;) Tak czy inaczej skomentowałem najważniejsze kwestie (mam nadzieję).

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

Możesz utworzyć instancję klasy dla określonej mapy z wartościami (lub prefiksem sufiksu lub noKeyFunction), takimi jak:

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

Co więcej, możesz zdefiniować, co się stanie, jeśli brakuje klucza w wartościach Map (działa w obie strony, np. Zła nazwa zmiennej w ciągu formatującym lub brakujący klucz w Map). Domyślnym zachowaniem jest wyrzucony niezaznaczony wyjątek (niezaznaczony, ponieważ używam domyślnej funkcji jdk8, która nie obsługuje sprawdzonych wyjątków), na przykład:

    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.

Możesz zdefiniować niestandardowe zachowanie w wywołaniu konstruktora, takie jak:

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

lub deleguj go z powrotem do domyślnego zachowania bez klucza:

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

Dla lepszej obsługi istnieją również statyczne reprezentacje metod, takie jak:

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
źródło
1

W tej chwili w Javie nie ma nic wbudowanego. Proponuję napisać własną implementację. Preferuję prosty, płynny interfejs konstruktora zamiast tworzenia mapy i przekazywania jej do funkcji - w rezultacie otrzymujesz ładny, ciągły fragment kodu, na przykład:

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

Oto prosta implementacja:

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
źródło
0

Wypróbuj Freemarker , bibliotekę szablonów.

tekst alternatywny

Boris Pavlović
źródło
4
Freemarker? Myślę, że chciałby wiedzieć, jak to zrobić w zwykłej Javie. W każdym razie, jeśli Freemarker jest prawdopodobną odpowiedzią, czy mogę powiedzieć, że JSP też będzie poprawną odpowiedzią?
Rakesh Juyal
1
Dzięki, ale dla mojego zadania wydaje się to być przesadą. Ale dzięki.
Andy
1
@Rakesh JSP jest bardzo specyficzną rzeczą dla "widoku / FE". W przeszłości korzystałem z FreeMarkera do generowania plików XML, a czasem nawet do generowania plików JAVA. Andy, obawiam się, że będziesz musiał sam napisać jedno narzędzie (lub takie jak opisane powyżej)
Kannan Ekanath,
@Boris, który jest lepszy freemarker vs velocity vs stringtemplate?
gaurav
1
@gaurav spójrz na stackoverflow.com/questions/3741618/… i dzone.com/articles/…
Boris Pavlović
0

Powinieneś zajrzeć do oficjalnej biblioteki ICU4J . Zapewnia MessageFormat klasę podobną do tej, dostępnej z JDK ale to dawnych podpór nazwanych zastępcze.

W przeciwieństwie do innych rozwiązań przedstawionych na tej stronie. ICU4j jest częścią projektu ICU który jest obsługiwany przez IBM i regularnie aktualizowany. Ponadto obsługuje zaawansowane przypadki użycia, takie jak liczba mnoga i wiele innych.

Oto przykład kodu:

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

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

System.out.println(messageFormat.format(args));
Laurent
źródło
0

Istnieje wtyczka Java do używania interpolacji ciągów w Javie (jak w Kotlin, JavaScript). Wspiera obsługę Javy, 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Używanie zmiennych w literałach łańcuchowych:

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

Korzystanie z wyrażeń:

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

Korzystanie z funkcji:

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

Aby uzyskać więcej informacji, przeczytaj projekt README.

hermeslm
źródło