Java эквивалентно PHP preg_replace_callback - PullRequest
33 голосов
/ 17 декабря 2008

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

Ответы [ 6 ]

55 голосов
/ 18 декабря 2008

Попытка эмулировать функцию обратного вызова 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);
22 голосов
/ 17 декабря 2008

ВАЖНО : Как указано 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());
        }
    }
}

Для этого конкретного случая использования может быть лучше просто поставить в очередь каждое совпадение в обратном вызове, а затем выполнить их в обратном порядке. Это предотвратит необходимость переназначения индексов при изменении строки.

3 голосов
/ 02 апреля 2010

Я не был полностью удовлетворен ни одним из решений здесь. Я хотел решение без гражданства. И я не хотел оказаться в бесконечном цикле, если моя замещающая строка соответствовала шаблону. Пока я занимался этим, я добавил поддержку параметра 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();
  }
}
0 голосов
/ 10 января 2018
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")

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

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

0 голосов
/ 05 августа 2009

Я обнаружил, что ответ 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;
}
0 голосов
/ 17 декабря 2008

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

content = ReplaceCallback.find(content, regex, new ReplaceCallback.Callback() {
    public String matches(MatchResult match) {
        // Do something special not normally allowed in regex's...
        return "newstring"
    }
});

Весь список классов следующий:

<code>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");
 *      }
 * });
 * 
*

* Это, по сути, допускает второй уровень обработки регулярных выражений строк. *

* * / открытый класс ReplaceCallback { общедоступный статический интерфейс Callback { публичные совпадения строк (MatchResult match); } приватный финальный паттерн-паттерн; частный обратный вызов; Частный класс Результат { int start; int end; Замена строки; } / ** * Возможно, вам это не нужно. {@see find (String, String, Callback)} * @param regex Строка regex для использования * @param callback Экземпляр Callback для выполнения в матчах * / public ReplaceCallback (регулярное выражение строки, окончательный обратный вызов) { this.pattern = Pattern.compile (regex); this.callback = обратный вызов; } public String execute (Строковая строка) { final Matcher matcher = this.pattern.matcher (string); Стек результаты = новый стек (); while (matcher.find ()) { final MatchResult matchResult = matcher.toMatchResult (); Результат г = новый результат (); r.replace = callback.matches (matchResult); if (r.replace == null) Продолжить; r.start = matchResult.start (); r.end = matchResult.end (); results.push (г); } // Улучшаем это с помощью строителя строк ... while (! results.empty ()) { Результат r = results.pop (); string = string.substring (0, r.start) + r.replace + string.substring (r.end); } возвращаемая строка; } / ** * Если вы хотите повторно использовать регулярное выражение несколько раз с различными обратными вызовами или строками поиска, вы можете создать * ReplaceCallback напрямую и использовать этот метод для поиска и замены. * * @param string Строка, которую мы ищем * @param callback Экземпляр обратного вызова, который будет применен к результатам сопоставления с регулярным выражением. * @return Модифицированная строка поиска. * / public String execute (String string, final Callback callback) { this.callback = обратный вызов; возврат выполнить (строка); } / ** * Используйте этот статический метод для выполнения поиска регулярных выражений. * @param search Строка, которую мы ищем * @param regex Регулярное выражение для применения к строке * @param callback Экземпляр обратного вызова, который будет применен к результатам сопоставления с регулярным выражением. * @return Модифицированная строка поиска. * / public static String find (поиск строки, регулярное выражение строки, обратный вызов обратного вызова) { ReplaceCallback rc = new ReplaceCallback (регулярное выражение, обратный вызов); возврат rc.execute (поиск); } }
...