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 limit
y un parámetro count
devuelto.(Utilicé un AtomicInteger
para simular el paso de un número entero por referencia.) Moví el parámetro callback
al final de la lista de parámetros, para que sea más fácil definir una clase anónima.
Aquí es 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í es 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();
}
}
De hecho, me gusta su respuesta original mejor con la cola de la cadena y los índices devuelto. Luego aplicándolos en reversa. De esta manera es más simple, pero parece hacer más trabajo, tener que volver a examinar toda la cadena para cada coincidencia. ¡Gracias por la sugerencia! – Mike
He añadido la sugerencia original nuevamente. El tamaño de entrada esperado marcaría la diferencia en cuanto a si volver a escanear o poner en cola y luego reemplazar sería más efectivo. Supongo que uno también podría hacer que el método de reemplazo los ponga en cola, junto con la cadena de reemplazo ... – jdmichal
Errr ... Misspoke. Obviamente, las colas siempre son más efectivas en lo que respecta al tiempo de CPU. La diferencia sería si es un problema lo suficientemente grande como para preocuparse. – jdmichal