38 votes

Java équivalent à PHP de la fonction preg_replace_callback

Je suis en train de passer d'une application à partir de PHP Java et il y a une forte utilisation des expressions régulières dans le code. J'ai couru à travers quelque chose en PHP qui ne semble pas avoir un java équivalent:

preg_replace_callback()

Pour chaque match de la regex, il appelle une fonction qui est passé le match de texte en tant que paramètre. Comme exemple d'utilisation:

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

Quel serait le moyen idéal de le faire en Java?

59voto

Jan Goyvaerts Points 10402

En essayant d'imiter PHP de la fonction de rappel semble beaucoup de travail quand vous pouvez simplement utiliser appendReplacement() et appendTail() dans une boucle:

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

24voto

jdmichal Points 6283

IMPORTANT: Comme l'a souligné par Kip dans les commentaires, cette classe a une boucle infinie bug si la mise en correspondance des matchs sur la chaîne de remplacement. Je vais le laisser comme un exercice pour les lecteurs de le corriger, si nécessaire.


Je ne sais pas de quoi que ce soit de semblable qui est intégré dans Java. Vous pourriez rouler sans trop de difficulté, en utilisant le Comparateur de classe:

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

Ensuite, composez le:

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

Notez que vous pouvez obtenir l'ensemble de la chaîne trouvée en appelant matchResults.group() ou matchResults.group(0), de sorte qu'il n'est pas nécessaire de passer le rappel de l'actuelle chaîne de l'état.

EDIT:en Fait, il ressemble plus à exactement la fonctionnalité de la fonction PHP.

Voici l'original, car le demandeur a aimé:

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

Pour ce cas d'utilisation particulier, il peut être préférable de simplement la file d'attente de chaque match dans le rappel, puis par la suite exécuter par eux en arrière. Cela permettra d'éviter d'avoir à reconfigurer les indices que la chaîne est modifiée.

3voto

Kip Points 37013

Je n'étais pas tout à fait satisfait avec l'une des solutions ici. Je voulais un apatride solution. Et je n'ai pas envie de finir dans une boucle infinie si ma chaîne de remplacement qui s'est passé pour correspondre à la forme. Alors que j'y étais j'ai ajouté le support pour un limit paramètre et un retourné count paramètre. (J'ai utilisé un AtomicInteger pour simuler le passage d'un entier par une référence.) J'ai déménagé l' callback paramètre à la fin de la liste des paramètres, pour le rendre plus facile de définir une classe anonyme.

Voici un exemple d'utilisation:

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

Et voici ma version de ReplaceCallback classe:

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

0voto

jevon Points 1162

J'ai trouvé que jdmichal la réponse de boucle infinie si votre chaîne retournée pourrait être de nouveau mis en correspondance; au-dessous de est une modification qui empêche les boucles infinies de cette correspondance.

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

-1voto

Mike Points 4226

Voici le résultat final de ce que j'ai fait avec votre suggestion. J'ai pensé qu'il serait agréable d'avoir ici au cas où quelqu'un a le même problème. Le résultant d'appeler le code ressemble à ceci:

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

L'ensemble de la classe liste suit:

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

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X