java - regular - regex preg_match
Java equivalente a preg_replace_callback de PHP (6)
Aquí está el resultado final de lo que hice con su sugerencia. Pensé que sería bueno tener aquí en caso de que alguien tenga el mismo problema. El código de llamada resultante se ve así:
content = ReplaceCallback.find(content, regex, new ReplaceCallback.Callback() {
public String matches(MatchResult match) {
// Do something special not normally allowed in regex''s...
return "newstring"
}
});
La lista completa de clases sigue:
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);
}
}
Estoy en el proceso de mover una aplicación de PHP a Java y hay un uso intensivo de expresiones regulares en el código. Me encontré con algo en PHP que no parece tener un equivalente de Java:
preg_replace_callback()
Para cada coincidencia en la expresión regular, llama a una función que pasa el texto de coincidencia como parámetro. Como un ejemplo de uso:
$articleText = preg_replace_callback("//[thumb(/d+)/]/",''thumbReplace'', $articleText);
# ...
function thumbReplace($matches) {
global $photos;
return "<img src=/"thumbs/" . $photos[$matches[1]] . "/">";
}
¿Cuál sería la forma ideal de hacer esto en Java?
Encontré que la respuesta de jdmichal sería un bucle infinito si tu cadena devuelta pudiera coincidir nuevamente; a continuación hay una modificación que evita bucles infinitos de esta coincidencia.
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;
}
Intentar emular la función de devolución de llamada de PHP parece mucho trabajo cuando puedes usar appendReplacement () y appendTail () en un bucle:
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);
No estaba del todo satisfecho con ninguna de las soluciones aquí. Yo quería una solución sin estado. Y no quería terminar en un bucle infinito si mi cadena de reemplazo coincidía con el patrón. Mientras estaba en eso, agregué soporte para un parámetro de limit
y un parámetro de count
devuelto. (Utilicé un AtomicInteger
para simular el paso de un entero por referencia.) AtomicInteger
el parámetro de callback
al final de la lista de parámetros, para que sea más fácil definir una clase anónima.
Aquí hay un ejemplo de uso:
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);
Y aquí está mi versión de la clase 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();
}
}
IMPORTANTE : Como señaló Kip en los comentarios, esta clase tiene un error de bucle infinito si la expresión regular coincidente coincide con la cadena de reemplazo. Lo dejaré como un ejercicio para que los lectores lo arreglen, si es necesario.
No sé nada similar que esté integrado en Java. Podrías tirar el tuyo propio sin demasiada dificultad, usando la clase 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);
}
}
}
Luego llame:
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);
Tenga en cuenta que puede obtener toda la cadena coincidente llamando a matchResults.group()
o matchResults.group(0)
, por lo que no es necesario pasar la devolución de llamada al estado actual de la cadena.
EDITAR: lo hizo parecerse más a la funcionalidad exacta de la función de PHP.
Aquí está el original, ya que al autor le gustó:
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());
}
}
}
Para este caso de uso particular, lo mejor sería simplemente poner en cola cada coincidencia en la devolución de llamada, y luego ejecutarlas hacia atrás. Esto evitará tener que volver a correlacionar índices a medida que se modifique la cadena.
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();
}
Ejemplo de uso:
replace(Pattern.compile("cat"), mr -> "dog", "one cat two cats in the yard")
producirá el valor de retorno:
un perro dos perros en el patio