Directives
Page Contents
Java 程序员可以使用TemplateDirectiveModel
接口在 Java 中实现用户定义的指令。请参阅 API 文档。
Note:
TemplateDirectiveModel
在 FreeMarker 2.3.11 中引入,代替了即将折旧的TemplateTransformModel
。
Example 1
我们将实现一个指令,该指令将其开始标记和结束标记之间的所有输出都转换为大写。像这样的模板:
foo
<@upper>
bar
<#-- All kind of FTL is allowed here -->
<#list ["red", "green", "blue"] as color>
${color}
</#list>
baaz
</@upper>
wombat
将输出以下内容:
foo
BAR
RED
GREEN
BLUE
BAAZ
wombat
这是指令类的源代码:
package com.example;
import java.io.IOException;
import java.io.Writer;
import java.util.Map;
import freemarker.core.Environment;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
/**
* FreeMarker user-defined directive that progressively transforms
* the output of its nested content to upper-case.
*
*
* <p><b>Directive info</b></p>
*
* <p>Directive parameters: None
* <p>Loop variables: None
* <p>Directive nested content: Yes
*/
public class UpperDirective implements TemplateDirectiveModel {
public void execute(Environment env,
Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body)
throws TemplateException, IOException {
// Check if no parameters were given:
if (!params.isEmpty()) {
throw new TemplateModelException(
"This directive doesn't allow parameters.");
}
if (loopVars.length != 0) {
throw new TemplateModelException(
"This directive doesn't allow loop variables.");
}
// If there is non-empty nested content:
if (body != null) {
// Executes the nested body. Same as <#nested> in FTL, except
// that we use our own writer instead of the current output writer.
body.render(new UpperCaseFilterWriter(env.getOut()));
} else {
throw new RuntimeException("missing body");
}
}
/**
* A {@link Writer} that transforms the character stream to upper case
* and forwards it to another {@link Writer}.
*/
private static class UpperCaseFilterWriter extends Writer {
private final Writer out;
UpperCaseFilterWriter (Writer out) {
this.out = out;
}
public void write(char[] cbuf, int off, int len)
throws IOException {
char[] transformedCbuf = new char[len];
for (int i = 0; i < len; i++) {
transformedCbuf[i] = Character.toUpperCase(cbuf[i + off]);
}
out.write(transformedCbuf);
}
public void flush() throws IOException {
out.flush();
}
public void close() throws IOException {
out.close();
}
}
}
现在,我们仍然需要创建此类的实例,并以某种方式使该指令可用于名称为“ upper”(或我们想要的任何名称)的模板。可能的解决方案是将指令放入数据模型:
root.put("upper", new com.example.UpperDirective());
但是通常最好将常用指令作为shared variables放入Configuration
中。
也可以使用new built-in将指令放入 FTL 库(宏的集合,就像在模板中一样,在其他模板中include
或import
)。
<#-- Maybe you have directives that you have implemented in FTL -->
<#macro something>
...
</#macro>
<#-- Now you can't use <#macro upper>, but instead you can: -->
<#assign upper = "com.example.UpperDirective"?new()>
Example 2
我们将创建一个指令,该指令一次又一次地执行其嵌套内容指定的次数(类似于list
指令),可以选择使用<hr>
-s 分隔重复的输出。我们将此指令称为“重复”。示例模板:
<#assign x = 1>
<@repeat count=4>
Test ${x}
<#assign x++>
</@repeat>
<@repeat count=3 hr=true>
Test
</@repeat>
<@repeat count=3; cnt>
${cnt}. Test
</@repeat>
Output:
Test 1
Test 2
Test 3
Test 4
Test
<hr> Test
<hr> Test
1. Test
2. Test
3. Test
The class:
package com.example;
import java.io.IOException;
import java.io.Writer;
import java.util.Iterator;
import java.util.Map;
import freemarker.core.Environment;
import freemarker.template.SimpleNumber;
import freemarker.template.TemplateBooleanModel;
import freemarker.template.TemplateDirectiveBody;
import freemarker.template.TemplateDirectiveModel;
import freemarker.template.TemplateException;
import freemarker.template.TemplateModel;
import freemarker.template.TemplateModelException;
import freemarker.template.TemplateNumberModel;
/**
* FreeMarker user-defined directive for repeating a section of a template,
* optionally with separating the output of the repetations with
* <tt><hr></tt>-s.
*
*
* <p><b>Directive info</b></p>
*
* <p>Parameters:
* <ul>
* <li><code>count</code>: The number of repetations. Required!
* Must be a non-negative number. If it is not a whole number then it will
* be rounded <em>down</em>.
* <li><code>hr</code>: Tells if a HTML "hr" element could be printed between
* repetations. Boolean. Optional, defaults to <code>false</code>.
* </ul>
*
* <p>Loop variables: One, optional. It gives the number of the current
* repetation, starting from 1.
*
* <p>Nested content: Yes
*/
public class RepeatDirective implements TemplateDirectiveModel {
private static final String PARAM_NAME_COUNT = "count";
private static final String PARAM_NAME_HR = "hr";
public void execute(Environment env,
Map params, TemplateModel[] loopVars,
TemplateDirectiveBody body)
throws TemplateException, IOException {
// ---------------------------------------------------------------------
// Processing the parameters:
int countParam = 0;
boolean countParamSet = false;
boolean hrParam = false;
Iterator paramIter = params.entrySet().iterator();
while (paramIter.hasNext()) {
Map.Entry ent = (Map.Entry) paramIter.next();
String paramName = (String) ent.getKey();
TemplateModel paramValue = (TemplateModel) ent.getValue();
if (paramName.equals(PARAM_NAME_COUNT)) {
if (!(paramValue instanceof TemplateNumberModel)) {
throw new TemplateModelException(
"The \"" + PARAM_NAME_HR + "\" parameter "
+ "must be a number.");
}
countParam = ((TemplateNumberModel) paramValue)
.getAsNumber().intValue();
countParamSet = true;
if (countParam < 0) {
throw new TemplateModelException(
"The \"" + PARAM_NAME_HR + "\" parameter "
+ "can't be negative.");
}
} else if (paramName.equals(PARAM_NAME_HR)) {
if (!(paramValue instanceof TemplateBooleanModel)) {
throw new TemplateModelException(
"The \"" + PARAM_NAME_HR + "\" parameter "
+ "must be a boolean.");
}
hrParam = ((TemplateBooleanModel) paramValue)
.getAsBoolean();
} else {
throw new TemplateModelException(
"Unsupported parameter: " + paramName);
}
}
if (!countParamSet) {
throw new TemplateModelException(
"The required \"" + PARAM_NAME_COUNT + "\" paramter"
+ "is missing.");
}
if (loopVars.length > 1) {
throw new TemplateModelException(
"At most one loop variable is allowed.");
}
// Yeah, it was long and boring...
// ---------------------------------------------------------------------
// Do the actual directive execution:
Writer out = env.getOut();
if (body != null) {
for (int i = 0; i < countParam; i++) {
// Prints a <hr> between all repetations if the "hr" parameter
// was true:
if (hrParam && i != 0) {
out.write("<hr>");
}
// Set the loop variable, if there is one:
if (loopVars.length > 0) {
loopVars[0] = new SimpleNumber(i + 1);
}
// Executes the nested body (same as <#nested> in FTL). In this
// case we don't provide a special writer as the parameter:
body.render(env.getOut());
}
}
}
}
Notices
重要的是TemplateDirectiveModel
对象通常不应为有状态的。典型的错误是将指令调用执行的状态存储在对象的字段中。考虑同一指令的嵌套调用,或用作多个线程同时访问的共享变量的指令对象。
不幸的是,TemplateDirectiveModel
-s 不支持按位置(而不是按名称)传递参数。这是从 FreeMarker 2.4 开始修复的。