जावा स्ट्रिंग में टोकन के सेट को कैसे बदलें?


106

मेरे पास निम्नलिखित टेम्पलेट स्ट्रिंग है: "Hello [Name] Please find attached [Invoice Number] which is due on [Due Date]" :।

मेरे पास नाम, चालान संख्या और नियत तारीख के लिए स्ट्रिंग चर भी हैं - चर के साथ टेम्पलेट में टोकन को बदलने का सबसे अच्छा तरीका क्या है?

(ध्यान दें कि यदि किसी चर में टोकन होता है तो उसे प्रतिस्थापित नहीं किया जाना चाहिए)।


संपादित करें

@Laginimaineb और @ alan-moore के धन्यवाद के साथ, यहाँ मेरा समाधान है:

public static String replaceTokens(String text, 
                                   Map<String, String> replacements) {
    Pattern pattern = Pattern.compile("\\[(.+?)\\]");
    Matcher matcher = pattern.matcher(text);
    StringBuffer buffer = new StringBuffer();

    while (matcher.find()) {
        String replacement = replacements.get(matcher.group(1));
        if (replacement != null) {
            // matcher.appendReplacement(buffer, replacement);
            // see comment 
            matcher.appendReplacement(buffer, "");
            buffer.append(replacement);
        }
    }
    matcher.appendTail(buffer);
    return buffer.toString();
}

एक बात पर ध्यान दें, हालांकि, यह है कि StringBuffer केवल StringBuilder के समान है। हालांकि, इस उदाहरण के बाद से आपको स्ट्रिंग की इमारत को सिंक्रनाइज़ करने की आवश्यकता नहीं है आप स्ट्रिंगबर्ल का उपयोग करके बेहतर हो सकते हैं (भले ही ताले प्राप्त करना लगभग शून्य-लागत ऑपरेशन है)।
लैगिनेमिनेब

1
दुर्भाग्य से, आपको इस मामले में स्ट्रिंगबफ़र का उपयोग करना होगा; यह वही है जो appendXXX () तरीकों की उम्मीद करता है। वे जावा 4 के बाद से चारों ओर हैं, और स्ट्रिंगरबुइट को जावा 5 तक जोड़ा नहीं गया था। जैसा कि आपने कहा, हालांकि यह कोई बड़ी बात नहीं है, बस कष्टप्रद है।
एलन मूर

4
एक और बात: परिशिष्ट (), जैसे किXXXXX () के तरीके, कैप्चर-ग्रुप संदर्भों जैसे $ 1, $ 2, आदि की तलाश करते हैं, और उन्हें संबंधित कैप्चर ग्रुप के टेक्स्ट से बदल देते हैं। यदि आपके प्रतिस्थापन पाठ में डॉलर के संकेत या बैकस्लैश हो सकते हैं (जो डॉलर के संकेतों से बचने के लिए उपयोग किए जाते हैं), तो आपको समस्या हो सकती है। इससे निपटने का सबसे आसान तरीका है, जैसा कि मैंने ऊपर दिए गए कोड में किया है, एपेंड ऑपरेशन को दो चरणों में तोड़ना है।
एलन मूर

एलन - बहुत प्रभावित है कि तुम देखा। मुझे नहीं लगता था कि इतनी आसान समस्या को हल करना इतना मुश्किल होगा!
मार्क

जवाबों:


65

सबसे कुशल तरीका एक मिलानकर्ता का उपयोग लगातार भावों को खोजने और उन्हें बदलने के लिए करना होगा, फिर एक स्ट्रिंग बिल्डर को पाठ को जोड़ना होगा:

Pattern pattern = Pattern.compile("\\[(.+?)\\]");
Matcher matcher = pattern.matcher(text);
HashMap<String,String> replacements = new HashMap<String,String>();
//populate the replacements map ...
StringBuilder builder = new StringBuilder();
int i = 0;
while (matcher.find()) {
    String replacement = replacements.get(matcher.group(1));
    builder.append(text.substring(i, matcher.start()));
    if (replacement == null)
        builder.append(matcher.group(0));
    else
        builder.append(replacement);
    i = matcher.end();
}
builder.append(text.substring(i, text.length()));
return builder.toString();

