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