我有一个实用程序类来解析具有某些模式的字符串输入,如下例所示。所有变量都被{
和}
包围。如果我的字符串类似于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();
}
}
答案 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问题。