2012-06-29 22 views
9

¿Existe una implementación estándar o al menos extendida de algo como String.format, pero con argumentos con nombre?Java string templatizer/formatter con argumentos con nombre

me gustaría dar formato a una cadena a plantillas de una manera como esa:

Map<String, Object> args = new HashMap<String, Object>(); 
args.put("PATH", "/usr/bin"); 
args.put("file", "foo"); 
String s = someHypotheticalMethod("#{PATH}/ls #{file}"); 
// "/usr/bin/ls foo" 

Técnicamente, es casi lo mismo que:

String[] args = new String[] { "/usr/bin", "foo" }; 
String s = String.format("%1$s/ls %2$s", args); 
// "/usr/bin/ls foo" 

pero con argumentos con nombre.

yo sepa:

pero todos ellos utilizan ordenado o al menos argumentos, que no se llame contados. Sé que es trivial implementar uno, pero ¿hay algún mecanismo que esté buscando en las bibliotecas estándar de Java o al menos en Apache Commons/Guava/algo similar, sin incluir motores de plantillas de alto perfil?

NOTA: No estoy realmente interesado en motores de plantilla en toda regla, con características como algo de lógica imperativa/funcionales, control de flujo, modificadores, sub-templates/inclusiones, iteradores, etc. En general, el siguiente método es una aplicación de 4 líneas de trabajo - que es todo lo que necesita:

public static String interpolate(String format, Map<String, ? extends Object> args) { 
    String out = format; 
    for (String arg : args.keySet()) { 
     out = Pattern.compile(Pattern.quote("#{" + arg + "}")). 
       matcher(out). 
       replaceAll(args.get(arg).toString()); 
    } 
    return out; 
} 
+0

¿Alguna razón por la que no solo está utilizando "#" + args.get ("RUTA") + "/ ls #" + args.get ("archivo")? – Charles

+0

Tengo un montón de archivos de plantilla, tengo un mapa de argumentos y necesito obtener cadenas completas de cada uno de estos archivos de plantilla. – GreyCat

Respuesta

8

También puede intentar org.apache.commons.lang3.text.StrSubstitutor si Java 7 no es una opción. Hace exactamente lo que quiere que haga. Si es ligero puede depender de si usa algo más de commons-lang también.

+0

Gracias! Parece que es exactamente lo que estaba buscando :) – GreyCat

1

StringTemplate puede ser tan ligero como un motor de interpolación es muy probable que conseguir, aunque no sé cómo esta se acumula en los recursos sabia contra cosas como FreeMarker, Mustache o Velocity.

Otra opción podría ser un motor EL como MVEL, que tiene un templating engine.

+1

Ejem ... "referencia de atributo", "referencia de plantilla (como #include o expansión de macro)", "incluye condicional de subetabla", "aplicación de plantilla a la lista de atributos" ... 221K de clases jar comprimidas, 124 clases dentro - Eso definitivamente no es realmente liviano. Sin embargo, verifico a los demás, ¡gracias! – GreyCat

+0

Sí, he confirmado que todos los FreeMarker, Moustache y Velocity son mucho más pesados ​​de lo que necesito. De todos modos, ¡gracias por tu sugerencia! – GreyCat

3

Recientemente descubrí JUEL que se ajusta muy bien a la descripción. Es el lenguaje de expresión sacado de JSP. Afirma ser muy rápido, también.

Estoy a punto de probarlo en uno de mis proyectos.

embargo, para un peso más ligero, que es una variante de la suya, prueba este (envuelto en una prueba de unidad):

public class TestInterpolation { 

    public static class NamedFormatter { 
     public final static Pattern pattern = Pattern.compile("#\\{(?<key>.*)}"); 
     public static String format(final String format, Map<String, ? extends Object> kvs) { 
      final StringBuffer buffer = new StringBuffer(); 
      final Matcher match = pattern.matcher(format); 
      while (match.find()) { 
       final String key = match.group("key"); 
       final Object value = kvs.get(key); 
       if (value != null) 
        match.appendReplacement(buffer, value.toString()); 
       else if (kvs.containsKey(key)) 
        match.appendReplacement(buffer, "null"); 
       else 
        match.appendReplacement(buffer, ""); 
      } 
      match.appendTail(buffer); 
      return buffer.toString(); 
     } 
    } 

