freeMarker(十一)——模板语言之指令

学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net

1.assign

概要

<#assign name1=value1 name2=value2 ... nameN=valueN><#assign same as above... in namespacehash><#assign name>
  capture this
#assign><#assign name in namespacehash>
  capture this
#assign>

这里:

  • name:变量的名字。 它不是表达式。而它可以写作是字符串,如果变量名包含保留字符这是很有用的, 比如 <#assign "foo-bar" = 1>。 请注意这个字符串没有展开插值(如"${foo}"); 如果需要赋值一个动态创建的名字,那么不得不使用 这个技巧。
  • =:赋值操作符。 它也可以是一个简写的赋值操作符(从 FreeMarker 2.3.23 版本开始): ++--+=-=*=/=%=。比如 <#assign x++><#assign x = x + 1> 是一样的,并且 <#assign x += 2><#assign x = x + 2> 是相同的。 请注意, ++ 通常意味着算术加法 (对于非数字将会失败),不像 ++= 可以进行字符连接等重载操作。
  • value: 存储的值。是表达式。
  • namespacehash:(通过 import) 为命名空间创建的哈希表。是表达式。

描述

  使用该指令你可以创建一个新的变量, 或者替换一个已经存在的变量。注意仅仅顶级变量可以被创建/替换 (也就是说你不能创建/替换 some_hash.subvar, 除了 some_hash)。

  关于变量的更多内容,请阅读:模板开发指南/其它/在模板中定义变量

  比如:变量 seq 存储一个序列:

<#assign seq = ["foo", "bar", "baz"]>

  比如:变量 x 中存储增长的数字:

<#assign x++>

  作为一个方便的特性,你可以使用一个 assign 标记来进行多次定义。比如这个会做上面两个例子中相同的事情:

<#assign
  seq = ["foo", "bar", "baz"]
  x++
>

  如果你知道什么是命名空间:assign 指令在命名空间中创建变量。通常它在当前的命名空间 (也就是和标签所在模板关联的命名空间)中创建变量。但如果你是用了 in namespacehash, 那么你可以用另外一个 命名空间 来创建/替换变量。 比如,这里你在命名空间中 /mylib.ftl 创建/替换了变量 bgColor

1 <#import "/mylib.ftl" as my>
2 <#assign bgColor="red" in my>

  assign 的极端使用是当它捕捉它的开始标记和结束标记中间生成的输出时。 也就是说,在标记之间打印的东西将不会在页面上显示, 但是会存储在变量中。比如:

1 <#macro myMacro>foo#macro>
2 <#assign x>
3   <#list 1..3 as n>
4     ${n} <@myMacro />
5   #list>
6 #assign>
7 Number of words: ${x?word_list?size}
8 ${x}

  将会输出:

Number of words: 6
    1 foo
    2 foo
    3 foo

  请注意,你不应该使用它来往字符串中插入变量:

<#assign x>Hello ${user}!#assign> <#-- BAD PRACTICE! -->

  你可以这么来写:

<#assign x="Hello ${user}!">

2.attempt, recover

概要

1 <#attempt>
2   attempt block
3 <#recover>
4   recover block
5 #attempt>

这里:

  • attempt block:任意内容的模板块。这是会被执行的, 但是如果期间发生了错误,那么这块内容的输出将会回滚, 之后 recover block 就会被执行。
  • recover block: 任意内容的模板块。 这个仅在 attempt block 执行期间发生错误时被执行。你可以在这里打印错误信息或其他操作。

  recover 是强制的。 attempt/recover 可以嵌套在其他 attempt blockrecover block中。

注意:上面的格式是从 2.3.3 版本开始支持的,之前它是 <#attempt>...<#recover>...,也支持向下兼容。此外, 这些指令是在 FreeMarker 2.3.1 版本时引入的,在 2.3 版本中是不存在的。

描述

  如果你想让页面成功输出内容,尽管它在页面特定位置发生错误也这样, 那么这些指令就是有用的。如果一个错误在 attempt block 执行期间发生, 那么模板执行就会中止,但是 recover block 会代替 attempt block 执行。 如果在 attempt block 执行期间没有发生错误, 那么 recover block 就会忽略。 一个简单的示例如下:

1 Primary content
2 <#attempt>
3   Optional content: ${thisMayFails}
4 <#recover>
5   Ops! The optional content is not available.
6 #attempt>
7 Primary content continued

  如果 thisMayFails 变量不存在,将会输出:

Primary content
  Ops! The optional content is not available.
Primary content continued

  如果 thisMayFails 变量存在而且值为 123,将会输出:

Primary content
  Optional content: 123
Primary content continued

  attempt block 块有多或没有的语义:不管 attempt block块的完整内容是否输出(没有发生错误), 或者在 attempt block (没有发生错误)块执行时没有输出结果。比如,上面的示例, 发生在"Optional content"之后的失败被打印出来了,而没有在"Ops!"之前输出。 (这是在 attempt block 块内,侵入的输出缓冲的实现,甚至连 flush 指令也会送输出到客户端。)

  为了阻止来自上面示例的误解: attempt/recover 不(仅仅)是处理未定义变量(可以使用 不存在变量控制符)。 它可以处理发生在块执行期间的各种类型的错误 (而不是语法错误,这会在执行之前被检测到)。它的目的是包围更大的模板段, 错误可能发生在很多地方。比如,你在模板中有一个部分,来处理打印广告, 但是它不是页面的主要内容,所以你不想你的页面因为一些打印广告 (也可能是短暂的数据库服务器故障)的错误而挂掉。 所以你将整个广告区域放在 attempt block 块中。

  在一些环境下,程序员配置 FreeMarker,所以对于特定的错误, 它不会中止模板的执行,在打印一些错误提示信息到输出 (更多内容,请参考 这里...)中之后, 而是继续执行。attempt 指令不会将这些抑制的错误视为错误。

  在 recover block 块中,错误的信息存在 特殊变量 error 中。 不要忘了以点开始引用特殊变量(比如:${.error})。

  在模板执行期间发生的错误通常被 日志记录,不管是否发生在 attempt block块中。

3.compress

概要

1 <#compress>
2   ...
3 #compress>

描述

  当你使用了对空白不敏感的格式(比如HTML或XML) 时压缩指令对于移除多余的 空白 是很有用的。它捕捉在指令体(也就是在开始标签和结束标签中)中生成的内容, 然后缩小所有不间断的空白序列到一个单独的空白字符。 如果被替代的序列包含换行符或是一段空间,那么被插入的字符也会是一个 换行符。 开头和结尾的不间断的空白序列将会完全被移除。

