Reemplazo de cadenas en java, similar a una plantilla de velocidad

96

¿Existe algún Stringmecanismo de reemplazo en Java, donde pueda pasar objetos con un texto, y reemplaza la cadena a medida que ocurre?
Por ejemplo, el texto es:

Hello ${user.name},
    Welcome to ${site.name}. 

Los objetos que tengo son "user"y "site". Quiero reemplazar las cadenas dadas en el interior ${}con sus valores equivalentes de los objetos. Esto es lo mismo que reemplazamos objetos en una plantilla de velocidad.

Joe
fuente
1
¿Reemplazar dónde? ¿Una clase? ¿Una JSP? La cadena tiene un método de formato si solo:String.format("Hello %s", username);
Droo
1
@Droo: En el ejemplo, la cadena es como Hello ${user.name}, no como Hello %so Hello {0}.
Adeel Ansari
2
Si necesita algo que se parezca a la velocidad y huela a velocidad, ¿tal vez sea velocidad? :)
serg
@Droo: No es una clase. Tengo el texto anterior en una variable "Cadena" y quiero reemplazar todas las ocurrencias de las cadenas dentro de $ {} con valores en los objetos correspondientes. por ejemplo, reemplace todos los $ {user.name} con la propiedad de nombre en el objeto "usuario".
Joe
@serg: Sí, es un código de velocidad. y quiero eliminar la velocidad de mi código.
Joe

Respuestas:

142

Utilizar StringSubstitutordesde Apache Commons Text.

https://commons.apache.org/proper/commons-text/

Lo hará por ti (y su código abierto ...)

 Map<String, String> valuesMap = new HashMap<String, String>();
 valuesMap.put("animal", "quick brown fox");
 valuesMap.put("target", "lazy dog");
 String templateString = "The ${animal} jumped over the ${target}.";
 StringSubstitutor sub = new StringSubstitutor(valuesMap);
 String resolvedString = sub.replace(templateString);
J H.
fuente
1
Debería ser Map<String, String> valuesMap = new HashMap<String, String>();.
andrewrjones
3
StrSubstitutorahora está en desuso en https://commons.apache.org/proper/commons-lang/ . Usuario https://commons.apache.org/proper/commons-text/ en su lugar
Lukuluba
7
StrSubstitutorobsoleto a partir de 1.3, utilice StringSubstitutoren su lugar. Esta clase se eliminará en 2.0. La dependencia de Gradle para la importación StringSubstitutoresorg.apache.commons:commons-text:1.4
Yurii Rabeshko
¿Permite la sustitución basada en condiciones?
Gaurav
130

Eche un vistazo a la java.text.MessageFormatclase, MessageFormat toma un conjunto de objetos, los formatea y luego inserta las cadenas formateadas en el patrón en los lugares apropiados.

Object[] params = new Object[]{"hello", "!"};
String msg = MessageFormat.format("{0} world {1}", params);
RealHowTo
fuente
10
¡Gracias! Sabía que Java debería tener una forma incorporada de hacer esto sin tener que usar un motor de plantillas para hacer algo tan simple.
Joseph Rajeev Motha
2
Parece que String.format puede hacer cualquier cosa que esto pueda hacer - stackoverflow.com/questions/2809633/…
Noumenon
6
+1. Tenga en cuenta que formattambién toma un Object...varargs para que pueda usar esta sintaxis más concisa donde sea preferibleformat("{0} world {1}", "Hello", "!");
davnicwil
Cabe señalar que MessageFormatsolo se puede usar de manera confiable para su homónimo, mostrar mensajes, no para la salida donde el formato técnico importa. Por ejemplo, los números se formatearán según la configuración regional, lo que los invalidará para usos técnicos.
Marnes
22

Mi forma preferida es String.format()porque es un delineador y no requiere bibliotecas de terceros:

String message = String.format("Hello! My name is %s, I'm %s.", name, age); 

Lo uso regularmente, por ejemplo, en mensajes de excepción como:

throw new Exception(String.format("Unable to login with email: %s", email));

Sugerencia: puede ingresar tantas variables como desee porque format()usa Varargs

artgrohe
fuente
Esto es menos útil cuando necesita repetir el mismo argumento más de una vez. Por ejemplo: String.format("Hello! My name is %s, I'm %s. Why is my name %s you ask? Well I'm only %s years old so I don't know", name, age, name, age);. Otras respuestas aquí requieren especificar cada argumento solo una vez.
asherbar
2
@asherbar puede usar especificadores de índice de argumentos en la cadena de formato, por ejemploString.format("Hello! My name is %1$s, I'm %2$s. Why is my name %1$s you ask? Well I'm only %2$s years old so I don't know", name, age)
jazzpi
@jazzpi Nunca supe eso. ¡Gracias!
asherbar
20

