Dinamai placeholder dalam pemformatan string

175

Dengan Python, saat memformat string, saya bisa mengisi placeholder dengan nama alih-alih dengan posisi, seperti itu:

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

Saya ingin tahu apakah itu mungkin di Jawa (mudah-mudahan, tanpa perpustakaan eksternal)?

Andy
sumber
Anda dapat memperluas MessageFormat dan mengimplementasikan fungsi pemetaan dari variabel ke indeks di dalamnya.
vpram86
1
Beberapa sejarah: Java sebagian besar menyalin C / C ++ pada masalah ini karena mencoba memikat pengembang dari dunia C ++ di mana %smerupakan praktik umum. en.wikipedia.org/wiki/Printf_format_string#History Juga perhatikan bahwa beberapa IDE dan FindBugs mungkin secara otomatis mendeteksi jumlah% s dan% d yang tidak cocok, tetapi saya masih lebih suka kolom yang bernama.
Christophe Roussy

Jawaban:

143

StrSubstitutor dari jakarta commons lang adalah cara yang ringan untuk melakukan ini asalkan nilai Anda sudah diformat dengan benar.

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

Hasil di atas dalam:

"Ada nilai '1' yang salah di kolom # 2"

Saat menggunakan Maven Anda dapat menambahkan ketergantungan ini ke pom.xml Anda:

<dependency>
    <groupId>org.apache.commons</groupId>
    <artifactId>commons-lang3</artifactId>
    <version>3.4</version>
</dependency>
schup
sumber
2
Saya menemukan itu mengecewakan bahwa perpustakaan tidak membuang jika kunci tidak ditemukan, namun, jika Anda menggunakan sintaks default ( ${arg}) alih-alih yang kustom di atas ( %(arg)) maka regex tidak akan mengkompilasi, yang merupakan efek yang diinginkan.
John Lehmann
2
Anda dapat mengatur VariableResolver kustom dan melemparkan Pengecualian jika kunci tidak ada di peta.
Mene
7
Utas lama, tetapi pada 3.6, paket teks tidak digunakan lagi untuk teks umum. commons.apache.org/proper/commons-text
Jeff Walker
74

tidak cukup, tetapi Anda dapat menggunakan MessageFormat untuk referensi satu nilai beberapa kali:

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

Di atas dapat dilakukan dengan String.format () juga, tapi saya menemukan pembersih sintaks messageFormat jika Anda perlu membangun ekspresi kompleks, ditambah Anda tidak perlu peduli dengan jenis objek yang Anda masukkan ke dalam string

giladbu
sumber
tidak yakin mengapa Anda tidak bisa, posisi dalam string tidak penting, hanya posisi dalam daftar args, yang menjadikannya masalah penggantian nama. Anda tahu nama kunci, yang berarti Anda dapat menentukan posisi kunci dalam daftar argumen. mulai sekarang nilai akan dikenal sebagai 0 dan kolom sebagai 1: MessageeFormat.format ("Ada nilai yang salah \" {0} \ "di kolom # {1}, menggunakan {0} karena nilai dapat menyebabkan banyak masalah", valueMap .get ('value'), valueMap.get ('kolom'));
giladbu
1
Terima kasih atas petunjuknya, itu membantu saya untuk menulis fungsi sederhana yang melakukan persis apa yang saya inginkan (saya taruh di bawah).
Andy
1
Setuju, sintaksinya jauh lebih bersih. Terlalu buruk MessageFormat punya pikiran sendiri ketika datang untuk memformat nilai numerik.
Kees de Kooter
Dan tampaknya mengabaikan placeholder dikelilingi oleh tanda kutip tunggal.
Kees de Kooter
MessageFormatbagus tapi tidak praktis untuk konten json yang relatif besar
EliuX
32

Contoh lain dari Apache Common StringSubstitutor untuk placeholder bernama sederhana.

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
sumber
Jika Anda berharap untuk memuat file yang sangat besar, saya menemukan perpustakaan ini juga mendukung replaceInnilai pengganti mana yang menjadi buffer: StringBuilder, atau TextStringBuilder. Dengan pendekatan ini, seluruh isi file tidak akan dimuat ke dalam memori.
Edward Corrigall
15

Anda dapat menggunakan pustaka StringTemplate , ia menawarkan apa yang Anda inginkan dan banyak lagi.

import org.antlr.stringtemplate.*;

final StringTemplate hello = new StringTemplate("Hello, $name$");
hello.setAttribute("name", "World");
System.out.println(hello.toString());
Fixpoint
sumber
Punya masalah dengan 'char:unexpected char: '''
AlikElzin-kilaka
11

Untuk kasus-kasus yang sangat sederhana, Anda cukup menggunakan penggantian String hardcoded, tidak perlu perpustakaan di sana:

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

PERINGATAN : Saya hanya ingin menunjukkan kode sesederhana mungkin. Tentu saja JANGAN gunakan ini untuk kode produksi serius di mana masalah keamanan, seperti yang dinyatakan dalam komentar: melarikan diri, penanganan kesalahan dan keamanan adalah masalah di sini. Tetapi dalam kasus terburuk Anda sekarang tahu mengapa menggunakan lib 'baik' diperlukan :-)

