문제

저는 애플리케이션을 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에서 이를 수행하는 이상적인 방법은 무엇입니까?

도움이 되었습니까?

해결책

중요한: 지적한대로 자다 주석에서,이 클래스에는 일치하는 Regex가 교체 문자열에서 일치하는 경우 무한 루프 버그가 있습니다. 필요한 경우 독자들에게 연습으로 남겨 둘 것입니다.


나는 Java에 내장 된 비슷한 것을 모른다. 경기자 클래스를 사용하여 너무 많은 어려움없이 직접 굴릴 수 있습니다.

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 함수의 정확한 기능처럼 보이게 만들었습니다.

Asker가 좋아했기 때문에 원본은 다음과 같습니다.

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의 콜백 기능을 모방하려고 시도하면 Loop에서 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);

그리고 내 버전의 RecreCallback 클래스는 다음과 같습니다.

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")

반환 값을 생성합니다.

마당에 개 두 마리의 개

다음은 내가 당신의 제안으로 한 일의 최종 결과입니다. 누군가 같은 문제가있는 경우 여기에 나가는 것이 좋을 것이라고 생각했습니다. 결과 호출 코드는 다음과 같습니다.

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);
    }
}
라이센스 : CC-BY-SA ~와 함께 속성
제휴하지 않습니다 StackOverflow
scroll top