10
यह है कि मैं इसे कैसे करूंगा, सिवाय इसके कि मैं बेमेल पाठ की नकल करने के लिए माचिस के परिशिष्ट () और परिशिष्ट () के तरीकों का उपयोग करूंगा; ऐसा करने की कोई जरूरत नहीं है।
एलन मूर

5
वास्तव में परिशिष्ट () और appentTail () विधियों के लिए एक StringBuffer की आवश्यकता होती है, जो snychronized है (जो यहाँ किसी काम का नहीं है)। दिया गया उत्तर एक स्ट्रिंगबर्ल का उपयोग करता है, जो मेरे परीक्षणों में 20% तेज है।
दूब

103

मुझे नहीं लगता कि इसके लिए आपको किसी टेंपलेटिंग इंजन या उस जैसी किसी चीज का इस्तेमाल करने की जरूरत है। आप String.formatविधि का उपयोग कर सकते हैं , जैसे:

String template = "Hello %s Please find attached %s which is due on %s";

String message = String.format(template, name, invoiceNumber, dueDate);

4
इसका एक नकारात्मक पहलू यह है कि आपको मापदंडों को सही क्रम में रखना होगा
गेरेटन

एक और यह है कि आप अपने स्वयं के प्रतिस्थापन टोकन प्रारूप को निर्दिष्ट नहीं कर सकते।
फ्रांज डी।

एक और यह है कि यह गतिशील रूप से काम नहीं करता है, कुंजी / मानों का डेटासेट करने में सक्षम है और फिर इसे किसी भी स्ट्रिंग पर लागू करता है
ब्रैड पार्क्स

43

दुर्भाग्य से ऊपर वर्णित सहज विधि String.format केवल जावा 1.5 से शुरू होता है (जो आजकल बहुत मानक होना चाहिए, लेकिन आप कभी नहीं जानते हैं)। इसके बजाय आप जावा की कक्षा MessageFormat का उपयोग कर सकते हैं प्लेसहोल्डर्स को बदलने के लिए ।

यह '' नंबर '' के रूप में प्लेसहोल्डर्स का समर्थन करता है, इसलिए आपका संदेश "हैलो {0} जैसा होगा कृपया संलग्न {1} खोजें जो कि {2}" के कारण है। ये स्ट्रिंग्स आसानी से रिसोर्सबंडल्स (जैसे कई स्थानों के साथ स्थानीयकरण के लिए) का उपयोग करके बाहर की जा सकती हैं। रिप्लेसमेंट 'मैसेजफ़ॉर्मैट' की स्थैतिक कार्यप्रणाली का उपयोग करते हुए किया जाएगा:

String msg = "Hello {0} Please find attached {1} which is due on {2}";
String[] values = {
  "John Doe", "invoice #123", "2009-06-30"
};
System.out.println(MessageFormat.format(msg, values));

3
मैं MessageFormat का नाम याद नहीं कर सका, और यह मूर्खतापूर्ण है कि इस उत्तर को खोजने के लिए मुझे कितना Googling करना पड़ा। हर कोई इस तरह से कार्य करता है जैसे कि यह या तो String.format है या इस अविश्वसनीय रूप से उपयोगी उपयोगिता को भूलकर एक 3-पार्टी का उपयोग करें।
पैट्रिक

1
यह 2004 से उपलब्ध है - मैं सिर्फ 2017 में अब तक के बारे में क्यों सीख रहा हूं? मैं कुछ कोड को फिर से कवर कर रहा हूं जो एस में कवर किया गया है StringBuilder.append()और मैं सोच रहा था "निश्चित रूप से एक बेहतर तरीका है ... कुछ और पायथोनिक ..." - और पवित्र बकवास, मुझे लगता है कि यह विधि पायथन के प्रारूपण के तरीकों से पहले हो सकती है। वास्तव में ... यह 2002 से अधिक पुराना हो सकता है ... मैं नहीं पा सकता जब यह वास्तव में अस्तित्व में आया ...
ArtOfWarfare

42

आप अपाचे वेग जैसी टेम्प्लेटिंग लाइब्रेरी का उपयोग करने का प्रयास कर सकते हैं।

http://velocity.apache.org/

यहाँ एक उदाहरण है:

