我正在将应用程序从PHP迁移到Java,并且在代码中大量使用正则表达式。我在PHP中遇到过似乎没有java等价物的东西:
preg_replace_callback()
对于正则表达式中的每个匹配,它调用一个函数,该函数将匹配文本作为参数传递。作为示例用法:
$articleText = preg_replace_callback("/\[thumb(\d+)\]/",'thumbReplace', $articleText);
# ...
function thumbReplace($matches) {
global $photos;
return "<img src=\"thumbs/" . $photos[$matches[1]] . "\">";
}
在Java中执行此操作的理想方法是什么?
答案 0 :(得分:55)
当你可以在循环中使用appendReplacement()和appendTail()时,尝试模拟PHP的回调功能似乎是一项非常多的工作:
StringBuffer resultString = new StringBuffer();
Pattern regex = Pattern.compile("regex");
Matcher regexMatcher = regex.matcher(subjectString);
while (regexMatcher.find()) {
// You can vary the replacement text for each match on-the-fly
regexMatcher.appendReplacement(resultString, "replacement");
}
regexMatcher.appendTail(resultString);
答案 1 :(得分:22)
重要:正如评论中Kip所指出的,如果匹配的正则表达式与替换字符串匹配,则此类会出现无限循环错误。如果有必要,我会把它留给读者来修理它。
我不知道Java中内置的任何类似内容。你可以使用Matcher类轻松地自己滚动:
import java.util.regex.*;
public class CallbackMatcher
{
public static interface Callback
{
public String foundMatch(MatchResult matchResult);
}
private final Pattern pattern;
public CallbackMatcher(String regex)
{
this.pattern = Pattern.compile(regex);
}
public String replaceMatches(String string, Callback callback)
{
final Matcher matcher = this.pattern.matcher(string);
while(matcher.find())
{
final MatchResult matchResult = matcher.toMatchResult();
final String replacement = callback.foundMatch(matchResult);
string = string.substring(0, matchResult.start()) +
replacement + string.substring(matchResult.end());
matcher.reset(string);
}
}
}
然后致电:
final CallbackMatcher.Callback callback = new CallbackMatcher.Callback() {
public String foundMatch(MatchResult matchResult)
{
return "<img src=\"thumbs/" + matchResults.group(1) + "\"/>";
}
};
final CallbackMatcher callbackMatcher = new CallbackMatcher("/\[thumb(\d+)\]/");
callbackMatcher.replaceMatches(articleText, callback);
请注意,您可以通过调用matchResults.group()
或matchResults.group(0)
来获取整个匹配的字符串,因此没有必要将回调作为当前字符串状态传递。
编辑:让它看起来更像是PHP函数的确切功能。
这是原作,因为提问者喜欢它:
public class CallbackMatcher
{
public static interface Callback
{
public void foundMatch(MatchResult matchResult);
}
private final Pattern pattern;
public CallbackMatcher(String regex)
{
this.pattern = Pattern.compile(regex);
}
public String findMatches(String string, Callback callback)
{
final Matcher matcher = this.pattern.matcher(string);
while(matcher.find())
{
callback.foundMatch(matcher.toMatchResult());
}
}
}
对于这个特定的用例,最好简单地对回调中的每个匹配进行排队,然后再向后运行它们。这将防止在修改字符串时重新映射索引。
答案 2 :(得分:3)
我对这里的任何解决方案都不太满意。我想要一个无国籍的解决方案。如果我的替换字符串碰巧匹配模式,我不想最终陷入无限循环。当我参与其中时,我添加了对limit
参数和返回的count
参数的支持。 (我使用AtomicInteger
来模拟通过引用传递整数。)我将callback
参数移动到参数列表的末尾,以便更容易定义匿名类。
以下是一个使用示例:
final Map<String,String> props = new HashMap<String,String>();
props.put("MY_NAME", "Kip");
props.put("DEPT", "R&D");
props.put("BOSS", "Dave");
String subjectString = "Hi my name is ${MY_NAME} and I work in ${DEPT} for ${BOSS}";
String sRegex = "\\$\\{([A-Za-z0-9_]+)\\}";
String replacement = ReplaceCallback.replace(sRegex, subjectString, new ReplaceCallback.Callback() {
public String matchFound(MatchResult match) {
String group1 = match.group(1);
if(group1 != null && props.containsKey(group1))
return props.get(group1);
return match.group();
}
});
System.out.println("replacement: " + replacement);
这是我的ReplaceCallback类的版本:
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.*;
public class ReplaceCallback
{
public static interface Callback {
/**
* This function is called when a match is made. The string which was matched
* can be obtained via match.group(), and the individual groupings via
* match.group(n).
*/
public String matchFound(MatchResult match);
}
/**
* Replaces with callback, with no limit to the number of replacements.
* Probably what you want most of the time.
*/
public static String replace(String pattern, String subject, Callback callback)
{
return replace(pattern, subject, -1, null, callback);
}
public static String replace(String pattern, String subject, int limit, Callback callback)
{
return replace(pattern, subject, limit, null, callback);
}
/**
* @param regex The regular expression pattern to search on.
* @param subject The string to be replaced.
* @param limit The maximum number of replacements to make. A negative value
* indicates replace all.
* @param count If this is not null, it will be set to the number of
* replacements made.
* @param callback Callback function
*/
public static String replace(String regex, String subject, int limit,
AtomicInteger count, Callback callback)
{
StringBuffer sb = new StringBuffer();
Matcher matcher = Pattern.compile(regex).matcher(subject);
int i;
for(i = 0; (limit < 0 || i < limit) && matcher.find(); i++)
{
String replacement = callback.matchFound(matcher.toMatchResult());
replacement = Matcher.quoteReplacement(replacement); //probably what you want...
matcher.appendReplacement(sb, replacement);
}
matcher.appendTail(sb);
if(count != null)
count.set(i);
return sb.toString();
}
}
答案 3 :(得分:0)
我发现如果返回的字符串可以再次匹配,jdmichal的答案将无限循环;下面是一个修改,它可以阻止这种匹配的无限循环。
public String replaceMatches(String string, Callback callback) {
String result = "";
final Matcher matcher = this.pattern.matcher(string);
int lastMatch = 0;
while(matcher.find())
{
final MatchResult matchResult = matcher.toMatchResult();
final String replacement = callback.foundMatch(matchResult);
result += string.substring(lastMatch, matchResult.start()) +
replacement;
lastMatch = matchResult.end();
}
if (lastMatch < string.length())
result += string.substring(lastMatch);
return result;
}
答案 4 :(得分:0)
public static String replace(Pattern pattern, Function<MatchResult, String> callback, CharSequence subject) {
Matcher m = pattern.matcher(subject);
StringBuffer sb = new StringBuffer();
while (m.find()) {
m.appendReplacement(sb, callback.apply(m.toMatchResult()));
}
m.appendTail(sb);
return sb.toString();
}
用法示例:
replace(Pattern.compile("cat"), mr -> "dog", "one cat two cats in the yard")
将产生返回值:
一只狗两只狗在院子里
答案 5 :(得分:0)
Matcher#replaceAll
是您要寻找的。 p>
Pattern.compile("random number")
.matcher("this is a random number")
.replaceAll(r -> "" + ThreadLocalRandom.current().nextInt())
输出:
this is a -107541873
答案 6 :(得分:0)
Java 9 引入了 Matcher#replaceAll
方法,该方法接受 ????????
????????
????????
????????
????????
????????
????????
????????
????????
????????
????????
????????
????????
以返回给定特定匹配的替换,这非常优雅。
Function<MatchResult,String>
答案 7 :(得分:-1)
以下是我对您的建议所做的最终结果。我觉得如果有人遇到同样的问题,请到这里会很高兴。生成的调用代码如下所示:
content = ReplaceCallback.find(content, regex, new ReplaceCallback.Callback() {
public String matches(MatchResult match) {
// Do something special not normally allowed in regex's...
return "newstring"
}
});
整个班级列表如下:
import java.util.regex.MatchResult;
import java.util.regex.Pattern;
import java.util.regex.Matcher;
import java.util.Stack;
/**
* <p>
* Class that provides a method for doing regular expression string replacement by passing the matched string to
* a function that operates on the string. The result of the operation is then used to replace the original match.
* </p>
* <p>Example:</p>
* <pre>
* ReplaceCallback.find("string to search on", "/regular(expression/", new ReplaceCallback.Callback() {
* public String matches(MatchResult match) {
* // query db or whatever...
* return match.group().replaceAll("2nd level replacement", "blah blah");
* }
* });
* </pre>
* <p>
* This, in effect, allows for a second level of string regex processing.
* </p>
*
*/
public class ReplaceCallback {
public static interface Callback {
public String matches(MatchResult match);
}
private final Pattern pattern;
private Callback callback;
private class Result {
int start;
int end;
String replace;
}
/**
* You probably don't need this. {@see find(String, String, Callback)}
* @param regex The string regex to use
* @param callback An instance of Callback to execute on matches
*/
public ReplaceCallback(String regex, final Callback callback) {
this.pattern = Pattern.compile(regex);
this.callback = callback;
}
public String execute(String string) {
final Matcher matcher = this.pattern.matcher(string);
Stack<Result> results = new Stack<Result>();
while(matcher.find()) {
final MatchResult matchResult = matcher.toMatchResult();
Result r = new Result();
r.replace = callback.matches(matchResult);
if(r.replace == null)
continue;
r.start = matchResult.start();
r.end = matchResult.end();
results.push(r);
}
// Improve this with a stringbuilder...
while(!results.empty()) {
Result r = results.pop();
string = string.substring(0, r.start) + r.replace + string.substring(r.end);
}
return string;
}
/**
* If you wish to reuse the regex multiple times with different callbacks or search strings, you can create a
* ReplaceCallback directly and use this method to perform the search and replace.
*
* @param string The string we are searching through
* @param callback A callback instance that will be applied to the regex match results.
* @return The modified search string.
*/
public String execute(String string, final Callback callback) {
this.callback = callback;
return execute(string);
}
/**
* Use this static method to perform your regex search.
* @param search The string we are searching through
* @param regex The regex to apply to the string
* @param callback A callback instance that will be applied to the regex match results.
* @return The modified search string.
*/
public static String find(String search, String regex, Callback callback) {
ReplaceCallback rc = new ReplaceCallback(regex, callback);
return rc.execute(search);
}
}