在java中递归替换字符串模式

时间:2018-02-19 10:44:31

标签: java

我有一个实用程序类来解析具有某些模式的字符串输入,如下例所示。所有变量都被{}包围。如果我的字符串类似于Language is {lang} and version 2 is {version}. Home located at {java.home},则输出为Language is java and version 2 is 1.8. Home located at C:/java,如果我的字符串类似于Language is {lang} and version 2 is {version}. Home located at {{lang}.home},则输出为Language is java and version 2 is 1.8. Home located at {java.home}。我试图找到的是一种递归解析嵌套属性的方法,但遇到了几个问题。可以将任何逻辑插入到代码中,以便动态地解析内部属性吗?

import java.util.*;
import java.util.regex.*;
public class MyClass {
public static void main(String args[]) {
   System.setProperty("lang" , "java");
   System.setProperty("version" , "1.8");
   System.setProperty("java.home" , "C:/java");
   System.out.println(resolve("Language is {lang} and version 2 is {version}. Home located at {java.home}"));
   System.out.println(resolve("Language is {lang} and version 2 is {version}. Home located at {{lang}.home}"));
}
public static String resolve(String input) {
    List<String> tokens = matchers("[{]\\S+[}]", input);
    String value;
    for(String token : tokens) {
        value = getProperty(token);
        if (null != value) {
            input = input.replace(token, value);
        }
        value = "";
    }
    return input;
} 

private static String getProperty(String key) {
   key = key.substring(1, key.length()-1);
   return System.getProperty(key);
}

public static List<String> matchers(String regex, String text) {
    List<String> matches = new ArrayList<String>();
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(text);
    while (matcher.find()) {
        matches.add(matcher.group());
    }
    return matches;
}

public static boolean contains(String regex, String text) {
    Pattern pattern = Pattern.compile(regex);
    Matcher matcher = pattern.matcher(text);
    return matcher.find();
}
}

2 个答案:

答案 0 :(得分:1)

您只需要求模式仅获取没有内部{}[^{}]的值。没有&#34;花括号&#34;意味着没有内在价值。所以你可以安全地进行替换。

首先,我们创建一个Pattern,我们需要转义那些{} ...然后我们添加一个捕获组以供日后使用。

Pattern p = Pattern.compile("\\{([^{}]+)\\}");

然后我们检查当前值:

Matcher m = p.matcher(s);

现在,我们只需检查是否存在匹配和循环。

while( m.find() ){
    ...
}

在那里,我们需要捕获的值,所以我们得到第一个组并获得它的值(假设它总是存在):

String key = m.group(1);
String value = properties.get(key); //add some fail safe.

使用Matcher.replaceFirst,我们将安全地仅替换当前匹配(我们从中获取值)。如果您使用replaceAll,它将替换具有相同值的每个模式。

s = m.replaceFirst(properties.get(key));

现在,由于我们更新了String,我们需要再次调用regex:

m = p.matcher(s);

以下是一个完整的例子:

Map<String, String> properties = new HashMap<>();
properties.put("lang", "java");
properties.put("java.version", "1.8");

String s = "This is {{lang}.version}.";

Pattern p = Pattern.compile("\\{([^{}]+)\\}");
Matcher m = p.matcher(s);
while(m.find()){
    String key = m.group(1);
    s = m.replaceFirst(properties.get(key));
    System.out.println(s);
    m = p.matcher(s); //Reset the matcher
}
  

这是{java.version}。

     

这是1.8。

这有一个问题,它需要很多Matcher初始化,所以它可能不是最优的。当然,它很可能没有优化(这里不是重点)

仅供参考:使用Matcher.replaceFirst代替String.replaceFirst可以阻止新的Pattern编译。这是String.replaceFirst代码:

public String replaceFirst(String regex, String replacement) {
    return Pattern.compile(regex).matcher(this).replaceFirst(replacement);
}

我们已经有Matcher来做,所以请使用它。

答案 1 :(得分:0)

有很多方法可以达到这个目的。

您需要某种方式与来电者沟通是否需要更换,或者是否有人进行了更换。

一个简单的选择:

public boolean hasPlaceholder(String s) {
    // return true if s contains a {} placeholder, else false
}

使用此功能可以反复替换,直到完成:

while(hasPlaceholder(s)) {
    s = replacePlaceholders(s);
}

这会扫描字符串的次数超过严格要求,但不应过早优化。

更复杂的选项是replacePlaceholders()方法报告它是否成功。为此,您需要一个包装结果String和wasReplaced() boolean:

的响应类
ReplacementResult replacePlaceholders(String s) {
     // process string into newString, counting placeholders replaced
     return new ReplacementResult(count > 0, newString);
}

ReplacementResult作为练习的实施)

使用此功能可以:

ReplacementResult result = replacePlaceholders(s);
while(result.wasReplaced()) {
   result = replacePlaceholders(result.string());
}

因此,每次拨打replacePlaceholders()时,要么至少进行一次替换,否则会在确认无法进行替换后报告错误。

你在问题​​中提到了递归。这当然可以完成,这意味着每次都要避免扫描整个字符串 - 因为你可以只查看替换片段。这是未经测试的类似Java的伪代码:

String replaceRecursively(String s) {
    StringBuilder result = new StringBuilder();
    while(Token token = takeTokenFrom(s)) {
        if(token.isPlaceholder()) {
            String rawReplacement = lookupReplacement(token);
            String processedReplacement = replaceRecursively(rawReplacement);
            result.append(processedReplacement);
        } else {
            result.append(token.text());
        }
    }
    return result.toString();
}

对于所有这些解决方案,您应该注意无限循环或堆栈式递归。如果您将"{foo}"替换为"{foo}"怎么办? (或者更糟糕的是,如果用"{foo}"替换"{foo}{foo}",该怎么办?!)。

当然,最简单的方法是控制配置,而不是触发该问题。以编程方式检测问题是完全可能的,但是足够复杂,如果你需要它,它将保证另一个SO问题。