Как я могу исследовать шаблон freemarker, чтобы узнать, какие переменные он использует?

Я вовсе не уверен, что это даже разрешимая проблема, но, полагая, что у меня есть шаблон freemarker, я хотел бы спросить шаблон о том, какие переменные он использует.

В моих целях мы можем предположить, что шаблон freemarker очень прост - только записи "root level" (модель для такого шаблона может быть простой Map). Другими словами, мне не нужно обрабатывать шаблоны, которые вызывают вложенные структуры и т.д.

Ответы

Ответ 1

У меня была та же задача получить список переменных из шаблона на стороне 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

Возможно, это было поздно, но в случае, если кто-то столкнулся с этой проблемой: вы можете использовать "data_model" и "globals" для проверки модели. data_model будет содержать только значения, предоставленные моделью, тогда как глобальные переменные также будут содержать любые переменные, определенные в шаблон. Вам нужно добавить специальные переменные с точкой - для доступа к глобальным переменным используйте ${. Globals}

Для других специальных переменных см. http://freemarker.sourceforge.net/docs/ref_specvar.html

Ответ 3

еще один способ получить переменные из java. Это просто пытается обработать шаблон и уловить InvalidReferenceException, чтобы найти все переменные в шаблоне freemarker

 /**
 * 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);
    }
}

Ответ 4

Я решил это для моей очень простой usecase (только с использованием плоской структуры данных, без вложенности (например: ${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 содержит ваши переменные.

Это очень упрощенный подход, который, безусловно, нуждается в улучшении, но вы получаете идею.

Ответ 5

У меня была такая же проблема, и ни одно из опубликованных решений не имело для меня смысла. То, с чем я столкнулся, - это 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

Ответ 6

Выполните следующее регулярное выражение в шаблоне:

(?<=\$\{)([^\}]+)(?=\})
  • (? < =\$\ {) Совпадает со всем, за которым следует ${
  • ([^ \}] +) Соответствует любой строке, не содержащей}
  • (? = \}) Совпадает со всем до}