import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.Velocity;

import java.io.StringWriter;

public class TemplateExample {
    public static void main(String args[]) throws Exception {
        Velocity.init();

        VelocityContext context = new VelocityContext();
        context.put("name", "Mark");
        context.put("invoiceNumber", "42123");
        context.put("dueDate", "June 6, 2009");

        String template = "Hello $name. Please find attached invoice" +
                          " $invoiceNumber which is due on $dueDate.";
        StringWriter writer = new StringWriter();
        Velocity.evaluate(context, writer, "TemplateName", template);

        System.out.println(writer);
    }
}

उत्पादन होगा:

नमस्कार मार्क। कृपया संलग्न चालान 42123 देखें जो 6 जून 2009 को होने वाला है।

मैंने अतीत में वेग का उपयोग किया है। बहुत अच्छा काम करता है।
हार्डवेयरगुई

4
सहमत हैं, पहिया को क्यों मजबूत किया जाए
वस्तुओं

6
इस तरह एक साधारण कार्य के लिए पूरे पुस्तकालय का उपयोग करना थोड़ा कठिन है। वेलोसिटी में बहुत सी अन्य विशेषताएं हैं, और मेरा दृढ़ता से मानना ​​है कि इस तरह एक सरल कार्य के लिए उपयुक्त नहीं है।
आंद्रेई सिओबानु

24

आप जटिल टेम्पलेट प्रतिस्थापन के लिए टेम्पलेट लाइब्रेरी का उपयोग कर सकते हैं।

FreeMarker एक बहुत अच्छा विकल्प है।

http://freemarker.sourceforge.net/

लेकिन सरल कार्य के लिए, एक सरल उपयोगिता वर्ग है जो आपकी सहायता कर सकता है।

org.apache.commons.lang3.text.StrSubstitutor

यह बहुत शक्तिशाली, अनुकूलन योग्य और उपयोग में आसान है।

यह वर्ग पाठ का एक टुकड़ा लेता है और इसके भीतर सभी चर को प्रतिस्थापित करता है। एक चर की डिफ़ॉल्ट परिभाषा $ {variableName} है। उपसर्ग और प्रत्यय को कंस्ट्रक्टर और सेट विधियों के माध्यम से बदला जा सकता है।

परिवर्तनीय मूल्यों को आमतौर पर एक नक्शे से हल किया जाता है, लेकिन सिस्टम गुणों से, या कस्टम चर रिज़ॉल्वर की आपूर्ति करके भी हल किया जा सकता है।

उदाहरण के लिए, यदि आप सिस्टम परिवेश चर को टेम्पलेट स्ट्रिंग में बदलना चाहते हैं, तो यहां कोड है:

public class SysEnvSubstitutor {
    public static final String replace(final String source) {
        StrSubstitutor strSubstitutor = new StrSubstitutor(
                new StrLookup<Object>() {
                    @Override
                    public String lookup(final String key) {
                        return System.getenv(key);
                    }
                });
        return strSubstitutor.replace(source);
    }
}


17
System.out.println(MessageFormat.format("Hello {0}! You have {1} messages", "Join",10L));

आउटपुट: हैलो ज्वाइन! आपके पास 10 संदेश हैं "


2
जॉन स्पष्ट रूप से अपने संदेशों की जाँच करता है जितनी बार मैं अपने "स्पैम" फ़ोल्डर की जाँच करता हूँ यह एक लंबा है।
हेममेल्स

9

यह निर्भर करता है कि आप जिस वास्तविक डेटा को बदलना चाहते हैं वह कहाँ स्थित है। आपके पास इस तरह का एक नक्शा हो सकता है:

Map<String, String> values = new HashMap<String, String>();

सभी डेटा जिसमें प्रतिस्थापित किया जा सकता है। फिर आप नक्शे पर पुनरावृत्ति कर सकते हैं और स्ट्रिंग में सब कुछ बदल सकते हैं:

String s = "Your String with [Fields]";
for (Map.Entry<String, String> e : values.entrySet()) {
  s = s.replaceAll("\\[" + e.getKey() + "\\]", e.getValue());
}

