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

在java中递归替换字符串模式,java,Java,我有一个实用程序类来解析具有特定模式的字符串输入,如下面的示例所示。所有变量都被{和}包围。如果我的字符串类似于语言是{lang},版本2是{version}。主页位于{java.Home}输出为语言为java,版本2为1.8。主页位于C:/java并且如果我的字符串类似于语言是{lang},版本2是{version}。Home位于{{lang}.Home}输出为语言为java,版本2为1.8。主页位于{java.Home}。我只是想找到一种递归解析嵌套属性的方法,但遇到了几个问题。是否可以在代

我有一个实用程序类来解析具有特定模式的字符串输入,如下面的示例所示。所有变量都被
{
}
包围。如果我的字符串类似于
语言是{lang},版本2是{version}。主页位于{java.Home}
输出为
语言为java,版本2为1.8。主页位于C:/java
并且如果我的字符串类似于
语言是{lang},版本2是{version}。Home位于{{lang}.Home}
输出为
语言为java,版本2为1.8。主页位于{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();
}
}
import java.util.*;
导入java.util.regex.*;
公共类MyClass{
公共静态void main(字符串参数[]){
setProperty(“lang”、“java”);
System.setProperty(“版本”、“1.8”);
System.setProperty(“java.home”、“C:/java”);
println(解析(“语言为{lang},版本2为{version}.Home,位于{java.Home}”);
println(解析(“语言为{lang},版本2为{version}.Home,位于{{lang}.Home}”);
}
公共静态字符串解析(字符串输入){
列表标记=匹配器(“[{]\\S+[}]”,输入);
字符串值;
for(字符串标记:标记){
value=getProperty(令牌);
if(null!=值){
输入=输入。替换(令牌、值);
}
value=“”;
}
返回输入;
} 
私有静态字符串getProperty(字符串键){
key=key.substring(1,key.length()-1);
返回系统.getProperty(键);
}
公共静态列表匹配器(字符串正则表达式、字符串文本){
列表匹配项=新的ArrayList();
Pattern=Pattern.compile(regex);
Matcher Matcher=pattern.Matcher(文本);
while(matcher.find()){
matches.add(matcher.group());
}
返回比赛;
}
公共静态布尔包含(字符串正则表达式、字符串文本){
Pattern=Pattern.compile(regex);
Matcher Matcher=pattern.Matcher(文本);
返回matcher.find();
}
}

您只需请求模式仅获取值,而不包含内部
{
}
[^{}]
。没有“花括号”表示没有内部值。这样您就可以安全地进行更换

首先,我们创建一个
模式
,我们需要转义那些
{}
。。。我们为以后添加了一个捕获组

Pattern p = Pattern.compile("\\{([^{}]+)\\}");
然后我们用当前值进行检查:

Matcher m = p.matcher(s);
现在,我们只需要检查它是否有匹配和循环

while( m.find() ){
    ...
}
在这里,我们需要捕获值,因此我们得到第一个组并得到它的值(假设它始终存在):

使用
Matcher.replaceFirst
,我们将安全地只替换当前匹配项(从中获取值的匹配项)。如果使用
replaceAll
,它将用相同的值替换每个图案

s = m.replaceFirst(properties.get(key));
现在,由于我们已经更新了
字符串
,我们需要再次调用check这个正则表达式:

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
}

我们已经有了一个
匹配器
,所以请使用它。

有很多方法可以实现这一点

您需要某种方式与调用者沟通是否需要更换,或者是否进行了更换

一个简单的选择:

public boolean hasPlaceholder(String s) {
    // return true if s contains a {} placeholder, else false
}
使用此选项,您可以重复更换,直到完成:

while(hasPlaceholder(s)) {
    s = replacePlaceholders(s);
}
这确实比严格需要扫描字符串的次数要多,但您不应该过早地进行优化


一个更复杂的选项是使用
replacep占位符()
方法报告是否成功。为此,您需要一个封装结果字符串和
wasrecated()
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());
}
因此,每次调用
replacepholders()
时,它将至少进行一次替换,或者在验证没有其他替换时报告false


你在问题中提到了递归。这当然可以做到,这意味着避免每次扫描整个字符串——因为您可以只查看替换片段。这是未经测试的类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}{foo}”
!?)该怎么办

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

ReplacementResult result = replacePlaceholders(s);
while(result.wasReplaced()) {
   result = replacePlaceholders(result.string());
}
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();
}