如何内省自由标记模板以找出它使用的变量?

时间:2009-12-17 02:54:41

标签: java freemarker

我完全不确定这是否是一个可解决的问题,但假设我有一个自由标记模板,我希望能够向模板询问它使用的变量。

出于我的目的,我们可以假设freemarker模板非常简单 - 只是“根级”条目(这种模板的模型可以是一个简单的Map)。换句话说,我不需要处理调用嵌套结构的模板等。

7 个答案:

答案 0 :(得分:5)

这可能已经晚了,但是如果有其他人遇到这个问题:你可以使用'data_model'和'globals'来检查模型 - data_model只包含模型提供的值,而globals也包含任何定义的变量模板。 您需要在点前添加特殊变量 - 因此要访问全局变量,请使用$ {。globals}

有关其他特殊变量,请参阅http://freemarker.sourceforge.net/docs/ref_specvar.html

答案 1 :(得分:2)

我有同样的任务从java端获取模板中的变量列表,除了使用反射之外,没有找到任何好的方法。我不确定是否有更好的方法来获取这些数据,但这是我的方法:

public Set<String> referenceSet(Template template) throws TemplateModelException {
    Set<String> 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);
    }
}

我在github上找到了用于演示模板内省的示例项目:https://github.com/SimY4/TemplatesPOC.git

答案 2 :(得分:2)

从java获取变量的另一种方法。这只是尝试处理模板并捕获InvalidReferenceException以查找freemarker-template中的所有变量

 /**
 * Find all the variables used in the Freemarker Template
 * @param templateName
 * @return
 */
public Set<String> getTemplateVariables(String templateName) {
    Template template = getTemplate(templateName);
    StringWriter stringWriter = new StringWriter();
    Map<String, Object> 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);
    }
}

答案 3 :(得分:0)

我为我的非常简单的用例解决了这个问题(仅使用平面数据结构,没有嵌套(例如:$ {parent.child}),列表或更具体)与虚拟数据提供者:

public class DummyDataProvider<K, V> extends HashMap<K, V> {
    private static final long serialVersionUID = 1;

    public final Set<String> variables = new HashSet<>();

    @SuppressWarnings("unchecked")
    @Override
    public V get(Object key) {
        variables.add(key.toString());
        return (V) key;
    }
}

您可以将其设置为处理模板,并在完成时设置variables包含您的变量。

这是一种非常简单的方法,当然需要改进,但你明白了。

答案 4 :(得分:0)