आप स्ट्रिंग पर भी पुनरावृति कर सकते हैं और मानचित्र में तत्वों को पा सकते हैं। लेकिन यह थोड़ा और अधिक जटिल है क्योंकि आपको स्ट्रिंग की खोज करने की आवश्यकता है []। आप इसे पैटर्न और माचिस के उपयोग से नियमित अभिव्यक्ति के साथ कर सकते हैं।


9
String.format("Hello %s Please find attached %s which is due on %s", name, invoice, date)

1
धन्यवाद - लेकिन मेरे मामले में टेम्प्लेट स्ट्रिंग को उपयोगकर्ता द्वारा संशोधित किया जा सकता है, इसलिए मैं टोकन के आदेश के बारे में सुनिश्चित नहीं हो सकता हूं
मार्क

3

$ {चर} शैली टोकन की जगह के लिए मेरा समाधान (यहां के जवाबों और स्प्रिंग उरीटेम्प्ट द्वारा प्रेरित):

public static String substituteVariables(String template, Map<String, String> variables) {
    Pattern pattern = Pattern.compile("\\$\\{(.+?)\\}");
    Matcher matcher = pattern.matcher(template);
    // StringBuilder cannot be used here because Matcher expects StringBuffer
    StringBuffer buffer = new StringBuffer();
    while (matcher.find()) {
        if (variables.containsKey(matcher.group(1))) {
            String replacement = variables.get(matcher.group(1));
            // quote to work properly with $ and {,} signs
            matcher.appendReplacement(buffer, replacement != null ? Matcher.quoteReplacement(replacement) : "null");
        }
    }
    matcher.appendTail(buffer);
    return buffer.toString();
}


1

अपाचे कॉमन्स लाइब्रेरी के साथ, आप बस Stringutils.replaceEach का उपयोग कर सकते हैं :

public static String replaceEach(String text,
                             String[] searchList,
                             String[] replacementList)

से प्रलेखन :

स्ट्रिंग्स की सभी घटनाओं को एक और स्ट्रिंग के भीतर बदल देता है।

इस पद्धति के लिए दिया गया एक शून्य संदर्भ एक नो-ऑप है, या यदि कोई "खोज स्ट्रिंग" या "स्ट्रिंग टू रिप्लेस" अशक्त है, तो उसे प्रतिस्थापित किया जाएगा। यह नहीं दोहराएगा। दोहराने की जगह के लिए, अधिभार विधि को बुलाओ।

 StringUtils.replaceEach(null, *, *)        = null

  StringUtils.replaceEach("", *, *)          = ""

  StringUtils.replaceEach("aba", null, null) = "aba"

  StringUtils.replaceEach("aba", new String[0], null) = "aba"

  StringUtils.replaceEach("aba", null, new String[0]) = "aba"

  StringUtils.replaceEach("aba", new String[]{"a"}, null)  = "aba"

  StringUtils.replaceEach("aba", new String[]{"a"}, new String[]{""})  = "b"

  StringUtils.replaceEach("aba", new String[]{null}, new String[]{"a"})  = "aba"

  StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"w", "t"})  = "wcte"
  (example of how it does not repeat)

StringUtils.replaceEach("abcde", new String[]{"ab", "d"}, new String[]{"d", "t"})  = "dcte"

1

FYI करें

नई भाषा कोटलिन में, आप सीधे अपने सोर्स कोड में "स्ट्रिंग टेम्प्लेट" का उपयोग कर सकते हैं, किसी भी 3 पार्टी लाइब्रेरी या टेम्पलेट इंजन को वैरिएबल रिप्लेसमेंट करने की आवश्यकता नहीं है।

यह भाषा की ही एक विशेषता है।

देखें: https://kotlinlang.org/docs/reference/basic-types.html#string-templates


0

अतीत में, मैंने StringTemplate और Groovy Templates के साथ इस तरह की समस्या को हल किया है ।