Christophe Roussy
sumber
1
ini sederhana dan mudah, tetapi downside adalah gagal secara diam-diam ketika nilainya tidak ditemukan. Itu hanya meninggalkan pengganti di string asli.
kiedysktos
@kiedysktos, Anda bisa memperbaikinya dengan melakukan cek, tetapi jika Anda menginginkan semuanya, gunakan lib :)
Christophe Roussy
2
Peringatan: Karena teknik ini memperlakukan hasil substitusi antara sebagai format string mereka sendiri, solusi ini rentan terhadap format serangan string . Setiap solusi yang benar harus membuat satu melewati string format.
200_sukses
@ 200_success Ya poin bagus berbicara tentang keamanan, tentu saja kode ini bukan untuk penggunaan produksi serius ...
Christophe Roussy
8

Terima kasih atas seluruh bantuan Anda! Dengan menggunakan semua petunjuk Anda, saya telah menulis rutin untuk melakukan apa yang saya inginkan - pemformatan string seperti python menggunakan kamus. Karena saya pemula Java, setiap petunjuk sangat dihargai.

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
sumber
Tidak seperti dalam jawaban Lombo, ini tidak bisa terjebak dalam loop yang tak terbatas, karena formatPostidak dapat menampung formatKey.
Aaron Dufour
6
Peringatan: Karena loop memperlakukan hasil substitusi antara sebagai format string mereka sendiri, solusi ini rentan terhadap serangan format string . Setiap solusi yang benar harus membuat satu melewati string format.
200_sukses
6

Ini adalah utas lama, tetapi hanya sebagai catatan, Anda juga bisa menggunakan gaya Java 8, seperti ini:

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

Pemakaian:

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

Outputnya adalah:

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
sumber
Ini brilian, tetapi sayangnya itu melanggar spesifikasi di sini docs.oracle.com/javase/8/docs/api/java/util/stream/… Fungsi kombiner harus mengembalikan parameter kedua jika parameter pertama adalah identitas. Yang di atas akan mengembalikan identitas sebagai gantinya. Itu juga melanggar aturan ini: combiner.apply (u, akumulator.apply (identitas, t)) == akumulator.apply (u, t)
Ali Cheaito
Menarik ... tetapi hanya jika Anda mengusulkan cara yang lebih baik untuk melewati peta, juga jika mungkin setelah templat seperti kebanyakan kode pemformatan.
Christophe Roussy
4
Peringatan: Karena .reduce()memperlakukan hasil substitusi antara sebagai format string mereka sendiri, solusi ini rentan terhadap format serangan string . Setiap solusi yang benar harus membuat satu melewati string format.
200_sukses
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());
}

Contoh:

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
sumber
2
Ini harus menjadi jawaban, karena menghindari serangan format string yang sebagian besar solusi lain di sini rentan. Perhatikan bahwa Java 9 membuatnya lebih sederhana, dengan dukungan untuk .replaceAll()panggilan balik penggantian string .
200_sukses
Ini harus menjadi jawabannya, karena itu tidak menggunakan perpustakaan eksternal.
Bohao LI
3

Saya penulis perpustakaan kecil yang melakukan apa yang Anda inginkan:

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

Atau Anda dapat mengaitkan argumen:

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

// Output: "5 + 10 = 15"
Andrei Ciobanu
sumber
apakah mungkin untuk melakukan pemformatan berbasis kondisi dengan perpustakaan Anda?
gaurav
@gaurav tidak cukup. Jika Anda memerlukannya, Anda memerlukan pustaka templating berfitur lengkap.
Andrei Ciobanu
2

Metode replaceEach Apache Commons Lang mungkin berguna tergantung pada kebutuhan spesifik Anda. Anda dapat dengan mudah menggunakannya untuk mengganti placeholder dengan nama dengan panggilan metode tunggal ini:

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

Diberikan beberapa input teks, ini akan menggantikan semua kemunculan placeholder di array string pertama dengan nilai yang sesuai di yang kedua.

Pyves
sumber
1

Anda dapat memiliki sesuatu seperti ini di kelas helper string