Reuní una pequeña implementación de prueba de esto. La idea básica es llamar formaty pasar la cadena de formato, un mapa de objetos y los nombres que tienen localmente.

El resultado de lo siguiente es:

Mi perro se llama fido y Jane Doe es su dueña.

public class StringFormatter {

    private static final String fieldStart = "\\$\\{";
    private static final String fieldEnd = "\\}";

    private static final String regex = fieldStart + "([^}]+)" + fieldEnd;
    private static final Pattern pattern = Pattern.compile(regex);

    public static String format(String format, Map<String, Object> objects) {
        Matcher m = pattern.matcher(format);
        String result = format;
        while (m.find()) {
            String[] found = m.group(1).split("\\.");
            Object o = objects.get(found[0]);
            Field f = o.getClass().getField(found[1]);
            String newVal = f.get(o).toString();
            result = result.replaceFirst(regex, newVal);
        }
        return result;
    }

    static class Dog {
        public String name;
        public String owner;
        public String gender;
    }

    public static void main(String[] args) {
        Dog d = new Dog();
        d.name = "fido";
        d.owner = "Jane Doe";
        d.gender = "him";
        Map<String, Object> map = new HashMap<String, Object>();
        map.put("d", d);
        System.out.println(
           StringFormatter.format(
                "My dog is named ${d.name}, and ${d.owner} owns ${d.gender}.", 
                map));
    }
}

Nota: Esto no se compila debido a excepciones no controladas. Pero hace que el código sea mucho más fácil de leer.

Además, no me gusta que tenga que construir el mapa usted mismo en el código, pero no sé cómo obtener los nombres de las variables locales mediante programación. La mejor manera de hacerlo es recordar poner el objeto en el mapa tan pronto como lo cree.

El siguiente ejemplo produce los resultados que desea de su ejemplo:

public static void main(String[] args) {
    Map<String, Object> map = new HashMap<String, Object>();
    Site site = new Site();
    map.put("site", site);
    site.name = "StackOverflow.com";
    User user = new User();
    map.put("user", user);
    user.name = "jjnguy";
    System.out.println(
         format("Hello ${user.name},\n\tWelcome to ${site.name}. ", map));
}

También debo mencionar que no tengo idea de qué es Velocity, así que espero que esta respuesta sea relevante.

jjnguy
fuente
Esto es lo que estaba buscando. Gracias por dar una implementación. Lo estaba intentando y obtenía resultados incorrectos. :RE. De todos modos resolvió mi problema.
Joe
2
@Joe, me alegro de haber podido ayudar. Fue una buena excusa para mí finalmente practicar la escritura de un código que usa la reflexión en Java.
jjnguy
6

Aquí hay un resumen de cómo podría hacer esto. Debería ser relativamente sencillo implementarlo como código real.

  1. Cree un mapa de todos los objetos a los que se hará referencia en la plantilla.
  2. Utilice una expresión regular para buscar referencias de variables en la plantilla y reemplácelas con sus valores (consulte el paso 3). La clase Matcher será útil para buscar y reemplazar.
  3. Divida el nombre de la variable en el punto. user.namese convertiría en usery name. Busque useren su mapa para obtener el objeto y use la reflexión para obtener el valor del nameobjeto. Suponiendo que sus objetos tengan captadores estándar, buscará un método getNamey lo invocará.
casablanca
fuente
Je, acabo de ver esta respuesta. Es idéntico al mío. Déjeme saber lo que piensa de mi implementación.
jjnguy
0

No hay nada fuera de la caja que sea comparable a la velocidad, ya que la velocidad se escribió para resolver exactamente ese problema. Lo más cercano que puede intentar es buscar en el formateador

http://cupi2.uniandes.edu.co/site/images/recursos/javadoc/j2se/1.5.0/docs/api/java/util/Formatter.html

Sin embargo, hasta donde yo sé, el formateador se creó para proporcionar opciones de formato similares a C en Java, por lo que es posible que no te rasque exactamente lo que te pica, pero puedes probar :).

Mike Milkin
fuente
0

Yo uso GroovyShell en Java para analizar la plantilla con Groovy GString:

Binding binding = new Binding();
GroovyShell gs = new GroovyShell(binding);
// this JSONObject can also be replaced by any Java Object
JSONObject obj = new JSONObject();
obj.put("key", "value");
binding.setProperty("obj", obj)
String str = "${obj.key}";
String exp = String.format("\"%s\".toString()", str);
String res = (String) gs.evaluate(exp);
// value
System.out.println(str);
Kan
fuente