अंत में, एक अस्थायी इंजन का उपयोग करने या न करने का निर्णय निम्नलिखित कारकों पर आधारित होना चाहिए:

  • क्या आपके पास आवेदन में इनमें से कई टेम्पलेट होंगे?
  • क्या आपको एप्लिकेशन को पुनरारंभ किए बिना टेम्प्लेट को संशोधित करने की क्षमता की आवश्यकता है?
  • कौन इन टेम्पलेट्स को बनाए रखेगा? एक जावा प्रोग्रामर या प्रोजेक्ट पर शामिल एक व्यापार विश्लेषक?
  • क्या आपको अपने टेम्प्लेट में तर्क देने की क्षमता की आवश्यकता होगी, जैसे कि चर में मूल्यों के आधार पर सशर्त पाठ?
  • क्या आपको किसी टेम्पलेट में अन्य टेम्प्लेट शामिल करने की क्षमता की आवश्यकता होगी?

यदि उपरोक्त में से कोई भी आपकी परियोजना पर लागू होता है, तो मैं एक अस्थायी इंजन का उपयोग करने पर विचार करूंगा, जिनमें से अधिकांश इस कार्यक्षमता को प्रदान करते हैं, और बहुत कुछ।


0

मैंनें इस्तेमाल किया

String template = "Hello %s Please find attached %s which is due on %s";

String message = String.format(template, name, invoiceNumber, dueDate);

2
यह काम करेगा, लेकिन मेरे मामले में टेम्पलेट स्ट्रिंग उपयोगकर्ता द्वारा अनुकूलन योग्य है, इसलिए मुझे नहीं पता कि टोकन किस क्रम में दिखाई देंगे।
मार्क

0

निम्न प्रपत्र के चर को बदलता है <<VAR>>, जिसमें मान एक मानचित्र से देखे जाते हैं। आप इसे यहाँ ऑनलाइन टेस्ट कर सकते हैं

उदाहरण के लिए, निम्नलिखित इनपुट स्ट्रिंग के साथ

BMI=(<<Weight>>/(<<Height>>*<<Height>>)) * 70
Hi there <<Weight>> was here

और निम्न चर मान

Weight, 42
Height, HEIGHT 51

निम्नलिखित आउटपुट

BMI=(42/(HEIGHT 51*HEIGHT 51)) * 70

Hi there 42 was here

यहाँ कोड है

  static Pattern pattern = Pattern.compile("<<([a-z][a-z0-9]*)>>", Pattern.CASE_INSENSITIVE);

  public static String replaceVarsWithValues(String message, Map<String,String> varValues) {
    try {
      StringBuffer newStr = new StringBuffer(message);
      int lenDiff = 0;
      Matcher m = pattern.matcher(message);
      while (m.find()) {
        String fullText = m.group(0);
        String keyName = m.group(1);
        String newValue = varValues.get(keyName)+"";
        String replacementText = newValue;
        newStr = newStr.replace(m.start() - lenDiff, m.end() - lenDiff, replacementText);
        lenDiff += fullText.length() - replacementText.length();
      }
      return newStr.toString();
    } catch (Exception e) {
      return message;
    }
  }


  public static void main(String args[]) throws Exception {
      String testString = "BMI=(<<Weight>>/(<<Height>>*<<Height>>)) * 70\n\nHi there <<Weight>> was here";
      HashMap<String,String> values = new HashMap<>();
      values.put("Weight", "42");
      values.put("Height", "HEIGHT 51");
      System.out.println(replaceVarsWithValues(testString, values));
  }

और यद्यपि अनुरोध नहीं किया गया है, आप अपनी एप्लिकेशन से संपत्तियों के साथ स्ट्रिंग में चर को बदलने के लिए एक समान दृष्टिकोण का उपयोग कर सकते हैं। हालांकि, यह पहले से ही किया जा रहा है:

private static Pattern patternMatchForProperties =
      Pattern.compile("[$][{]([.a-z0-9_]*)[}]", Pattern.CASE_INSENSITIVE);

protected String replaceVarsWithProperties(String message) {
    try {
      StringBuffer newStr = new StringBuffer(message);
      int lenDiff = 0;
      Matcher m = patternMatchForProperties.matcher(message);
      while (m.find()) {
        String fullText = m.group(0);
        String keyName = m.group(1);
        String newValue = System.getProperty(keyName);
        String replacementText = newValue;
        newStr = newStr.replace(m.start() - lenDiff, m.end() - lenDiff, replacementText);
        lenDiff += fullText.length() - replacementText.length();
      }
      return newStr.toString();
    } catch (Exception e) {
      return message;
    }
  }
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.