Вопрос

Я нахожусь в процессе переноса приложения с 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")

выдаст возвращаемое значение:

одна собака, две собаки во дворе

Вот окончательный результат того, что я сделал с вашим предложением.Я подумал, что было бы неплохо иметь его здесь на случай, если у кого-то возникнет такая же проблема.Результирующий вызывающий код выглядит следующим образом:

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