定义自己的指令

Page Contents

就模板作者而言,可以使用macro指令定义用户定义的指令。想要以 Java 语言而不是模板来实现指令的 Java 程序员应该使用freemarker.template.TemplateDirectiveModel(请参阅more here...)。

Basics

宏是与变量关联的模板片段。您可以在模板中将该变量用作用户定义的指令,因此它有助于执行重复的任务。例如,这将创建一个宏变量,该变量打印一个大的“ Hello Joe!”:

<#macro greet>
  <font size="+2">Hello Joe!</font>
</#macro>

macro指令本身不显示任何内容;它只是创建宏变量,所以会有一个名为greet的变量。仅当您将变量用作指令时,才会执行<#macro greet></#macro>之间的操作(称为 宏定义主体 )。您可以通过在 FTL 标记中写入@而不是#来使用用户定义的指令。使用变量名称作为指令名称。此外,用户定义指令的end-tag是强制性的。因此,您使用greet是这样的:

<@greet></@greet>

但由于<anything></anything><anything/>等价,因此您应使用以下较短的格式(如果您知道XML,则对您很熟悉):

<@greet/>

这将打印:

<font size="+2">Hello Joe!</font>

但是宏可以做更多的事情,因为<#macro ...></#macro>之间的内容是模板片段,因此它可以包含插值(${...})和 FTL 标签(例如<#if ...>...</#if>)。

Note:

程序员会在<@...>上说您“调用”宏。

Parameters

让我们改进greet宏,以便它可以使用任意名称,而不仅是“ Joe”。为此,您可以使用 parameters 。您可以在macro指令中的宏名称之后定义参数。在这里,我们为greetperson定义了一个参数:

<#macro greet person>
  <font size="+2">Hello ${person}!</font>
</#macro>

然后可以将此宏用作:

<@greet person="Fred"/> and <@greet person="Batman"/>

这类似于 HTML 语法。这将打印:

<font size="+2">Hello Fred!</font>
 and   <font size="+2">Hello Batman!</font>

如您所见,宏参数的实际值可以在宏定义主体中作为变量(person)访问。与predefined directives一样,参数的值(=的右侧)是FTL expression。因此,与 HTML 不同,"Fred""Batman"周围的引号不是可选的。 <@greet person=Fred/>表示您将变量Fred的值用于person参数,而不是字符串"Fred"。当然,参数值不必是字符串,可以是数字,布尔值,哈希,序列等,也可以在=的右侧使用复杂的表达式(例如someParam=(price + 50)*1.25)。

用户定义的指令可以具有多个参数。例如,添加一个新参数color

<#macro greet person color>
  <font size="+2" color="${color}">Hello ${person}!</font>
</#macro>

然后您可以使用此宏,例如:

<@greet person="Fred" color="black"/>

参数的 Sequences 并不重要,因此与前面的命令等效:

<@greet color="black" person="Fred"/>

调用宏时,只能使用在macro指令中定义的参数(在本例中为personcolor)。因此,如果您尝试<@greet person="Fred" color="black" background="green"/>,则将得到一个错误,因为您没有在<#macro ...>中提到参数background

同样,您必须为宏定义的所有参数提供值。因此,如果您尝试<@greet person="Fred"/>,则将得到一个错误,因为您忘记指定color的值。但是,在大多数情况下,经常会为参数指定相同的值,因此仅在需要与通常不同的值时才希望指定该值。如果在macro指令中将参数指定为param_name=usual_value,则可以实现此目的。例如,如果在使用greet指令时未为该参数指定值,则想将"black"用于color

<#macro greet person color="black">
  <font size="+2" color="${color}">Hello ${person}!</font>
</#macro>

现在<@greet person="Fred"/>是确定的,因为它等效于<@greet person="Fred" color="black"/>,因此color参数的值是已知的。如果要_5 表示"red",则编写<@greet person="Fred" color="red"/>,并且该值将覆盖macro指令指定的常规值,因此color参数的值将为"red"

同样,重要的是要认识到-根据已经说明的FTL 表达规则-someParam=foosomeParam="${foo}"是非常不同的。在第一种情况下,将变量foo的值用作参数的值。在第二种情况下,您使用带插值的字符串 Literals,因此参数的值将是一个字符串-在这种情况下,foo的值将呈现为文本-而不考虑foo的类型(如数字,日期等)。 。或者,另一个示例:someParam=3/4someParam="${3/4}"不同。如果该指令想要someParam的数值,它将不喜欢第二种变化。不要交换这些。

宏参数的一个非常重要的方面是它们是局部变量。有关局部变量的更多信息,请阅读:在模板中定义变量

Nested content

自定义指令可以具有嵌套内容,类似于<#if ...>nested content</#if>之类的 sched 义指令。例如,这将创建一个宏,以在其嵌套内容周围绘制边框:

<#macro border>
  <table border=4 cellspacing=0 cellpadding=4><tr><td>
    <#nested>
  </tr></td></table>
</#macro>

<#nested>指令在指令的开始标记和结束标记之间执行模板片段。因此,如果您这样做:

