Java programmers can implement user-defined directives in Java using the TemplateDirectiveModel interface. See in the API documentation.
Note
TemplateDirectiveModel was introduced in FreeMarker 2.3.11, replacing the soon to be depreciated TemplateTransformModel.
We will implement a directive which converts all output between its start-tag and end-tag to upper case. Like, this template:
|
|
|
|
foo <@upper> bar <#-- All kind of FTL is allowed here --> <#list ["red", "green", "blue"] as color> ${color} </#list> baaz </@upper> wombat聽
|
|
|
|
|
|
will output this:
|
|
|
|
foo BAR RED GREEN BLUE BAAZ wombat聽
|
|
|
|
|
|
This is the source code of the directive class:
|
|
|
|
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(); } }
}聽
|
|
|
|
|
|
Now we still need to create an instance of this class, and make this
directive available to the template with the name "upper" (or with
whatever name we want) somehow. A possible solution is to put the
directive in the data-model:
|
|
|
|
root.put("upper", new com.example.UpperDirective());聽
|
|
|
|
|
|
But typically it is better practice to put commonly used directives into the Configuration as shared variables.
It is also possible to put the directive into an FTL library (collection of macros and like in a template, that you include or import in other templates) using the new built-in:
|
|
|
|
<#-- 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()>聽
|
|
|
|
|
|
鎴戜滑灝嗗垱寤轟竴涓寚浠ょ殑鎵ц鎸囧畾鐨勬鏁幫紙鍚屾牱鍒楀嚭鎸囦護錛夐殢鎰忓垎闅斾笌涓涓?lt;hr> - S鐨剅epetations杈撳嚭錛屽叾宓屽鐨勫唴瀹癸紝鍐嶅涔犮?/span>
璁╂垜浠妸榪欑鎸囦護鈥滈噸澶嶁濄?/span>
渚嬪妯℃澘錛?/span>
|
|
|
|
<#assign x = 1>
<@repeat count=4> Test ${x} <#assign x = x + 1> </@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()); } } }
}聽
|
|
|
|
|
|
榪欐槸闈炲父閲嶈鐨勪竴TemplateDirectiveModel瀵硅薄閫氬父涓嶅簲璇ユ湁鐘舵併?/span>
鍏稿瀷鐨勯敊璇槸瀵瑰湪璇ュ璞$殑瀛楁鎸囦護璋冪敤鎵ц鐘舵佸偍瀛樸?/span>
瀵瑰悓涓鎸囦護錛屾垨鑰呮寚浠ゅ祵濂楄皟鐢ㄧ湅鎴愭槸鐢卞涓嚎紼嬪悓鏃惰闂叡浜彉閲忎嬌鐢ㄧ殑瀵硅薄銆?br />
鍙儨鐨勬槸錛孴emplateDirectiveModel錛屽氨鍋氫笉鏀寔浼犻掑弬鏁版寜浣嶇疆錛堣屼笉鏄悕縐幫級銆?/span>
榪欐槸鍥哄畾鐨勮搗浠稦reeMarker鐨?.4銆?/span>