    @Test 
    public void test() { 
     assertEquals("hello world", NamedFormatter.format("hello #{name}", map("name", "world"))); 
     assertEquals("hello null", NamedFormatter.format("hello #{name}", map("name", null))); 
     assertEquals("hello ", NamedFormatter.format("hello #{name}", new HashMap<String, Object>())); 
    } 

    private Map<String, Object> map(final String key, final Object value) { 
     final Map<String, Object> kvs = new HashMap<>(); 
     kvs.put(key, value); 
     return kvs; 
    } 
} 

me extiendo a añadir a métodos de conveniencia para un rápido valor clave pares

format(format, key1, value1) 
format(format, key1, value1, key2, value2) 
format(format, key1, value1, key2, value2, key3, value3) 
... 

Y no debería ser demasiado difícil de convertir de java 7+ a java 6-

+0

Me gustaría mencionar que tampoco es realmente liviano: básicamente permite ejecutar el subconjunto Java, interpolado dentro de algunas cadenas. Es 138K valor de clases comprimidas, 114 clases dentro. – GreyCat

+0

Sí, respondí eso desde mi teléfono e iba a agregar otra implementación tan pronto como llegue a un teclado. –

+0

agregó mi propia implementación ahora. –

0

Aquí está mi solución:

public class Template 
{ 

    private Pattern pattern; 
    protected Map<CharSequence, String> tokens; 
    private String template; 

    public Template(String template) 
    { 
     pattern = Pattern.compile("\\$\\{\\w+\\}"); 
     tokens = new HashMap<CharSequence, String>(); 
     this.template = template; 
    } 

    public void clearAllTokens() 
    { 
     tokens.clear(); 
    } 

    public void setToken(String token, String replacement) 
    { 
     if(token == null) 
     { 
      throw new NullPointerException("Token can't be null"); 
     } 

     if(replacement == null) 
     { 
      throw new NullPointerException("Replacement string can't be null"); 
     } 

     tokens.put(token, Matcher.quoteReplacement(replacement)); 
    } 

    public String getText() 
    { 
     final Matcher matcher = pattern.matcher(template); 
     final StringBuffer sb = new StringBuffer(); 

     while(matcher.find()) 
     { 
      final String entry = matcher.group(); 
      final CharSequence key = entry.subSequence(2, entry.length() - 1); 
      if(tokens.containsKey(key)) 
      { 
       matcher.appendReplacement(sb, tokens.get(key)); 
      } 
     } 
     matcher.appendTail(sb); 
     return sb.toString(); 
    } 


    public static void main(String[] args) { 
     Template template = new Template("Hello, ${name}."); 
     template.setToken("name", "Eldar"); 

     System.out.println(template.getText()); 
    } 
} 
0

Sé que mi respuesta llega un poco tarde, pero si aún necesita esta funcionalidad, sin la necesidad de descargar un motor de plantillas completas, puede consultar aleph-formatter (soy uno de los autores):

Student student = new Student("Andrei", 30, "Male"); 

String studStr = template("#{id}\tName: #{st.getName}, Age: #{st.getAge}, Gender: #{st.getGender}") 
        .arg("id", 10) 
        .arg("st", student) 
        .format(); 
System.out.println(studStr); 

O puede encadenar los argumentos:

String result = template("#{x} + #{y} = #{z}") 
        .args("x", 5, "y", 10, "z", 15) 
        .format(); 
System.out.println(result); 

// Output: "5 + 10 = 15" 

Internamente funciona usando un StringBuilder crear el resultado por "analizar" la expresión, sin concatenación de cadenas, expresiones regulares/sustitución se lleva a cabo.

Cuestiones relacionadas