public static Set<String> getNecessaryTemplateVariables(String templateName) throws TemplateModelException {
        Set<String> result = new HashSet<>();
        TemplateElement rootTreeNode = getTemplate(templateName).getRootTreeNode();
        if ("IteratorBlock".equals(rootTreeNode.getClass().getSimpleName())) {
            introspectFromIteratorBlock(result, rootTreeNode);
            return result;
        }
        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())) {
                introspectFromDollarVariable(result, wrappedObject);
            } else if ("ConditionalBlock".equals(wrappedObject.getClass().getSimpleName())) {
                introspectFromConditionalBlock(result, wrappedObject);
            } else if ("IfBlock".equals(wrappedObject.getClass().getSimpleName())) {
                introspectFromIfBlock(result, wrappedObject);
            } else if ("IteratorBlock".equals(wrappedObject.getClass().getSimpleName())) {
                introspectFromIteratorBlock(result, wrappedObject);
            }
        }
        return result;
    }

    private static void introspectFromIteratorBlock(Set<String> result, Object wrappedObject) {
        try {
            Object expression = getInternalState(wrappedObject, "listExp");
            result.add(getInternalState(expression, "name").toString());
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
    }

    private static void introspectFromConditionalBlock(Set<String> result, Object wrappedObject)
        throws TemplateModelException {
        try {
            Object expression = getInternalState(wrappedObject, "condition");
            if (expression == null) {
                return;
            }
            result.addAll(dealCommonExpression(expression));
            String nested = getInternalState(wrappedObject, "nestedBlock").toString();
            result.addAll(getNecessaryTemplateVariables(nested));
        } catch (NoSuchFieldException | IllegalAccessException ignored) {
        }
    }

    private static Set<String> dealCommonExpression(Object expression)
        throws NoSuchFieldException, IllegalAccessException {
        Set<String> ret = Sets.newHashSet();
        switch (expression.getClass().getSimpleName()) {
            case "ComparisonExpression":
                String reference = dealComparisonExpression(expression);
                ret.add(reference);
                break;
            case "ExistsExpression":
                reference = dealExistsExpression(expression);
                ret.add(reference);
                break;
            case "AndExpression":
                ret.addAll(dealAndExpression(expression));
            default:
                break;
        }
        return ret;
    }

    private static String dealComparisonExpression(Object expression)
        throws NoSuchFieldException, IllegalAccessException {
        Object left = getInternalState(expression, "left");
        Object right = getInternalState(expression, "right");
        String reference;
        if ("Identifier".equals(left.getClass().getSimpleName())) {
            reference = getInternalState(left, "name").toString();
        } else {
            reference = getInternalState(right, "name").toString();
        }
        return reference;
    }

    private static String dealExistsExpression(Object expression) throws NoSuchFieldException, IllegalAccessException {
        Object exp = getInternalState(expression, "exp");
        return getInternalState(exp, "name").toString();
    }

    private static Set<String> dealAndExpression(Object expression)  throws NoSuchFieldException,
        IllegalAccessException{
        Set<String> ret = Sets.newHashSet();
        Object lho = getInternalState(expression, "lho");
        ret.addAll(dealCommonExpression(lho));
        Object rho = getInternalState(expression, "rho");
        ret.addAll(dealCommonExpression(rho));
        return ret;
    }

    private static void introspectFromIfBlock(Set<String> result, Object wrappedObject)
        throws TemplateModelException {
        result.addAll(getNecessaryTemplateVariables(wrappedObject.toString()));
    }

    private static void introspectFromDollarVariable(Set<String> result, Object wrappedObject)
        throws TemplateModelException {
        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:
                    break;
            }
        } catch (NoSuchFieldException | IllegalAccessException e) {
            throw new TemplateModelException("Unable to reflect template model");
        }
    }

    private static Object getInternalState(Object o, String fieldName) throws NoSuchFieldException, IllegalAccessException {
        Field [] fieldArray = o.getClass().getDeclaredFields();
        for (Field field : fieldArray) {
            if (!field.getName().equals(fieldName)) {
                continue;
            }
            boolean wasAccessible = field.isAccessible();
            try {
                field.setAccessible(true);
                return field.get(o);
            } finally {
                field.setAccessible(wasAccessible);
            }
        }
        throw new NoSuchFieldException();
    }

    private static Template getTemplate(String templateName) {
        try {
            StringReader stringReader = new StringReader(templateName);
            return new Template(null, stringReader, null);
        } catch (IOException e) {
            throw new IllegalStateException("Failed to Load Template: " + templateName, e);
        }
    }

我优化了SimY4的答案,并支持<#list>和<#if>块。代码未经过全面测试

答案 5 :(得分:-1)

在模板上执行以下正则表达式:

(?<=\$\{)([^\}]+)(?=\})
  • (?&lt; = \ $ \ {)匹配所有内容,后跟$ {
  • ([^ \}] +)匹配任何不包含}
  • 的字符串
  • (?= \})匹配之前的所有内容}

答案 6 :(得分:-1)

我遇到了同样的问题,发布的解决方案都没有对我有意义。我带来的是插入自定义实现 TemplateExceptionHandler。例如:

final private List<String> 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.getBlamedExpressionString());
            return;
        }
        throw arg0;
    }

}

Template template = loadTemplate(cfg, templateId, templateText);

StringWriter out = new StringWriter();

try {
    template.process(templateData, out);
} catch (TemplateException | IOException e) {
        throw new RuntimeException("oops", e);
}

System.out.println("Missing references: " + missingReferences);

在此处阅读更多内容:https://freemarker.apache.org/docs/pgui_config_errorhandling.html