1 <#assign x = "    moo  \n\n   ">
2 (<#compress>
3   1 2  3   4    5
4   ${moo}
5   test only
6 
7   I said, test only
8 
9 #compress>)

  将会输出:

(1 2 3 4 5
moo
test only
I said, test only)

4.escape, noescape

概要

1 <#escape identifier as expression>
2   ...
3   <#noescape>...#noescape>
4   ...
5 #escape>

描述

  当你使用escape指令包围模板中的一部分时,在块中出现的插值 (${...}) 会和转义表达式自动结合。这是一个避免编写相似表达式的很方便的方法。 它不会影响在字符串形式的插值(比如在 <#assign x = "Hello ${user}!">)。而且,它也不会影响数值插值 (#{...})。

  例如:

1 <#escape x as x?html>
2   First name: ${firstName}
3   Last name: ${lastName}
4   Maiden name: ${maidenName}
5 #escape>

  事实上它等同于:

1  First name: ${firstName?html}
2   Last name: ${lastName?html}
3   Maiden name: ${maidenName?html}

  请注意,它和你在指令中用什么样的标识符无关 - 它仅仅是作为一个转义表达式的正式参数。

  当在调用宏或者 include 指令时, 理解 在模板文本 中转义仅仅对出现在 <#escape ...> 中的插值起作用是很重要的。 也就是说,它不会转义文本中 <#escape ...> 之前的东西或 之后的东西, 也不会从 escape 的部分中来调用。

 1 <#assign x = "">
 2 <#macro m1>
 3   m1: ${x}
 4 #macro>
 5 <#escape x as x?html>
 6   <#macro m2>m2: ${x}#macro>
 7   ${x}
 8   <@m1/>
 9 #escape>
10 ${x}
11 <@m2/>

  将会输出:

  <test>
  m1: <test>
<test>
m2: <test>

  从更深的技术上说, escape 指令的作用是用在模板解析的时间而不是模板处理的时间。 这就表示如果你调用一个宏或从一个转义块中包含另外一个模板, 它不会影响在宏/被包含模板中的插值,因为宏调用和模板包含被算在模板处理时间。 另外一方面,如果你用一个转义区块包围一个或多个宏声明(算在模板解析时间,和宏调用想法), 那么那些宏中的插值将会和转义表达式合并。

  有时需要暂时为一个或两个在转义区块中的插值关闭转义。你可以通过关闭, 过后再重新开启转义区块来达到这个功能,但是那么你不得不编写两遍转义表达式。 你可以使用非转义指令来替代:

1 <#escape x as x?html>
2   From: ${mailMessage.From}
3   Subject: ${mailMessage.Subject}
4   <#noescape>Message: ${mailMessage.htmlFormattedBody}#noescape>
5   ...
6 #escape>

  和这个是等同的:

  From: ${mailMessage.From?html}
  Subject: ${mailMessage.Subject?html}
  Message: ${mailMessage.htmlFormattedBody}
  ...

  转义可以被嵌套(尽管你不会在罕见的情况下来做)。 因此,你可以编写如下面代码(这个例子固然是有点伸展的, 正如你可能会使用 list 来迭代序列中的每一项, 但是我们现在所做的是阐述这个观点)的东西:

 1 <#escape x as x?html>
 2   Customer Name: ${customerName}
 3   Items to ship:
 4   <#escape x as itemCodeToNameMap[x]>
 5     ${itemCode1}
 6     ${itemCode2}
 7     ${itemCode3}
 8     ${itemCode4}
 9   #escape>
10 #escape>

  实际上和下面是等同的:

  Customer Name: ${customerName?html}
  Items to ship:
    ${itemCodeToNameMap[itemCode1]?html}
    ${itemCodeToNameMap[itemCode2]?html}
    ${itemCodeToNameMap[itemCode3]?html}
    ${itemCodeToNameMap[itemCode4]?html}

  当你在嵌入的转义区块内使用非转义指令时,它仅仅不处理一个单独层级的转义。 因此,为了在两级深的转义区块内完全关闭转义,你需要使用两个嵌套的非转义指令。

5.flush

概要

<#flush>

描述

  当 FreeMarker 生成输出时,它通常不会立即发送到最终接收端 (比如web浏览器或最终的文件),而是会将内容累积在缓冲区,发送一个大块的内容。 缓冲区的精确规则不是由 FreeMarker 决定的,而是由嵌入的软件决定的。 将缓冲区中累积的内容发送出去称为冲洗。尽管冲洗是自动发生的, 有时你想在模板处理时的一点强制执行,这就是 flush 指令要做的。如果需要在确定之处用到它,这是由程序员决定的,而不是设计师。

  请注意, flush 告诉嵌入的软件我们想要冲洗, 那么也许就会决定忽略该请求。这不由 FreeMarker 之手控制。

  冲洗简单调用当前使用 java.io.Writer 实例的 flush() 方法。 整个缓冲区和冲洗机制由 Writer(就是传递给 Template.process 方法的参数)实现; FreeMarker不用来处理它。

6.ftl

概要

<#ftl param1=value1 param2=value2 ... paramN=valueN>

这里:

  • param1, param2 等: 参数的名字,不是表达式。允许的参数有: encodingstrip_whitespacestrip_text 等。参见下面。
  • value1, value2 等: 参数的值。必须是一个常量表达式(如 true, 或 "ISO-8859-5",或 {x:1, y:2})。它不能用变量。

描述

  告诉 FreeMarker 和其他工具关于模板的信息, 而且帮助程序员来自动检测一个文本文件是否是 FTL 文件。该指令, 如果存在,必须是模板的第一句代码。该指令前的任何 空白 将被忽略。 该指令的老式语法(#-less)格式是不支持的。

  一些设置(编码方式,空白剥离等)在这里给定的话就有最高的优先级, 也就是说,它们直接作用于模板而不管其他任何 FreeMarker 的配置设置。

参数:

  • encoding: 使用它,你可以在模板文件本身中为模板指定编码方式(字符集) (也就是说,这是新创建 Templateencoding 设置, 而且 Configuration.getTemplate 中的 encoding 参数不能覆盖它)。要注意, FreeMarker 会尝试会和自动猜测的编码方式 (这依赖于程序员对 FreeMarker 的配置)找到 ftl 指令并解释它,然后就会发现 ftl 指令会让一些东西有所不同,之后以新的编码方式来重新读取模板。 因此,直到 ftl 标记使用第一个编码方式读取到结尾, 模板必须是合法的FTL。 这个参数的合法值是从IANA字符集注册表中参考MIME中的字符集名称。 比如 ISO-8859-5,UTF-8 或 Shift_JIS。

  • strip_whitespace: 这将开启/关闭 空白剥离。 合法的值是布尔值常量 truefalse (为了向下兼容,字符串 "yes""no""true""false" 也是可以的)。 默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker 的配置, 但是对新的项目还应该是 true

  • strip_text:当开启它时, 当模板被解析时模板中所有顶级文本被移除。这个不会影响宏,指令,或插值中的文本。 合法值是布尔值常量 truefalse (为了向下兼容,字符串 "yes""no""true""false" 也是可以的)。 默认值(也就是当你不使用这个参数时)是 false

  • strict_syntax:这会开启/关闭"严格的语法"。 合法值是布尔值常量 truefalse (为了向下兼容,字符串 "yes""no""true""false" 也是可以的)。 默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker 的配置, 但是对新的项目还应该是 true(程序员:对于 config.setStrictSyntaxMode(true); 你必须明确设置它为 true) 要获取更多信息,可以参考:废弃的 FTL 结构/老式 FTL 语法

  • ns_prefixes:这是关联结点命名空间前缀的哈希表。 比如:{"e":"http://example.com/ebook", "vg":"http://example.com/vektorGraphics"}。这通常是用于XML处理的, 前缀可以用于XML查询,但是它也影响 指令 visitrecurse的工作。 相同结点的命名空间只能注册一个前缀(否则会发生错误), 所以在前缀和结点命名空间中是一对一的关系。前缀 DN 是保留的。如果你注册前缀 D, 那么除了你可以使用前缀 D 来关联结点命名空间, 你也可以设置默认的结点命名空间。前缀 N 不能被注册; 当且仅当前缀 D 被注册时, 它被用来表示在特定位置没有结点命名空间的结点。 (要参考默认结点命名空间的用法,N,一般的前缀, 可以参考 XML 处理 和 visitrecurse)。ns_prefixes 的作用限制在单独的 FTL 命名空间内, 换句话说,就是为模板创建的FTL命名空间内。这也意味着 ns_prefixes 当一个FTL命名空间为包含它的模板所创建时才有作用, 否则 ns_prefixes 参数没有效果。FTL命名空间当下列情况下为模板创建: (a)模板是"主"模板,也就是说它不是被 <#include ...> 来调用的模板,但是直接被调用的(和类 TemplateEnvironment 中的Java方法 process); (b)模板直接被 <#import ...> 调用。

  • attributes:这是关联模板任意属性(名-值对)的哈希表。 属性的值可以是任意类型(字符串,数字,序列等)。FreeMarker 不会尝试去理解属性的含义。 它是由封装 FreeMarker(比如Web应用框架)的应用程序决定的。 因此,允许的属性的设置是它们依赖的应用(Web应用框架)的语义。 程序员:你可以通过关联 Template 对象的 getCustomAttributeNamesgetCustomAttribute 方法 (从 freemarker.core.Configurable 继承而来)获得属性。 如当模板被解析时,关联 Template 对象的模板属性, 属性可以在任意时间被读取,而模板不需要被执行。 上面提到的方法返回未包装的属性值,也就是说, 使用 FreeMarker 独立的类型,如 java.util.List

  该指令也决定模板是否使用尖括号语法(比如 <#include 'foo.ftl'>)或 方括号语法 (如 [#include 'foo.ftl'])。简单而言, 该指令使用的语法将会是整个模板使用的语法, 而不管 FreeMarker 是如何配置的。

7.function, return

概要

1 <#function name param1 param2 ... paramN>
2   ...
3   <#return returnValue>
4   ...
5 #function>

这里:

  • name:方法变量的名称(不是表达式)
  • param1, param2 等: 局部变量的名称, 存储参数的值(不是表达式),在 = 号后面和默认值 (是表达式)是可选的。
  • paramN,最后一个参数, 可以可选的包含一个尾部省略(...), 这就意味着宏接受可变的参数数量。局部变量 paramN 将是额外参数的序列。
  • returnValue: 计算方法调用值的表达式。

  return 指令可以在 <#function ...> 之间被用在任意位置和任意次数。

  没有默认值的参数必须在有默认值参数 (paramName=defaultValue) 之前

描述

  创建一个方法变量(在当前命名空间中,如果你知道命名空间的特性)。 这个指令和 macro 指令 的工作方式一样,除了 return 指令必须有一个参数来指定方法的返回值,而且视图写入输出的将会被忽略。 如果到达 (也就是说没有 return returnValue), 那么方法的返回值就是未定义变量。

  示例1:创建一个方法来计算两个数的平均值:

1 <#function avg x y>
2   <#return (x + y) / 2>
3 #function>
4 ${avg(10, 20)}

  将会输出:

15

  示例2:创建一个方法来计算多个数的平均值:

 1 <#function avg nums...>
 2   <#local sum = 0>
 3   <#list nums as num>
 4     <#local sum = sum + num>
 5   #list>
 6   <#if nums?size != 0>
 7     <#return sum / nums?size>
 8   #if>
 9 #function>
10 ${avg(10, 20)}
11 ${avg(10, 20, 30, 40)}
12 ${avg()!"N/A"}

  将会输出:

15
25
N/A

8.global

概要

<#global name=value><#global name1=value1 name2=value2 ... nameN=valueN><#global name>
  capture this
#global>

这里:

  • name:变量的名称。 它不是表达式。但它可以被写作是字符串形式,如果变量名包含保留字符这是很有用的, 比如 <#global "foo-bar" = 1>。 注意这个字符串没有扩展插值(如 "${foo}")。
  • =:赋值操作符,也可以简写的赋值操作符之一 (+++=,等...),和 assign 指令 相似
  • value:存储的值,是表达式。

描述

  该指令和 assign 相似, 但是被创建的变量在所有的 命名空间 中都可见, 但又不会存在于任何一个命名空间之中。精确地说,正如你会创建 (或替换)一个数据模型变量。因此,这个变量是全局的。如果在数据模型中, 一个相同名称的变量存在的话,它会被使用该指令创建的变量隐藏。 如果在当前的命名空间中,一个相同名称的变量存在的话, 那么会隐藏由 global 指令创建的变量。

  例如,用 <#global x = 1> 创建一个变量, 那么在所有命名空间中 x 都可见, 除非另外一个称为 x 的变量隐藏了它 (比如你已经用 <#assign x = 2> 创建了一个变量)。 这种情形下,你可以使用 特殊变量 globals,比如 ${.globals.x}。请注意, 使用 globals 你看到所有全局可访问的变量; 不但由 global 指令创建的变量,而且是数据模型中的变量。

  自定义JSP标记的用户请注意:用这个指令创建的变量集合和JSP页面范围对应。 这就意味着,如果自定义JSP标记想获得一个页面范围的属性(page-scope bean), 在当前命名空间中一个相同名称的变量,从JSP标记的观点出发,将不会隐藏。

9.if, else, elseif

概要

 1 <#if condition>
 2   ...
 3 <#elseif condition2>
 4   ...
 5 <#elseif condition3>
 6   ...
 7 ...
 8 <#else>
 9   ...
10 #if>

这里:

  • condition, condition2, 等:将被计算成布尔值的表达式。

  elseifelse 是可选的。

描述

  你可以使用 ifelseifelse 指令来条件判断是否越过模板的一个部分。 condition 必须计算成布尔值, 否则错误将会中止模板处理。elseifelse 必须出现在 if 内部 (也就是,在 if 的开始标签和结束标签之间)。 if 中可以包含任意数量的 elseif(包括0个) 而且结束时 else 是可选的。比如:

  只有 if 没有 elseifelse

1 <#if x == 1>
2   x is 1
3 #if>

  只有 if 没有 elseif 但是有 else

1 <#if x == 1>
2   x is 1
3 <#else>
4   x is not 1
5 #if>

  有 if 和两个 elseif 但是没有 else

1 <#if x == 1>
2   x is 1
3 <#elseif x == 2>
4   x is 2
5 <#elseif x == 3>
6   x is 3
7 #if>

  有 if 和三个 elseif 还有 else:

 1 <#if x == 1>
 2   x is 1
 3 <#elseif x == 2>
 4   x is 2
 5 <#elseif x == 3>
 6   x is 3
 7 <#elseif x == 4>
 8   x is 4
 9 <#else>
10   x is not 1 nor 2 nor 3 nor 4
11 #if>

  要了解更多布尔表达式,可以参考:模板开发指南/模板/表达式.

  你(当然)也可以嵌套 if 指令:

 1 <#if x == 1>
 2   x is 1
 3   <#if y == 1>
 4     and y is 1 too
 5   <#else>
 6     but y is not
 7   #if>
 8 <#else>
 9   x is not 1
10   <#if y < 0>
11     and y is less than 0
12   #if>
13 #if>
注意:当你想测试是否 x > 0x >= 0,编写 <#if x > 0><#if x >= 0> 是错误的, 因为第一个 > 会结束 #if 标签。要这么来做,可以编写 <#if x gt 0><#if gte 0>。也请注意,如果比较发生在括号内部,那么就没有这样的问题, 比如 <#if foo.bar(x > 0)> 就会得到想要的结果。

10.import

概要

<#import path as hash>

这里:

  • path:模板的路径。 这是一个算作是字符串的表达式。(换句话说,它不是一个固定的字符串, 它可以是这样的一些东西,比如,profile.baseDir + "/menu.ftl"。)
  • hash: 访问命名空间的哈希表变量不带引号的名字。不是表达式。 (如果要引入动态创建的名字,那么就不得不使用 这个技巧。)

描述

  引入一个库。也就是说,它创建一个新的空命名空间, 然后在那个命名空间中执行给定 path 参数中的模板, 所以模板用变量(宏,函数等)填充命名空间。 然后使得新创建的命名空间对哈希表的调用者可用。 这个哈希表变量将会在命名空间中,由 import (就像你可以用 assign 指令来创建一样。) 的调用者被创建成一个普通变量,名字就是 hash 参数给定的。

  如果你用同一个 path 多次调用 import,它会创建命名空间, 但是只运行第一次 import 的调用。 后面的调用仅仅创建一个哈希表变量,你只是通过它来访问 同一个 命名空间。

  由引入的模板打印的输出内容将会被忽略 (不会在包含它的地方被插入)。模板的执行是来用变量填充命名空间, 而不是写到输出中。

  比如:

1 <#import "/libs/mylib.ftl" as my>
2 
3 <@my.copyright date="1999-2002"/>

  path 参数可以是一个相对路径,比如 "foo.ftl""../foo.ftl",或者是像 "/foo.ftl" 一样的绝对路径。 相对路径是相对于使用 import 指令模板的目录。 绝对路径是程序员配置 FreeMarker 时定义的相对于根路径 (通常指代"模板的根目录")的路径。

  通常使用/(斜杠)来分隔路径组成, 而不是\(反斜杠)。如果你从你本地的文件系统中加载模板, 那么它使用反斜杠(比如在Windows环境下),FreeMarker 将会自动转换它们。

  像 include 指令一样,获得机制 和 本地化查找 也可以用来解决路径问题。

  请注意,对于所有模板来说, 它可以自动做通用的引入操作,使用 Configuration 的"自动引入"设置就行了。

11.include

概要

1 <#include path>
2 3 <#include path options>

这里:

  • path: 要包含文件的路径;一个算作是字符串的表达式。(用其他话说, 它不用是一个固定的字符串,它也可以是像 profile.baseDir + "/menu.ftl"这样的东西。)
  • options: 一个或多个这样的选项: encoding=encoding, parse=parse
    • encoding: 算作是字符串的表达式
    • parse: 算作是布尔值的表达式(为了向下兼容,也接受一部分字符串值)
    • ignore_missing: 算作是布尔值的表达式

描述

  你可以使用它在你的模板中插入另外一个 FreeMarker 模板文件 (由 path 参数指定)。 被包含模板的输出格式是在 include 标签出现的位置插入的。 被包含的文件和包含它的模板共享变量,就像是被复制粘贴进去的一样。 include 指令不能由被包含文件的内容所替代, 它只是当 FreeMarker 每次在模板处理期间到达 include 指令时处理被包含的文件。所以对于如果 includelist 循环之中的例子, 你可以为每个循环周期内指定不同的文件名。

注意:这个指令不能和JSP(Servlet)的include搞混, 因为它不涉及到Servlet容器中,只是处理应外一个FreeMarker模板, 不能"离开"FreeMarker。

  path 参数可以是如 "foo.ftl""../foo.ftl" 一样的相对路径,或者是如 "/foo.ftl" 这样的绝对路径。 相对路径是相对于使用 import 指令的模板文件夹。 绝对路径是相对于程序员在配置 FreeMarker 时定义的基路径 (通常指代"模板的根路径")。

注意:这和 FreeMarker 2.1 版本之前的处理方式不同, 之前的路径通常是绝对路径。为了保留原来的行为, 要在 Configuration 对象中开启经典的兼容模式。

  通常使用 /(斜杠)来分隔路径成分, 而不是 \(反斜杠)。如果你从你本地的文件系统加载模板, 而它使用反斜杠(像Windows操作系统),也要使用 /

  比如:

  假设 /common/copyright.ftl 包含:

Copyright 2001-2002 ${me}<br>
All rights reserved.

  那么:

1 <#assign me = "Juila Smith">
2 <h1>Some testh1>
3 <p>Yeah.
4 <hr>
5 <#include "/common/copyright.ftl">

  将会输出:

<h1>Some testh1>
<p>Yeah.
<hr>
Copyright 2001-2002 Juila Smith
All rights reserved.

  支持的 options 选项有:

  • parse:如果它为 true, 那么被包含的文件将会当作FTL来解析,否则整个文件将被视为简单文本 (也就是说不会在其中查找 FreeMarker 的结构)。如果你忽略了这个选项, 那么它默认是 true

  • encoding:被包含文件从包含它的文件继承的编码方式 (实际就是字符集),除非你用这个选项来指定编码方式。 合法的名字有:ISO-8859-2,UTF-8,Shift_JIS,Big5,EUC-KR,GB2312。 编码名称要和java.io.InputStreamReader 中支持的那些一致(对于Java API 1.3版本:MIME 希望的字符集是从IANA字符集注册处得到的)

  • ignore_missing:当为 true,模板引用为空时压制错误,而 <#include ...> 不会输出任何东西。当为 false 时,如果模板不存在, 那么模板处理就会发生错误并停止。如果忽略这个选项,那么它的默认值是 false

  比如:

<#include "/common/navbar.html" parse=false encoding="Shift_JIS">

  请注意, 对于所有模板可能会用 Configuration 的"自动包含"设置自动处理通用的包含物。

使用获得机制

  有一个特殊的路径组成,是用一个星号(*)来代表的。 它被解释为"当前目录或其他任意它的父目录"。因此, 如果模板在 /foo/bar/template.ftl 位置上,有下面这行:

<#include "*/footer.ftl">

  那么引擎就会在下面的位置上寻找模板,并按这个顺序:

  • /foo/bar/footer.ftl
  • /foo/footer.ftl
  • /footer.ftl

  该机制被称为 acquisition 并允许设计者在父目录中放置通用的被包含的文件, 而且当需要时在每个子路径基础上重新定义它们。 我们说包含它们的模板获得了从包含它的第一个父目录中的模板。请注意, 你不但可以在星号的右面指定一个模板的名字,也可以指定一个子路径。 也就是说,如果前面的模板由下面这个所替代:

<#include "*/commons/footer.ftl">

  那么引擎将会从下面的路径开始寻找模板,并按这个顺序:

  • /foo/bar/commons/footer.ftl
  • /foo/commons/footer.ftl
  • /commons/footer.ftl

  最终,星号不再是路径的第一个元素:

<#include "commons/*/footer.ftl">

  会让引擎将会从下面的路径开始寻找模板,并按这个顺序:

  • /foo/bar/commons/footer.ftl
  • /foo/bar/footer.ftl
  • /foo/footer.ftl
  • /footer.ftl

  然而,在路径中最多只能有一个星号。 指定多余一个星号会导致模板不能被发现。

本地化查找

  本地化是语言和可选的国家或方言标识符 (加上可能的更多变体标识符,比如 "MAC")。 无论何时模板被请求,期望的本地化都会被指定(明确或含蓄), FreeMarker 会试图找到变化的模板来匹配本地化环境。当模板包含或引入其它模板时, 在内部也会被请求一个本地化环境,也就是 locale 配置的本地化, 通常它是顶级模板的本地化设置。

  假设模板使用本地化 en_US 来加载, 就是美国英语。当包含其它模板时:

<#include "footer.ftl">

  引擎实际上就会寻找一些模板,并按照这个顺序:

  • footer_en_US.ftl,
  • footer_en.ftl
  • footer.ftl

  它会使用第一个存在的。

  请注意,如果 FreeMarker 查找的本地化变化是由程序员配置的, 那么这里我们只能描述默认的行为。 可以使用 localized_lookup 设置来禁用本地化查找(Configuration.setLocalizedLookup(boolean))。 而且,可以使用 template_lookup_strategy 设置来自行定义推导出的模板名称序列 (Configuration.setTemplateLookupStrategy(TemplateLookupStrategy))。

  当你同时使用获得机制(也就是路径中的 * 步骤) 和本地化查找时,在父目录中有指定本地化的模板优先于在子目录中有很少本地化的模板。 假设你使用下面的代码来包含 /foo/bar/template.ftl

<#include "*/footer.ftl">

  引擎将会查找这些模板,并按照这个顺序:

  • /foo/bar/footer_en_US.ftl
  • /foo/footer_en_US.ftl
  • /footer_en_US.ftl
  • /foo/bar/footer_en.ftl
  • /foo/footer_en.ftl
  • /footer_en.ftl
  • /foo/bar/footer.ftl
  • /foo/footer.ftl
  • /footer.ftl

12.list, else, items, sep, break

概要

形式 1:

1 <#list sequence as item>
2     Part repeated for each item
3 <#else>
4     Part executed when there are 0 items
5 #list>

这里:

  • else 部分是可选的, 而且仅仅从 FreeMarker 2.3.23 版本开始支持。
  • sequence: 将我们想要迭代的项,算作是序列或集合的表达式
  • item: 循环变量 的名称 (不是表达式)
  • 在标签之间的多个 "parts" 可以是任意的FTL (包括嵌套的 list)

形式 2 (从 FreeMarker 2.3.23 版本开始):

1 <#list sequence>
2     Part executed once if we have more than 0 items
3     <#items as item>
4         Part repeated for each item
5     #items>
6     Part executed once if we have more than 0 items
7 <#else>
8     Part executed when there are 0 items
9 #list>

这里:和上面形式1的 "这里" 部分相同。

描述

最简形式

  假设 users 包含 ['Joe', 'Kate', 'Fred'] 序列:

1 <#list users as user>
2   <p>${user}
3 #list>
4 
5   <p>Joe
6   <p>Kate
7   <p>Fred

  list 指令执行在 list 开始标签和 list 结束标签 ( list 中间的部分) 之间的代码, 对于在序列(或集合)中每个值指定为它的第一个参数。 对于每次迭代,循环变量(本例中的 user)将会存储当前项的值。

循环变量(user) 仅仅存在于 list 标签体内。 而且从循环中调用的宏/函数不会看到它(就像它只是局部变量一样)。

else 指令

注意:list 中的 else 仅从 FreeMarker 2.3.23 版本开始支持。

  当没有迭代项时,才使用 else 指令, 可以输出一些特殊的内容而不只是空在那里:

1 <#list users as user>
2   <p>${user}
3 <#else>
4   <p>No users
5 #list>

  该输出和之前示例是相同的,除了当 users 包含0项时:

  <p>No users

  请注意,循环变量 (user) 在 else 标签和 list 结束标签中间不存在, 因为那部分不是循环中的部分。

  else 必须是真的在 (也就是在源代码中) list 指令体内部。也就是说, 不能将它移出到宏或包含的模板中。

items 指令

注意: items 从 FreeMarker 2.3.23 版本开始存在

  如果不得不在第一列表项之前或在最后一个列表项之后打印一些东西, 那么就要使用 items 指令,但至少要有一项。典型的示例为:

1 <#list users>
2   <ul>
3     <#items as user>
4       <li>${user}li>
5     #items>
6   ul>
7 #list>
  <ul>
      <li>Joeli>
      <li>Kateli>
      <li>Fredli>
  ul>

  如果没有迭代项,那么上面的代码不会输出任何内容, 因此不用以空的

    来结束。

      也就是说,当 list 指令没有 as item 参数, 如果只有一个迭代项,指令体中的代码仅仅执行一次,否则就不执行。 必须内嵌的 items 指令体会对每个迭代项执行, 那么 items 指令使用 as item 定义循环变量,而不是 list

      有 itemslist 指令也可以有 else 指令:

    1 <#list users>
    2   <ul>
    3     <#items as user>
    4       <li>${user}li>
    5     #items>
    6   ul>
    7 <#else>
    8   <p>No users
    9 #list>

      更多细节:

    • 解析器会检查没有 as item 参数的 list 通常会有嵌入的 items 指令,该 items 指令通常会有一个包围的 list,它没有 as item 参数。当模板解析时就会检查,而不是当模板执行的时候。因此,这些规则也适用于FTL源代码本身, 所以不能将 items 移出到宏或者被包含的模板中。

    • list 可以有多个 items 指令, 但是只有一个允许执行(直到不离开或重新进入包围的 list 指令); 之后试图调用 items 会发生错误。所以多个 items 可以用于不同的 if-else 分支中去,但不能迭代两次。

    • items 指令不能有它自己的嵌入 else 指令,只能被包含的 list 可以有。

    • 循环变量 (user) 仅仅存在于 items 指令体内部。

    sep 指令

    注意: sep 从 FreeMarker 2.3.23 版本开始存在。

      当不得不显示介于每个迭代项(但不能在第一项之前或最后一项之后) 之间的一些内容时,可以使用 sep。例如:

    <#list users as user>${user}<#sep>, #list>
    Joe, Kate, Fred

      上面的 <#sep>, <#sep>, 的简写; 如果将它放到被包含的指令关闭的位置时,sep 结束标签可以忽略。下面的示例中,就不能使用该简写 (HTML标签不会结束任何代码,它们只是 FreeMarker 输出的原生文本):

    1 <#list users as user>
    2   <div>
    3     ${user}<#sep>, #sep>
    4   div>
    5 #list>

      sep 是编写 <#if item?has_next>... 的方便形式,有 listitems 循环变量时,它就可以使用,并且不限次数。而且, 也可以有任意的 FTL 作嵌入的内容。

      解析器会检查在 list ... as item 内部使用的 sep 或者 items 指令,所以不能将 sep 从重复的部分移出到宏或被包含的模板中。

    break 指令

      可以使用 break 指令在迭代的任意点退出。例如:

    1 <#list 1..10 as x>
    2   ${x}
    3   <#if x == 3>
    4     <#break>
    5   #if>
    6 #list>
      1
      2
      3

      break 指令可以放在 list 中的任意位置,直到有 as item 参数, 否则,可以放在 items 指令中的任意位置。 如果 breakitems 内部, 那么就只能从 items 开始时存在,而不能从 list 开始时存在。通常来说,break 将仅存在于为每个迭代项调用的指令体中,而且只能存在于这样的指令中。 例如不能在 listelse 部分使用 break,除非 list 内嵌到了其它 可以 break 的指令中。

      像 elseitemsbreak 只能在指令体内部使用,而不能移出到宏或被包含的模板中。

    访问迭代状态

      从 2.3.23 版本开始, 循环变量内建函数 就是访问当前迭代状态的最佳方式。例如,这里我们使用 counteritem_parity 循环变量内建函数(在 循环变量内建函数参考 中查看它们全部):

    1 <#list users>
    2   <table>
    3     <#items as user>
    4       <tr class="${user?item_parity}Row">
    5         <td>${user?counter}
    6         <td>${user}
    7     #items>
    8   table>
    9 #list>
      <table>
          <tr class="oddRow">
            <td>1
            <td>Joe
          <tr class="evenRow">
            <td>2
            <td>Kate
          <tr class="oddRow">
            <td>3
            <td>Fred
      table>

      在 2.3.22 和之前的版本中,有两个额外的循环变量来获得迭代状态 (出于向后兼容考虑,它们仍然存在):

    • item_index (已废弃,由 item?index 代替): 循环中当前项的索引(从0开始的数字)。

    • item_has_next (已废弃,由 item?has_next 代替): 辨别当前项是否是序列的最后一项的布尔值。

      所以在上面的示例中,可以将 ${user?counter} 替换为 ${user_index + 1}

    相互嵌套循环

      很自然地,listitems 可以包含更多 list

    1 <#list 1..2 as i>
    2   <#list 1..3 as j>
    3     i = ${i}, j = ${j}
    4   #list>
    5 #list>
        i = 1, j = 1
        i = 1, j = 2
        i = 1, j = 3
        i = 2, j = 1
        i = 2, j = 2
        i = 2, j = 3

      允许使用冲突的循环变量名称,比如:

    1 <#list 1..2 as i>
    2   Outer: ${i}
    3   <#list 10..12 as i>
    4     Inner: ${i}
    5   #list>
    6   Outer again: ${i}
    7 #list>
      Outer: 1
        Inner: 10
        Inner: 11
        Inner: 12
      Outer again: 1
      Outer: 2
        Inner: 10
        Inner: 11
        Inner: 12
      Outer again: 2

    Java程序员请注意

      如果经典兼容模式下 list 接受标量,并将它视为单元素序列。

      如果传递包装了 java.util.Iterator 的集合到 list 中,那么只能迭代其中的元素一次,因为 Iterator 是它们天然的一次性对象。 当视图再次去迭代这样的集合变量时,会发生错误并中止模板处理。

    13.local

    概要

    1 <#local name=value>
    2 3 <#local name1=value1 name2=value2 ... nameN=valueN>
    4 5 <#local name>
    6   capture this
    7 #local>

    这里:

    • name: 在root中局部对象的名称。它不是一个表达式。但它可以被写作是字符串形式, 如果变量名包含保留字符,这是很有用的,比如 <#local "foo-bar" = 1>。 请注意,这个字符串没有扩展插值(如"${foo}")。
    • =:赋值操作符,也可以简写的赋值操作符之一 (+++= 等...),和 the assign 指令 相似。
    • value: 存储的值,是表达式。

    描述

      它和 assign 指令 类似,但是它创建或替换局部变量。 这仅仅在宏和方法的内部定义才会有作用。

    14.macro, nested, return

    概要

    1 <#macro name param1 param2 ... paramN>
    2   ...
    3   <#nested loopvar1, loopvar2, ..., loopvarN>
    4   ...
    5   <#return>
    6   ...
    7 #macro>

    这里:

    • name: 宏变量的名称,它不是表达式。和 顶层变量 的语法相同,比如 myMacromy\-macro。 然而,它可以被写成字符串的形式,如果宏名称中包含保留字符时,这是很有用的, 比如 <#macro "foo~bar">...。 注意这个字符串没有扩展插值(如 "${foo}")。
    • param1param2,等...: 局部变量 的名称,存储参数的值 (不是表达式),在 = 号后面和默认值(是表达式)是可选的。 默认值也可以是另外一个参数,比如 <#macro section title label=title>。参数名称和 顶层变量 的语法相同,所以有相同的特性和限制。
    • paramN, 最后一个参数,可能会有三个点(...), 这就意味着宏接受可变数量的参数,不匹配其它参数的参数可以作为最后一个参数 (也被称作笼统参数)。当宏被命名参数调用, paramN 将会是包含宏的所有未声明的键/值对的哈希表。当宏被位置参数调用, paramN 将是额外参数的序列。 (在宏内部,要查找参数,可以使用 myCatchAllParam?is_sequence。)
    • loopvar1loopvar2等...: 可选的,循环变量 的值, 是 nested 指令想为嵌套内容创建的。这些都是表达式。

      returnnested 指令是可选的,而且可以在 <#macro ...> 之间被用在任意位置和任意次数。

      没有默认值的参数必须在有默认值参数 (paramName=defaultValue) 之前。

    描述

      创建一个宏变量(在当前命名空间中,如果你知道命名空间的特性)。 如果你对宏和自定义指令不了解,你应该阅读 自定义指令指南。

      宏变量存储模板片段(称为宏定义体)可以被用作 自定义指令。 这个变量也存储自定义指令的被允许的参数名。当你将这个变量作为指令时, 你必须给所有参数赋值,除了有默认值的参数。 默认值当且仅当你调用宏而不给参数赋值时起作用。

      变量会在模板开始时被创建;而不管 macro 指令放置在模板的什么位置。因此,这样也可以:

    <#-- call the macro; the macro variable is already created: -->
    <@test/>
    ...
    
    <#-- create the macro variable: -->
    <#macro test>
      Test text
    #macro>

      然而,如果宏定义被插在 include 指令中, 它们直到 FreeMarker 执行 include 指令时才会可用。

      例如:没有参数的宏:

    1 <#macro test>
    2   Test text
    3 #macro>
    4 <#-- call the macro: -->
    5 <@test/>

      将会输出:

      Test text

      示例:有参数的宏:

    1 <#macro test foo bar baaz>
    2   Test text, and the params: ${foo}, ${bar}, ${baaz}
    3 #macro>
    4 <#-- call the macro: -->
    5 <@test foo="a" bar="b" baaz=5*5-2/>

      将会输出:

      Test text, and the params: a, b, 23

      示例:有参数和默认值参数的宏:

    1 <#macro test foo bar="Bar" baaz=-1>
    2   Test text, and the params: ${foo}, ${bar}, ${baaz}
    3 #macro>
    4 <@test foo="a" bar="b" baaz=5*5-2/>
    5 <@test foo="a" bar="b"/>
    6 <@test foo="a" baaz=5*5-2/>
    7 <@test foo="a"/>

      将会输出:

      Test text, and the params: a, b, 23
      Test text, and the params: a, b, -1
      Test text, and the params: a, Bar, 23
      Test text, and the params: a, Bar, -1

      示例:更为复杂的宏。

    1 <#macro list title items>
    2   <p>${title?cap_first}:
    3   <ul>
    4     <#list items as x>
    5       <li>${x?cap_first}
    6     #list>
    7   ul>
    8 #macro>
    9 <@list items=["mouse", "elephant", "python"] title="Animals"/>

      将会输出:

      <p>Animals:
      <ul>
          <li>Mouse
          <li>Elephant
          <li>Python
      ul>

      示例:支持多个参数和命名参数的宏:

    1 <#macro img src extra...>
    2   <img src="/context${src?html}" 
    3   <#list extra?keys as attr>
    4     ${attr}="${extra[attr]?html}"
    5   #list>
    6   >
    7 #macro>
    8 <@img src="/images/test.png" width=100 height=50 alt="Test"/>

      将会输出:

      <img src="/context/images/test.png"
        alt="Test"
        height="50"
        width="100"
      >

      示例:支持多个位置参数的宏,不管是否使用命名或位置参数传递:

     1 <#macro m a b ext...>
     2   a = ${a}
     3   b = ${b}
     4   <#if ext?is_sequence>
     5     <#list ext as e>
     6       ${e?index} = ${e}
     7     #list>
     8   <#else>
     9     <#list ext?keys as k>
    10       ${k} = ${ext[k]}
    11     #list>
    12   #if>
    13 #macro>
    14 
    15 <@m 1 2 3 4 5 />
    16 
    17 <@m a=1 b=2 c=3 d=4 e=5 data\-foo=6 myns\:bar=7 />

      将会输出:

      a = 1
      b = 2
          0 = 3
          1 = 4
          2 = 5
    
      a = 1
      b = 2
          c = 3
          d = 4
          e = 5
          data-foo=6
          myns:bar=7
    警告:当前,命名的笼统参数是无序的,也就是说,不知道它们枚举时的顺序。 那么它们不会按相同传递顺序返回(上述示例输出相同的顺序只是为了理解)。

    nested

      nested 指令执行自定义指令开始和结束标签中间的模板片段。 嵌套的片段可以包含模板中任意合法的内容:插值,指令等...它在上下文环境中被执行, 也就是宏被调用的地方,而不是宏定义体的上下文中。因此,比如, 你不能看到嵌套部分的宏的局部变量。如果你没有调用 nested 指令, 自定义指令开始和结束标记中的部分将会被忽略。

      比如:

    1 <#macro do_twice>
    2   1. <#nested>
    3   2. <#nested>
    4 #macro>
    5 <@do_twice>something@do_twice>

      将会输出:

      1. something
      2. something

      nested 指令可以对嵌套内容创建循环变量。例如:

    1 <#macro do_thrice>
    2   <#nested 1>
    3   <#nested 2>
    4   <#nested 3>
    5 #macro>
    6 <@do_thrice ; x>
    7   ${x} Anything.
    8 @do_thrice>

      将会输出:

      1 Anything.
      2 Anything.
      3 Anything.

      更为复杂的示例:

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

      将会输出:

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

    return

      使用 return 指令, 你可以在任意位置留下一个宏或函数定义。比如:

    1 <#macro test>
    2   Test text
    3   <#return>
    4   Will not be printed.
    5 #macro>
    6 <@test/>

      将会输出:

      Test text

    15.noparse

    概要

    1 <#noparse>
    2   ...
    3 #noparse>

    描述

      FreeMarker 不会在这个指令体中间寻找FTL标签, 插值和其他特殊的字符序列,除了noparse的结束标记。

      比如:

    1 Example:
    2 --------
    3 
    4 <#noparse>
    5   <#list animals as animal>
    6   <tr><td>${animal.name}<td>${animal.price} Euros
    7   #list>
    8 #noparse>

      将会输出:

    Example:
    --------
    
      <#list animals as animal>
      <tr><td>${animal.name}<td>${animal.price} Euros
      #list>

    16.nt

    概要

    <#nt>

    描述

      "不要削减"。该指令禁用行中出现的 剥离空白。 它也关闭其他同一行中出现的削减指令(trtlt的效果)。

    17.setting

    概要

    <#setting name=value>

    这里:

    • name: 设置的名称。不是表达式!
    • value: 设置的新值,是表达式。

    描述

      为进一步的处理而设置。设置是影响 FreeMarker 行为的值。 新值仅仅在被设置的模板处理时出现,而且不触碰模板本身。 设置的初始值是由程序员设定的 (参考: 程序开发指南/配置(Configuration)/配置设置)。

      支持的设置有:

    • locale:输出的本地化(语言)。 它可以影响数字,日期等显示格式。它的值是由语言编码 (小写两个字母的ISO-639编码)和可选的国家码 (大写的两个字母ISO-3166编码)组成的字符串,它们以下划线相分隔, 如果我们已经指定了国家那么一个可选的不同编码 (不是标准的)会以下划线分隔开国家。合法的值示例:enen_USen_US_MAC。 FreeMarker 会尝试使用特定可用的本地化设置,所以如果你指定了 en_US_MAC,但是它不被知道,那么它会尝试 en_US,然后尝试 en, 然后是计算机(可能是由程序员设置的)默认的本地化设置。

    • number_format: 当没有指定确定的格式化形式时,用来转化数字到字符串形式的数字格式化设置。 可以是下列中的一个预定义值 number(默认的), computercurrency, 或 percent。此外,以Java小数点数字格式化语法书写的任意的格式化形式也可以被指定。 更多格式形式内容: string 内建函数。

    • boolean_format: 以逗号分隔的一对字符串来分别展示 true 和 false 值, 当没有指定确定的格式时(比如在 ${booleanValue} 中), 将转换布尔值到字符串。请注意,当前的空格没有从该字符串中移除, 所以不要将空格放在逗号后面。默认值是 "true,false"。 但是 FreeMarker 会拒绝为 ${booleanValue} 使用特定值,而需要使用 ${booleanValue?c} 来代替(从 2.3.21 版本开始有效)。对于其它任意值,比如 "Y,N"${booleanValue} 也是有效的。 请参考:string 内建函数。

    • date_format, time_format, datetime_format:当没有通过 string 内建函数(或相反)指定确定的格式时,格式将日期/时间/日期-时间值 (Java java.util.Date 和它的子类)转换为字符串, 比如 ${someDate}date_format 设置仅仅格式于存储的无时间部分的值,time_format 仅仅格式于存储无日期部分的值,而 datetime_format 仅仅格式于日期-时间值。除了当它应用于字符串值时,这些设置也影响进行了 ?time?date, 和 ?datetime 操作的格式。

      可能的设置是(引号标记不是值本身的一部分):

      • Java的SimpleDateFormat格式的模式,例如 "dd.MM.yyyy HH:mm:ss" (这里 "HH" 表示 0-23 时) 或 "MM/dd/yyyy hh:mm:ss a" (如果当前语言是英语,这里 "a" 输出 AM 或 PM)。

      • "xs" 就是XML Schema 格式,或 "iso" 是 ISO 8601:2004 格式。 这些格式允许多个可选项,由空格分隔开,比如 "iso m nz" (或者使用 _,比如 "iso_m_nz"; 比如设置 lastModified?string.iso_m_nz 时就很有用)。 选项和它们的意义是:

        • 精度选择:

          • ms:毫秒,通常显示3位数字, 即便它们都是0。例如:13:45:05.800
          • s:秒(如果非0,小数部分就被丢弃了), 比如 13:45:05
          • m:分,比如 13:45。它不允许用于 "xs"
          • h:小时,比如 13。它不允许用于 "xs"
          • 两者皆不:上至毫秒的精度,但是尾部的0毫秒就被移除了, 否则,如果它是0,整个毫秒部分也被移除。比如: 13:45:05.8
        • 时区偏移可见性选项:

          • fz: "Force Zone",通常显示时区偏移(也对 java.sql.Datejava.sql.Time 值)。但是, 因为 ISO 8601 不允许日期(也就是没有时间的日期)显示时区偏移, 该选项对使用 "iso" 的日期就没有作用。
          • nz: "No Zone", 从不显示时区偏移
          • 两者皆不:除了 java.sql.Datejava.sql.Time,还有 "iso" 日期值,通常都显示时区偏移。
        • 时区选项:

          • u:使用 UTC 来代替 time_zone 设置建议的内容。 然而,java.sql.Datejava.sql.Time 不受它影响 (参考 sql_date_and_time_time_zone 去理解为什么)
          • fu: "Force UTC",也就是说,使用 UTC 来代替 time_zonesql_date_and_time_time_zone 设置建议的内容。这会影响 java.sql.Datejava.sql.Time 值。
          • 两者皆不:使用 time_zonesql_date_and_time_time_zone 配置设置项建议的时区。

        来自相同分类的选项是互斥的,比如一起使用 ms 就会有错误。

        选项可以指定一个任意的顺序。

        精度和时区偏移可见性选项不影响解析,只影响格式化。例如, 即使使用 "iso m nz""2012-01-01T15:30:05.125+01" 也会被成功解析含有毫秒精度。仅当解析不包含时区偏移的字符串时, 时区选项(比如 "u") 影响选择的时区。

        使用 "iso" 解析会理解 "extend format" 和 "basic format",比如 20141225T235018。 它不支持所有的 ISO 8601 字符串:如果有日期部分, 必须使用年,月和日值(不是年中的星期),并且日不能被忽略。

        "iso" 的输出是有意的, 所以它也是有 XML Schema 格式值的很好表述,除了0和负数的年, 这里是不可能的。也请注意,时区偏移在 "iso" 格式中是忽略的,而在 "xs" 格式中是保留的。

      • "short""medium""long",或 "full",这些由Java平台定义,有本地依赖含义。 对于日期-时间值,可以分别指定日期和时间部分的长度,使用 _ 将它们分开,比如 "short_medium"。 (对于时间-日期值,"medium" 表示 "medium_medium"。)

    • time_zone:时区的名称来显示并格式化时间。 默认情况下,使用JVM的时区。也可以是Java时区API接受的值,或者 "JVM default" (从 FreeMarker 2.3.21 版本开始) 使用JVM默认的时区。比如: "GMT""GMT+2""GMT-1:30""CET""PST""America/Los_Angeles"

      警告:如果修改了该设置的默认值,那么也应该设置 sql_date_and_time_time_zone 为 "JVM default"。 Configurable.setSQLDateAndTimeTimeZone(TimeZone) 的Java API 文档中参考更多内容。
    • sql_date_and_time_time_zone (从 FreeMarker 2.3.21 版本开始):它控制高度技术性的问题, 所以它应该由程序员在Java代码中来设置。 对于程序员:如果它设置为 非 null,对于来自SQL数据库(更精确地,就是 java.sql.Datejava.sql.Time 对象)的仅日期和仅时间值来说, FreeMarker 会使用该时区来代替由 time_zone 设置项指定的时区。在 Configurable.setSQLDateAndTimeTimeZone(TimeZone) 的Java API文档中参考更多。

    • url_escaping_charset: 用来URL转义(比如${foo?url})的字符集, 来计算转义(%XX)的部分。 通常包含 FreeMarker 的框架应该设置它,所以不应该在模板中来设置。(程序员可以在 这里... 阅读更多内容。)

    • output_encoding:告诉 FreeMarker 输出的字符集是什么。因为 FreeMarker 输出 UNICODE 字符集 (写入 java.io.Writer)的流,它不由输出编码所影响, 但是一些宏/函数和内建函数也许想使用这些信息。

    • classic_compatible:这是对于专家来说的。 它的值应该是布尔值。参考 freemarker.template.Configurable 的文档来获取更多信息。

       比如:假设初始化的模板本地化是 de_DE (德国)。那么:

    1 ${1.2}
    2 <#setting locale="en_US">
    3 ${1.2}

      将会输出:

    1,2
    1.2

      因为德国人使用逗号作为小数分隔符,而美国人使用点。

    18.stop

    概要

    1 <#stop>
    2 3 <#stop reason>

    这里:

    • reason: 关于终止原因的信息化消息。是表达式,被算做是字符串。

    描述

      中止模板处理,给出(可选的)错误消息。 不要在普通情况下对结束模板处理使用! FreeMarker 模板的调用者会将它视为失败的模板呈现, 而不是普通完成的。

      该指令抛出 StopException,而且 StopException 会持有 reason 参数的值。

    19.switch, case, default, break

    概要

     1 <#switch value>
     2   <#case refValue1>
     3     ...
     4     <#break>
     5   <#case refValue2>
     6     ...
     7     <#break>
     8   ...
     9   <#case refValueN>
    10     ...
    11     <#break>
    12   <#default>
    13     ...
    14 #switch>

    这里:

    • valuerefValue1,等: 表达式将会计算成相同类型的标量。

    breakdefault 是可选的。

    描述

      这个指令的用法是不推荐的,因为向下通过的行为容易出错。使用 elseif来代替, 除非你想利用向下通过这种行为。

      Switch 被用来选择模板中的一个片段,如何选择依赖于表达式的值:

     1 <#switch animal.size>
     2   <#case "small">
     3      This will be processed if it is small
     4      <#break>
     5   <#case "medium">
     6      This will be processed if it is medium
     7      <#break>
     8   <#case "large">
     9      This will be processed if it is large
    10      <#break>
    11   <#default>
    12      This will be processed if it is neither
    13 #switch>

      在 switch 中间必须有一个或多个 <#case value>, 在所有 case 标签之后,有一个可选的 <#default>。 当FreeMarker到达 switch 指令时,它会选择一个 case 指令,这里的 refValue 等于 value 并且继续模板处理。 如果没有和合适的值匹配的 case 指令,那么就继续处理 default 指令,如果它存在,否则就继续处理 switch 结束标签之后的内容。现在有一个混乱的事情: 当它选择一个 case 指令后,它就会继续处理其中的内容, 直到遇到 break 指令。也就是它遇到另外一个 case 指令或 <#default> 标记时也不会自动离开 switch 指令。比如:

    1 <#switch x>
    2   <#case 1>
    3     1
    4   <#case 2>
    5     2
    6   <#default>
    7     d
    8 #switch>

      如果 x 是 1,它会打印1 2 d;如果 x 是 2,那么就会打印2 d;如果 x 是 3,那么就会打印d。这就是前面提到的向下通过行为。 break 标记指示 FreeMarker 直接略过剩下的 switch 代码段。

    20.t, lt, rt

    概要

    <#t>
    
    <#lt>
    
    <#rt>

    描述

      这些指令,指示FreeMarker去忽略标记中行的特定的空白:

    • t (整体削减):忽略本行中首和尾的所有空白。

    • lt (左侧削减):忽略本行中首部所有的空白。

    • rt (右侧削减):忽略本行中尾部所有的空白。

      这里:

    • "首部空白" 表示本行所有空格和制表符 (和其他根据 UNICODE 中的空白字符,除了换行符) 在第一个非空白字符之前。

    • "尾部空白" 表示本行所有的空格和制表符 (和其他根据 UNICODE 中的空白字符,除了换行符) 在最后一个非空白字符之后,还有 行末尾的换行符。

      理解这些检查模板本身的指令是很重要的,而 不是 当你合并数据模型时,模板生成的输出。 (也就是说,空白的移除发生在解析阶段。)

      例如:

    1 --
    2   1 <#t>
    3   2<#t>
    4   3<#lt>
    5   4
    6   5<#rt>
    7   6
    8 --

      将会输出:

    --
    1 23
      4
      5  6
    --

      这些指令在行内的放置不重要。也就是说,不管你是将它们放在行的开头, 或是行的末尾,或是在行的中间,效果都是一样的。

    21.User-defined directive (<@...>)

    概要

     1 <@user_def_dir_exp param1=val1 param2=val2 ... paramN=valN/>
     2 (注意 XML 风格, / 在 > 之前)  
     3 或如果需要循环变量 (更多细节...)
     4 <@user_def_dir_exp param1=val1 param2=val2 ... paramN=valN ; lv1, lv2, ..., lvN/>
     5 
     6 或和上面两个相同但是使用结束标签 (更多细节...):
     7 
     8 <@user_def_dir_exp ...>
     9   ...
    10 @user_def_dir_exp>
    11 12 <@user_def_dir_exp ...>
    13   ...
    14 @>
    15 
    16 或和上面的相同但是使用位置参数传递 (更多细节...):
    17 
    18 <@user val1, val2, ..., valN/>
    19 等...

    这里:

    • user_def_dir_exp: 表达式算作是自定义指令(比如宏),将会被调用。
    • param1param2等...: 参数的名称,它们 不是 表达式。
    • val1val2等...: 参数的值,它们 表达式。
    • lv1lv2等...: 循环变量 的名称, 它们 不是 表达式。

      参数的数量可以是0(也就是没有参数)。

      参数的顺序并不重要(除非你使用了位置参数传递)。 参数名称必须唯一。在参数名中小写和大写的字母被认为是不同的字母 (也就是 Colorcolor 是不同的)。

    描述

      这将调用用户自定义指令,比如宏。参数的含义, 支持和需要的参数的设置依赖于具体的自定义指令。

      示例1:调用存储在变量 html_escape 中的指令:

    1 <@html_escape>
    2   a < b
    3   Romeo & Juliet
    4 >

      将会输出:

     a < b
      Romeo & Juliet

      示例2:调用有参数的宏:

     1 <@list items=["mouse", "elephant", "python"] title="Animals"/>
     2 ...
     3 <#macro list title items>
     4   <p>${title?cap_first}:
     5   <ul>
     6     <#list items as x>
     7       <li>${x?cap_first}
     8     #list>
     9   ul>
    10 #macro>

      将会输出:

      <p>Animals:
      <ul>
          <li>Mouse
          <li>Elephant
          <li>Python
      ul>
      
    ...

    结束标签

      你可以在 结束标签 中忽略 user_def_dir_exp。 也就是说,你可以写 来替代 anything>。 这个规则当表达式 user_def_dir_exp 太复杂时非常有用,因为你不需要在结束标签中重复表达式。 此外,如果表达式包含比简单变量名和点还多的表达式,你就不能再重复它们了。比如, <@a_hash[a_method()]>... 就是错的,你必须写为 <@a_hash[a_method()]>...。 但是 <@a_hash.foo>... 是可以的。

    循环变量

      一些自定义指令创建循环变量(和 list 指令相似)。 正如预定义指令(如 list)一样,当你调用这个指令 (如 <#list foos as foo>...中的 foo)时循环变量的 名称 就给定了, 而变量的 是由指令本身设置的。 在自定义指令的情形下,语法是循环变量的名称在分号之后给定。比如:

    <@myRepeatMacro count=4 ; x, last>
      ${x}. Something... <#if last> This was the last!#if>
    @myRepeatMacro>

      请注意,由自定义指令创建的循环变量数量和分号之后指定的循环变量数量需要不匹配。 也就是说,如果你对重复是否是最后一个不感兴趣,你可以简单来写:

    <@myRepeatMacro count=4 ; x>
      ${x}. Something...
    @myRepeatMacro>

      或者你可以:

    <@myRepeatMacro count=4>
      Something...
    @myRepeatMacro>

      此外,如果你在分号之后指定更多循环变量而不是自定义指令创建的, 也不会引起错误,只是最后的循环变量不能被创建 (也就是在嵌套内容中那些将是未定义的)。尝试使用未定义的循环变量, 就会引起错误(除非你使用如 ?default 这样的内建函数), 因为你尝试访问了一个不存在的变量。

    位置参数传递

      位置参数传递(如<@heading "Preface", 1/>) 是正常命名参数传递(如<@heading title="Preface" level=1/>) 的速记形式,这里忽略了参数的名称。如果自定义指令只有一个参数, 或者对于经常使用的自定义指令它参数的顺序很好记忆,速记形式应该被应用。 为了应用这种形式,你不得不了解声明的命名参数的顺序(如果指令只有一个参数这是很琐碎的)。 也就是,如果 heading 被创建为 <#macro heading title level>..., 那么 <@heading "Preface", 1/><@heading title="Preface" level=1/> (或 <@heading level=1 title="Preface"/>; 如果你使用参数名称,那顺序就不重要了)是相等的。 要注意位置参数传递现在仅仅支持宏定义。

    22.visit, recurse, fallback

    概要

     1 <#visit node using namespace>
     2  3 <#visit node>
     4 
     5 <#recurse node using namespace>
     6  7 <#recurse node>
     8  9 <#recurse using namespace>
    10 11 <#recurse>
    12 
    13 <#fallback>

    这里:

    • node: 算作 结点变量 的表达式。
    • namespace: 命名空间,或者是命名空间的序列。 命名空间可以以命名空间哈希表(又称为根哈希表)给定, 或者可以引入一个存储模板路径的字符串。代替命名空间哈希表, 你也可以使用普通哈希表。

    描述

      visitrecurse 指令是用来递归处理树的。在实践中,这通常被用来 处理XML。

    Visit

      当你调用了 <#visit node>时, 它看上去像用户自定义指令(比如宏)来调用从结点名称 (node?node_name) 和命名空间 (node?node_namesoace) 中有名称扣除的结点。名称扣除的规则:

    • 如果结点不支持结点命名空间(如XML中的文本结点), 那么这个指令名仅仅是结点的名称 (node?node_name)。 如果 getNodeNamespace 方法返回 null 时结点就不支持结点命名空间了。

    • 如果结点支持结点命名空间(如XML中的元素结点), 那么从结点命名空间中的前缀扣除可能在结点名称前和一个做为分隔符 (比如 e:book)的冒号追加上去。前缀,以及是否使用前缀, 依赖于何种前缀 FTL命名空间 中用 ftl 指令的 ns_prefixes 参数注册的, 那里 visit 寻找控制器指令 (visit 调用的相同FTL命名空间不是重要的,后面你将会看到)。 具体来说,如果没有用 ns_prefixes 注册默认的命名空间, 那么对于不属于任何命名空间(getNodeNamespace 返回 "")的结点来说就不使用前缀。 如果使用 ns_prefixes 给不属于任意命名空间的结点注册了默认命名空间, 那么就使用前缀 N,而对于属于默认结点命名空间的结点就不使用前缀了。 否则,这两种情况下,用 ns_prefixes 关联结点命名空间的前缀已经被使用了。 如果没有关联结点命名空间的结点前缀,那么 visit 仅仅就好像没有以合适的名称发现指令。

      自定义指令调用的结点对于特殊变量 .node 是可用的。比如:

    1 <#-- Assume that nodeWithNameX?node_name is "x" -->
    2 <#visit nodeWithNameX>
    3 Done.
    4 <#macro x>
    5    Now I'm handling a node that has the name "x".
    6    Just to show how to access this node: this node has ${.node?children?size} children.
    7 #macro>

      将会输出:

     Now I'm handling a node that has the name "x".
       Just to show how to access this node: this node has 3 children.
    Done.

      如果使用可选的 using 从句来指定一个或多个命名空间, 那么 visit 就会在那么命名空间中寻找指令, 和先前列表中指定的命名空间都获得优先级。如果指定 using 从句, 对最后一个未完成的 visit 调用的用 using 从句指定命名空间的命名空间或序列被重用了。如果没有这样挂起的 visit 调用,那么当前的命名空间就被使用。 比如,如果你执行这个模板:

     1 <#import "n1.ftl" as n1>
     2 <#import "n2.ftl" as n2>
     3 
     4 <#-- This will call n2.x (because there is no n1.x): -->
     5 <#visit nodeWithNameX using [n1, n2]>
     6 
     7 <#-- This will call the x of the current namespace: -->
     8 <#visit nodeWithNameX>
     9 
    10 <#macro x>
    11   Simply x
    12 #macro>

      这是 n1.ftl

    1 <#macro y>
    2   n1.y
    3 #macro>

      这是 n2.ftl

     1 <#macro x>
     2   n2.x
     3   <#-- This callc n1.y as it inherits the "using [n1, n2]" from the pending visit call: -->
     4   <#visit nodeWithNameY>
     5   <#-- This will call n2.y: -->
     6   <#visit nodeWithNameY using .namespace>
     7 #macro>
     8 
     9 <#macro y>
    10   n2.y
    11 #macro>

    将会输出:

      n2.x
      n1.y
      n2.y
    
      Simply x

      如果 visit 既没有在和之前描述规则的名称扣除相同名字的FTL命名空间发现自定义指令, 那么它会尝试用名称 @node_type 查找, 又如果结点不支持结点类型属性 (也就是 node?node_type 返回未定义变量), 那么使用名称 @default。对于查找来说,它使用和之前描述相同的机制。 如果仍然没有找到处理结点的自定义指令,那么 visit 停止模板执行, 并抛出错误。一些XML特定的结点类型在这方面有特殊的处理; 参考:XML处理指南/声明的XML处理/具体细节。比如:

     1 <#-- Assume that nodeWithNameX?node_name is "x" -->
     2 <#visit nodeWithNameX>
     3 
     4 <#-- Assume that nodeWithNameY?node_type is "foo" -->
     5 <#visit nodeWithNameY>
     6 
     7 <#macro x>
     8 Handling node x
     9 #macro>
    10 
    11 <#macro @foo>
    12 There was no specific handler for node ${node?node_name}
    13 #macro>

      将会输出:

    Handling node x
      
    There was no specific handler for node y

    Recurse

      <#recurse> 指令是真正纯语义上的指令。 它访问结点的所有子结点(而没有结点本身)。所以来写:

    <#recurse someNode using someLib>

      和这个是相等的:

    <#list someNode?children as child><#visit child using someLib>#list>

      而目标结点在 recurse 指令中是可选的。 如果目标结点没有指定,那就仅仅使用 .node。 因此,<#recurse> 这个精炼的指令和下面这个是相同的。

    <#list .node?children as child><#visit child>#list>

      对于熟悉XSLT的用户的评论,<#recurse> 是和XSLT中 指令相当类似的。

    Fallback

      正如前面所学的,在 visit 指令的文档中, 自定义指令控制的结点也许在多个FTL命名空间中被搜索。 fallback 指令可以被用在自定义指令中被调用处理结点。 它指挥 FreeMarker 在更多的命名空间 (也就是,在当前调用列表中自定义指令命名空间之后的命名空间) 中来继续搜索自定义指令。如果结点处理器被发现, 那么就被调用,否则 fallback 不会做任何事情。

      这个指令的典型用法是在处理程序库之上写定制层,有时传递控制到定制的库中:

     1 <#import "/lib/docbook.ftl" as docbook>
     2 
     3 <#--
     4   We use the docbook library, but we override some handlers
     5   in this namespace.
     6 -->
     7 <#visit document using [.namespace, docbook]>
     8 
     9 <#--
    10   Override the "programlisting" handler, but only in the case if
    11   its "role" attribute is "java"
    12 -->
    13 <#macro programlisting>
    14   <#if .node.@role[0]!"" == "java">
    15     <#-- Do something special here... -->
    16     ...
    17   <#else>
    18     <#-- Just use the original (overidden) handler -->
    19     <#fallback>
    20   #if>
    21 #macro>

     

    译自 Email: ddekany at users.sourceforge.net

    转载于:https://www.cnblogs.com/fx-blog/p/6296462.html

    你可能感兴趣的:(freeMarker(十一)——模板语言之指令)