Comment puis-je introspectionner un modèle Freemarker pour savoir quelles variables il utilise?

Je ne suis pas du tout sûr qu’il s’agisse même d’un problème pouvant être résolu, mais en supposant que je dispose d’un modèle Freemarker, j’aimerais pouvoir demander au modèle quelles variables il utilise.

Pour nos besoins, nous pouvons supposer que le modèle freemarker est très simple – il suffit d’entrer des entrées de “niveau racine” (le modèle d’un tel modèle pourrait être une simple carte). En d’autres termes, je n’ai pas besoin de gérer des modèles qui appellent des structures nestedes, etc.

J’ai eu la même tâche pour obtenir la liste des variables du modèle côté java et je n’ai trouvé aucune bonne approche, sauf la reflection. Je ne suis pas sûr qu’il existe un meilleur moyen d’obtenir ces données ou non, mais voici mon approche:

 public Set referenceSet(Template template) throws TemplateModelException { Set result = new HashSet<>(); TemplateElement rootTreeNode = template.getRootTreeNode(); for (int i = 0; i < rootTreeNode.getChildCount(); i++) { TemplateModel templateModel = rootTreeNode.getChildNodes().get(i); if (!(templateModel instanceof StringModel)) { continue; } Object wrappedObject = ((StringModel) templateModel).getWrappedObject(); if (!"DollarVariable".equals(wrappedObject.getClass().getSimpleName())) { continue; } try { Object expression = getInternalState(wrappedObject, "expression"); switch (expression.getClass().getSimpleName()) { case "Identifier": result.add(getInternalState(expression, "name").toString()); break; case "DefaultToExpression": result.add(getInternalState(expression, "lho").toString()); break; case "BuiltinVariable": break; default: throw new IllegalStateException("Unable to introspect variable"); } } catch (NoSuchFieldException | IllegalAccessException e) { throw new TemplateModelException("Unable to reflect template model"); } } return result; } private Object getInternalState(Object o, String fieldName) throws NoSuchFieldException, IllegalAccessException { Field field = o.getClass().getDeclaredField(fieldName); boolean wasAccessible = field.isAccessible(); try { field.setAccessible(true); return field.get(o); } finally { field.setAccessible(wasAccessible); } } 

Vous trouverez un exemple de projet pour démontrer l'introspection de modèles sur github: https://github.com/SimY4/TemplatesPOC.git

C’est probablement en retard, mais au cas où quelqu’un d’autre aurait rencontré ce problème: vous pouvez utiliser ‘data_model’ et ‘globals’ pour inspecter le modèle – data_model contiendra uniquement les valeurs fournies par le modèle alors que les globales contiendront toutes les variables définies dans le modèle. Vous devez append les variables spéciales avec un point – pour accéder aux globales, utilisez $ {. Globals}

Pour d’autres variables spéciales, voir http://freemarker.sourceforge.net/docs/ref_specvar.html

une autre façon d’obtenir les variables de java. Cela tente juste de traiter le modèle et d’attraper l’ InvalidReferenceException pour trouver toutes les variables dans un freemarker-template

  /** * Find all the variables used in the Freemarker Template * @param templateName * @return */ public Set getTemplateVariables(Ssortingng templateName) { Template template = getTemplate(templateName); SsortingngWriter ssortingngWriter = new SsortingngWriter(); Map dataModel = new HashMap<>(); boolean exceptionCaught; do { exceptionCaught = false; try { template.process(dataModel, stringWriter); } catch (InvalidReferenceException e) { exceptionCaught = true; dataModel.put(e.getBlamedExpressionString(), ""); } catch (IOException | TemplateException e) { throw new IllegalStateException("Failed to Load Template: " + templateName, e); } } while (exceptionCaught); return dataModel.keySet(); } private Template getTemplate(String templateName) { try { return configuration.getTemplate(templateName); } catch (IOException e) { throw new IllegalStateException("Failed to Load Template: " + templateName, e); } } 

J’ai résolu ce problème avec ma firebase database très simple (n’utilisant que des structures de données plates, aucune imbrication (par exemple: $ {parent.child}), des listes ou plus spécifiques) avec un fournisseur de données factice:

 public class DummyDataProvider extends HashMap { private static final long serialVersionUID = 1; public final Set variables = new HashSet<>(); @SuppressWarnings("unchecked") @Override public V get(Object key) { variables.add(key.toString()); return (V) key; } } 

Vous pouvez donner ceci pour le traitement à un modèle et quand il se termine, définir les variables contient vos variables.

C’est une approche très simpliste, qui doit certainement être améliorée, mais vous avez l’idée.

J’ai eu le même problème et aucune des solutions affichées n’a eu de sens pour moi. Ce que je suis venu est de twigr l’implémentation personnalisée de TemplateExceptionHandler . Exemple:

 final private List missingReferences = Lists.newArrayList(); final Configuration cfg = new Configuration(Configuration.VERSION_2_3_23); cfg.setTemplateExceptionHandler(new TemplateExceptionHandler() { @Override public void handleTemplateException(TemplateException arg0, Environment arg1, Writer arg2) throws TemplateException { if (arg0 instanceof InvalidReferenceException) { missingReferences.add(arg0.getBlamedExpressionSsortingng()); return; } throw arg0; } } Template template = loadTemplate(cfg, templateId, templateText); SsortingngWriter out = new SsortingngWriter(); try { template.process(templateData, out); } catch (TemplateException | IOException e) { throw new RuntimeException("oops", e); } System.out.println("Missing references: " + missingReferences); 

Lire plus ici: https://freemarker.apache.org/docs/pgui_config_errorhandling.html

Exécutez la regex suivante sur le template:

 (?<=\$\{)([^\}]+)(?=\}) 
  • (? <= \ $ \ {) Correspond à tout suivi de $ {
  • ([^ \}] +) Correspond à toute chaîne ne contenant pas}
  • (? = \}) Correspond à tout avant}