Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java equivalent to PHP's preg_replace_callback

I'm in the process of moving an application from PHP to Java and there is heavy use of regular expressions in the code. I've run across something in PHP that doesn't seem to have a java equivalent:

preg_replace_callback() 

For every match in the regex, it calls a function that is passed the match text as a parameter. As an example usage:

$articleText = preg_replace_callback("/\[thumb(\d+)\]/",'thumbReplace', $articleText); # ... function thumbReplace($matches) {    global $photos;    return "<img src=\"thumbs/" . $photos[$matches[1]] . "\">"; } 

What would be the ideal way to do this in Java?

like image 961
Mike Avatar asked Dec 17 '08 17:12

Mike


2 Answers

Trying to emulate PHP's callback feature seems an awful lot of work when you could just use appendReplacement() and appendTail() in a loop:

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); 
like image 127
Jan Goyvaerts Avatar answered Oct 06 '22 07:10

Jan Goyvaerts


IMPORTANT: As pointed out by Kip in the comments, this class has an infinite loop bug if the matching regex matches on the replacement string. I'll leave it as an exercise to readers to fix it, if necessary.


I don't know of anything similar that's built into Java. You could roll your own without too much difficulty, using the Matcher class:

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);         }     } } 

Then call:

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

Note that you can get the entire matched string by calling matchResults.group() or matchResults.group(0), so it's not necessary to pass the callback the current string state.

EDIT: Made it look more like the exact functionality of the PHP function.

Here's the original, since the asker liked it:

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());         }     } } 

For this particular use case, it might be best to simply queue each match in the callback, then afterwards run through them backwards. This will prevent having to remap indexes as the string is modified.

like image 20
jdmichal Avatar answered Oct 06 '22 07:10

jdmichal