/**
 * 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
sumber
Terima kasih banyak, ia melakukan hampir apa yang saya inginkan, tetapi satu-satunya hal itu tidak pengubah akun (pertimbangkan "% (kunci) 08d")
Andy
1
Perhatikan juga bahwa ini akan menjadi infinite loop jika salah satu nilai yang digunakan berisi entri yang sesuai.
Aaron Dufour
1
Peringatan: Karena loop memperlakukan hasil substitusi antara sebagai format string mereka sendiri, solusi ini rentan terhadap serangan format string . Setiap solusi yang benar harus membuat satu melewati string format.
200_sukses
1

Jawaban saya adalah:

a) gunakan StringBuilder bila memungkinkan

b) menjaga (dalam bentuk apa pun: integer adalah yang terbaik, khusus char seperti dolar makro dll) posisi "placeholder" dan kemudian gunakan StringBuilder.insert() (beberapa versi argumen).

Menggunakan perpustakaan eksternal tampaknya berlebihan dan saya percaya menurunkan kinerja yang signifikan, ketika StringBuilder dikonversi ke String secara internal.

Jacek Cz
sumber
1

Berdasarkan jawaban yang saya buat MapBuilderkelas:

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

}

kemudian saya membuat kelas StringFormatuntuk pemformatan 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;
    }

}

yang bisa Anda gunakan seperti itu:

String bookingDate = StringFormat.format("From $startDate to $endDate"), 
        "$startDate", formattedStartDate, 
        "$endDate", formattedEndDate
);
Rafal Enden
sumber
1
Peringatan: Karena loop memperlakukan hasil substitusi antara sebagai format string mereka sendiri, solusi ini rentan terhadap serangan format string . Setiap solusi yang benar harus membuat satu melewati string format.
200_sukses
1

Saya membuat juga kelas util / helper (menggunakan jdk 8) yang dapat memformat string dan menggantikan kemunculan variabel.

Untuk tujuan ini saya menggunakan metode Matchers "appendReplacement" yang melakukan semua substitusi dan loop hanya pada bagian yang terpengaruh dari string format.

Kelas pembantu saat ini tidak didokumentasikan dengan baik oleh javadoc. Saya akan mengubah ini di masa depan;) Pokoknya saya mengomentari baris yang paling penting (saya harap).

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

Anda dapat membuat instance kelas untuk Peta tertentu dengan nilai (atau awalan akhiran atau noKeyFunction) seperti:

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

Lebih jauh lagi, Anda dapat menentukan apa yang terjadi jika kunci dalam nilai-nilai Peta hilang (berfungsi dengan dua cara, mis. Nama variabel yang salah dalam format string atau kunci yang hilang di Peta). Perilaku default adalah pengecualian yang tidak dicentang yang dilemparkan (tidak dicentang karena saya menggunakan Fungsi jdk8 default yang tidak dapat menangani pengecualian yang diperiksa) seperti:

    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.

Anda dapat menentukan perilaku khusus dalam panggilan konstruktor seperti:

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

atau mendelegasikannya kembali ke perilaku default no key:

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

Untuk penanganan yang lebih baik ada juga representasi metode statis seperti:

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

Tidak ada yang dibangun ke Jawa pada saat penulisan ini. Saya akan menyarankan untuk menulis implementasi Anda sendiri. Preferensi saya adalah antarmuka pembangun fasih sederhana alih-alih membuat peta dan meneruskan fungsinya - Anda berakhir dengan sepotong kode yang berdekatan, misalnya:

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

Berikut ini adalah implementasi sederhana:

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

Coba Freemarker , templating library.

teks alternatif

Boris Pavlovic
sumber
4
Freemarker? Saya kira dia mau tahu, bagaimana melakukan ini di java polos. Bagaimanapun jika Freemarker adalah jawaban yang memungkinkan maka bisakah saya mengatakan JSP juga akan menjadi jawaban yang benar?
Rakesh Juyal
1
Terima kasih, tetapi untuk tugas saya, ini sepertinya agak berlebihan. Tapi terima kasih.
Andy
1
@Rakesh JSP adalah hal yang sangat spesifik "view / FE". Saya telah menggunakan FreeMarker di masa lalu untuk menghasilkan XML dan kadang-kadang bahkan menghasilkan file JAVA. Andy takut Anda harus menulis sendiri satu utilitas (atau seperti yang ditentukan di atas)
Kannan Ekanath
@ Boris mana yang freemarker lebih baik vs kecepatan vs stringtemplate?
gaurav
0

Anda harus melihat perpustakaan resmi ICU4J . Ini menyediakan kelas MessageFormat mirip dengan yang tersedia dengan JDK tetapi mantan ini mendukung placeholder bernama.

Tidak seperti solusi lain yang disediakan di halaman ini. ICU4j adalah bagian dari proyek ICU yang dikelola oleh IBM dan diperbarui secara berkala. Selain itu, mendukung kasus penggunaan tingkat lanjut seperti pluralisasi dan banyak lagi.

Berikut ini contoh kode:

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

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

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

Ada Plugin Java untuk menggunakan interpolasi string di Jawa (seperti di Kotlin, JavaScript). Mendukung Java 8, 9, 10, 11 ... https://github.com/antkorwin/better-strings

Menggunakan variabel dalam string literal:

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

Menggunakan ekspresi:

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

Menggunakan fungsi:

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

Untuk info lebih lanjut, silakan baca proyek README.

hermeslm
sumber