Как я могу исследовать шаблон 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
Выполните следующее регулярное выражение в шаблоне:
(?<=\$\{)([^\}]+)(?=\})
- (? < =\$\ {) Совпадает со всем, за которым следует ${
- ([^ \}] +) Соответствует любой строке, не содержащей}
- (? = \}) Совпадает со всем до}