PHP の preg_replace_callback に相当する Java
-
22-08-2019 - |
質問
アプリケーションを 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 でこれを行う理想的な方法は何でしょうか?
解決
重要:が指摘したように、 キップ コメントでは、一致する正規表現が置換文字列に一致する場合、このクラスには無限ループのバグがあります。必要に応じて読者が修正できるように、演習として残しておきます。
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());
}
}
}
この特定の使用例では、コールバック内の各一致を単純にキューに入れてから、後でそれらを逆方向に実行するのが最善の方法かもしれません。これにより、文字列が変更されたときにインデックスを再マップする必要がなくなります。
他のヒント
PHPのコールバック機能をエミュレートしようとすると、仕事の非常に多くを思わあなただけのループにappendReplacement()とappendTail()を使用することができるときます:
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);
私はここでのソリューションのいずれかに非常に満足していませんでした。私はステートレスなソリューションを求めていました。そして、私は私の置換文字列がパターンに一致するように起こった場合、無限ループで終わるしたくありませんでした。私はそれであったが、私は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();
}
}
私はあなたの返された文字列が再び一致することができれば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;
}
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")
戻り値を生成します
庭に1匹の犬2匹
ここで私はあなたの提案に何をしたかの最終的な結果です。私は出て、ここで誰かが同じ問題を抱えている場合には持っていいだろうと思いました。結果として呼び出すコードは次のようになります:
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);
}
}