<@border>The bordered text</@border>

输出将是:

<table border=4 cellspacing=0 cellpadding=4><tr><td>
    The bordered text
  </td></tr></table>

nested指令可以多次调用,例如:

<#macro do_thrice>
  <#nested>
  <#nested>
  <#nested>
</#macro>
<@do_thrice>
  Anything.
</@do_thrice>

will print:

Anything.
  Anything.
  Anything.

如果您不使用nested指令,则将不会执行嵌套的内容。因此,如果您不小心使用了greet指令,例如:

<@greet person="Joe">
  Anything.
</@greet>

那么 FreeMarker 不会将其视为错误,而只是打印:

<font size="+2">Hello Joe!</font>

并且嵌套内容将被忽略,因为greet宏从不使用nested指令。

嵌套的内容可以是任何有效的 FTL,包括其他用户定义的指令。这样就可以了:

<@border>
  <ul>
  <@do_thrice>
    <li><@greet person="Joe"/>
  </@do_thrice>
  </ul>
</@border>

并打印:

<table border=4 cellspacing=0 cellpadding=4><tr><td>
      <ul>
    <li><font size="+2">Hello Joe!</font>

    <li><font size="+2">Hello Joe!</font>

    <li><font size="+2">Hello Joe!</font>

  </ul>

  </tr></td></table>

宏的local variables在嵌套内容中不可见。说这个:

<#macro repeat count>
  <#local y = "test">
  <#list 1..count as x>
    ${y} ${count}/${x}: <#nested>
  </#list>
</#macro>
<@repeat count=3>${y!"?"} ${x!"?"} ${count!"?"}</@repeat>

将打印此:

test 3/1: ? ? ?
    test 3/2: ? ? ?
    test 3/3: ? ? ?

因为yxcount是宏的局部(私有)变量,并且在宏定义之外看不到。此外,每个宏调用使用一组不同的局部变量,因此不会引起混淆:

<#macro test foo>${foo} (<#nested>) ${foo}</#macro>
<@test foo="A"><@test foo="B"><@test foo="C"/></@test></@test>

并打印此:

A (B (C () C) B) A

具有循环变量的宏

诸如list之类的 sched 义指令可以使用所谓的循环变量。您应该阅读在模板中定义变量以了解循环变量。

用户定义的指令也可以具有循环变量。例如,让我们扩展前面示例的do_thrice指令,以便将当前的重复编号公开为循环变量。与 sched 义的指令(如list)一样,循环变量的名称(在<#list foos as foo>...</#list>中为foo)在调用指令时给出,而变量的值(*)由指令本身设置。

<#macro do_thrice>
  <#nested 1>
  <#nested 2>
  <#nested 3>
</#macro>
<@do_thrice ; x> <#-- user-defined directive uses ";" instead of "as" -->
  ${x} Anything.
</@do_thrice>

这将打印:

1 Anything.
  2 Anything.
  3 Anything.

语法规则是,您将某个“循环”(即重复嵌套内容)的循环变量的实际值作为nested指令的参数(当然,该参数可以通过任意表达式表示)传递。循环变量的名称在参数和分号后的用户定义的指令打开标记(<@...>)中指定。

宏可以使用更多的一个循环变量(变量的 Sequences 很重要):

<#macro repeat count>
  <#list 1..count as x>
    <#nested x, x/2, x==count>
  </#list>
</#macro>
<@repeat count=4 ; c, halfc, last>
  ${c}. ${halfc}<#if last> Last!</#if>
</@repeat>

输出将是:

1. 0.5
  2. 1
  3. 1.5
  4. 2 Last!

如果您在用户定义的指令起始标记(即分号后)中指定的循环变量数与nested指令中指定的循环变量数不同,这不是问题。如果在分号后指定较少的循环变量,则简单地您将看不到nested指令提供的最后几个值,因为没有循环变量来保存这些值。这样就可以了:

<@repeat count=4 ; c, halfc, last>
  ${c}. ${halfc}<#if last> Last!</#if>
</@repeat>
<@repeat count=4 ; c, halfc>
  ${c}. ${halfc}
</@repeat>
<@repeat count=4>
  Just repeat it...
</@repeat>

如果在分号后指定的变量多于nested指令,则不会创建最后几个循环变量(即在嵌套内容中未定义)。

有关用户定义的指令和宏的更多信息

现在您可以阅读《 FreeMarker 参考》的相关部分:

您也可以在 FTL 中定义方法,请参见功能指令

另外,您可能对命名空间Namespaces感兴趣。命名空间可帮助您组织和重用常用的宏。

Java 程序员可能想知道指令(宏是指令)和方法(类似函数的东西)也可以分别通过实现 TemplateDirectiveModelTemplateMethodModelEx接口用 Java 语言编写。然后,您可以将 Java 实现插入<#assign foo = "com.example.FooDirective"?new()><#assign foo = "com.example.FooMethod"?new()>之类的模板中,否则将放置<#macro foo ...><#function foo ...>