freemarker指令(九)

Freemarker指令

如果你没有在这里发现模板中的指令,可能你需要在废弃的 FTL 结构中来查找它了。

2.1 if ,else ,elseif  指令

2.1.1  概要

<#if condition>

...

<#elseif condition2>

...

<#elseif condition3>

...

...

<#else>

...

</#if>

这里:

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

2.1.2  描述

你可以使用 if , elseif 和 else 指令来条件判断是否越过模板的一个部分。这些

condition -s 必须计算成布尔值,否则错误将会中止模板处理。 elseif -s 和 else -s

必须出现在 if 的内部(也就是,在 if 的开始标签和技术标签之间)。 if 中可以包含任意

数量的 elseif -s(包括 0 个)而且结束时 else 是可选的。

比如:

只有 if ,没有 elseif 和 else :

<#if x == 1>

x is 1

</#if>

只有 if 和 else ,没有 elseif :

<#if x == 1>

x is 1

<#else>

x is not 1

</#if>

if 和两个 elseif ,没有 else :

<#if x == 1>

x is 1

<#elseif x == 2>

x is 2

<#elseif x == 3>

x is 3

</#if>

if 和 3 个 elseif ,还有 else :

<#if x == 1>

x is 1

<#elseif x == 2>

x is 2

<#elseif x == 3>

x is 3

<#elseif x == 4>

x is 4

<#else>

x is not 1 nor 2 nor 3 nor 4

</#if>

要了解更多布尔表达式,可以参考:模板开发指南/模板/表达式部分内容。

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

<#if x == 1>

x is 1

<#if y == 1>

and y is 1 too

<#else>

but y is not

</#if>

<#else>

x is not 1

<#if y < 0>

and y is less than 0

</#if>

</#if>

注意:

如何测试 x 比 1 大? <#if x > 1> 是不对的,因为 FreeMarker 将会解释第一个 > 作

为结束标记。因此,编写 <#if (x > 1)> 或 <#if x &gt;1> 是正确的。

2.2 switch ,case ,default ,break  指令

2.2.1  概要

<#switch value>

<#case refValue1>

...

<#break>

<#case refValue2>

...

<#break>

...

<#case refValueN>

...

<#break>

<#default>

...

</#switch>

这里:

  value , refValue1 等:表达式将会计算成相同类型的标量。

2.2.2  描述

这个指令的用法是不推荐的,因为向下通过的行为容易出错。使用 elseif -s 来代替,

除非你想利用向下通过这种行为。

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

<#switch being.size>

<#case "small">

This will be processed if it is small

<#break>

<#case "medium">

This will be processed if it is medium

<#break>

<#case "large">

This will be processed if it is large

<#break>

<#default>

This will be processed if it is neither

</#switch>

在 switch 中间必须有一个或多个 <#case value> ,在所有 case 标签之后,有

一个可选的 <#default> 。当 FreeMarker 到达指令时,它会选择一个 refValue 和

value 相等的 case 指令来继续处理模板。如果没有和合适的值匹配的 case 指令,如

果 default 指令存在,那么就会处理 default 指令,否则就会继续处理 switch 结

束标签之后的内容。现在有一个混乱的事情:当它选择一个 case 指令后,它就会继续处

理 case 指令中的内容,直到遇到 break 指令。也就是它遇到另外一个 case 指令或

<#default> 标记时也不会自动离开switch 指令。比如:

<#switch x>

<#case x = 1>

1

<#case x = 2>

2

<#default>

d

</#switch>

如果 x 是 1,那么它会打印 1 2 d;如果 x 是 2,那么就会打印 2 d;如果 x 是 3,那么

它会打印 d。这就是前面提到的向下通过行为。 break 标记指示 FreeMarker 直接略过剩下

的 switch 代码段。

2.3 list ,break  指令

2.3.1  概要

<#list sequence as item>

...

</#list>

这里:

  sequence :表达式将被算作序列或集合

  item :循环变量(不是表达式)的名称

2.3.2  描述

你可以使用 list 指令来处理模板的一个部分中的一个序列中包含的各个变量。在开

始标签和结束标签中的代码将会被处理,首先是第一个子变量,然后是第二个子变量,接着

是第三个子变量,等等,直到超过最后一个。对于每个变量,这样的迭代中循环变量将会包

含当前的子变量。

在 list 循环中,有两个特殊的循环变量可用:

  item_index :这是一个包含当前项在循环中的步进索引的数值。

  item_has_next :来辨别当前项是否是序列的最后一项的布尔值。

示例 1:

<#assign seq = ["winter","spring", "summer", "autumn"]>

<#list seq as x>

${x_index + 1}. ${x}<#ifx_has_next>,</#if>

</#list>

将会打印:

1. winter,

2. spring,

3. summer,

4. autumn

示例 2:你可以使用 list 在两个数字中来计数,使用一个数字范围序列表达式:

<#assign x=3>

<#list 1..x as i>

${i}

</#list>

输出是:

1

2

3

注意上面的示例在你希望 x 是 0 的时候不会有作用,那么它打印 0 和-1。

你可以使用 break 指令在它通过最后一个序列的子变量之前离开 list 循环。比如

这会仅仅打印“winter”和“spring”。

<#list seq as x>

${x}

<#if x ="spring"><#break></#if>

</#list>

通常来说,避免 list 中使用无论何时可能包装了 Iterator 作为参数的集合和使

用包装了 java.util.Collection 或序列的集合是最好的。但是在某些情况,当你

处理时仅仅有一个 Iterator 。要注意如果你传递了一个包装了 Iterator 的集合给

list ,你仅仅可以迭代一次元素,因为Iterator s 是由它们一次性对象的特性决定的。

当你尝试第二次列出这样一个集合变量时,错误会中止模板的处理。

2.4 include  指令

2.4.1  概要

<#include path>

or

<#include path options>

这里:

  path :要包含文件的路径;一个算作是字符串的表达式。(用其他话说,它不用

是一个固定的字符串,它也可以是像 profile.baseDir + "/menu.ftl"

这样的东西。)

1. winter,

2. spring,

3. summer,

4. autumn

<#assign x=3>

<#list 1..x as i>

${i}

</#list>

1

2

3

<#list seq as x>

${x}

<#if x ="spring"><#break></#if>

</#list>

  options :一个或多个这样的选项:encoding=encoding ,  parse=parse

  encoding :算作是字符串的表达式

  parse :算作是布尔值的表达式(为了向下兼容,也接受一部分字符串值)

2.4.2  描述

你可以使用它在你的模板中插入另外一个 FreeMarker 模板文件(由 path 参数指定)。

被包含模板的输出格式是在 include 标签出现的位置插入的。被包含的文件和包含它的

模板共享变量,就像是被复制粘贴进去的一样。 include 指令不能由被包含文件的内容

所替代,它只是当 FreeMarker 每次在模板处理期间到达 include 指令时处理被包含的文

件。所以对于如果 include 在 list 循环之中的例子,你可以为每个循环周期内指定不

同的文件名。

注意:

这个指令不能和 JSP(Servlet)的 include 搞混,因为它不涉及到 Servlet 容器中,只是

处理应外一个 FreeMarker 模板,不能“离开”FreeMarker。关于如何处理“JSP include”,

可以参考 FAQ 中的内容。

path 参数可以是如"foo.ftl" 和 "../foo.ftl" 一样的相对路径,或者是如

"/foo.ftl" 这样的绝对路径。相对路径是相对于使用 import 指令的模板文件夹。绝

对路径是相对于程序员在配置 FreeMarker 时定义的基路径(通常指代“模板的根路径”)。

注意:

这和 FreeMarker 2.1 版本之前的处理方式不同,之前的路径通常是绝对路径。为了保留

原来的行为,要在 Configuration 对象中开启经典的兼容模式。

通常使用/(斜杠)来分隔路径成分,而不是\(反斜杠)。如果你从你本地的文件系统

加载模板,要使用反斜杠(像 Windows 操作系统)。FreeMarker 会自动转换它们。

比如:

假设/common/copyright.ftl 包含:

Copyright 2001-2002 ${me}<br>

All rights reserved.

那么这个:

<#assign me = "JuilaSmith">

<h1>Some test</h1>

<p>Yeah.

<hr>

<#include"/common/copyright.ftl">

会打印出:

<h1>Some test</h1>

<p>Yeah.

<hr>

Copyright 2001-2002 Juila Smith

All rights reserved.

支持的 options 选项有:

  parse:如果它为真,那么被包含的文件将会当作FTL 来解析,否则整个文件将被

视为简单文本(也就是说不会在其中查找 FreeMarker 的结构)。如果你忽略了这个

选项,那么它默认是 true。

  encoding:被包含文件从包含它的文件继承的编码方式(实际就是字符集),除非

你用这个选项来指定编码方式。编码名称要和 java.io.InputStreamReader 中支持的

那些一致(对于 Java API 1.3 版本: MIME  希望的字符集是从 IANA字符集注册处得

到的)。合法的名字有:ISO-8859-2,UTF-8,Shift_JIS,Big5,EUC-KR,GB2312。

比如:

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

encoding="Shift_JIS">

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

的包含物。

2.4.2.1  使用获得 机制

有一个特殊的路径组成,是用一个星号( * )来代表的。它被解释为“当前目录或其他

任意它的父目录”。因此,如果模板在 /foo/bar/template.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

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

2.4.2.2  本地化查找

无论何时模板被加载,它都被分配了一个本地化环境。本地化环境是语言和可选的国家

或方言标识。模板一般是由程序员编写一些代码来加载的,出于一些方面的考虑,程序员为

模板选择一种本地化环境。比如:当 FreemarkerServlet 加载模板时,它经常用本

地化环境匹配浏览器请求 Web 页面的语言偏好来请求模板。

当一个模板包含另一个模板时,它试图加载以相同的本地化环境加载模板。假定你的模

板以本地化 en_US 来加载,那就意味着是 U.S. English。当你包含另外一个模板:

<include "footer.ftl">

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

 footer_en_US.ftl

 footer_en.ftl

  footer.ftl

要注意你可以使用 Configuration 的 setLocalizedLookup 方法关闭本地化查找特性。

当你同时使用获得机制和本地化查找时,在父目录中有指定本地化的模板优先于在子目

录中有很少本地化的模板。假设你使用下面的代码来包含 /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

2.5 import  指令

2.5.1  概要

<#import path as hash>

这里:

  path :模板的路径。这是一个算作是字符串的表达式。(换句话说,它不是一个固

定的字符串,它可以是这样的一些东西,比如, profile.baseDir +

"/menu.ftl" 。)

  hash :哈希表变量的结束名称,你可以由它来访问命名空间。这不是表达式。

2.5.2  描述

引入一个库。也就是说,它创建一个新的命名空间,然后在那个命名空间中执行给定

path 参数中的模板,所以模板用变量(宏,函数等)填充命名空间。然后使得新创建的命

名空间对哈希表的调用者可用。这个哈希表变量将会在命名空间中,由 import (就像你

可以用 assign 指令来创建一样。)的调用者被创建成一个普通变量,名字就是 hash 参

<include "footer.ftl">

<include "*/footer.ftl">

数给定的。

如果你用同一个 path 多次调用 import ,它会创建命名空间,但是只运行第一次

import 的调用。后面的调用仅仅创建一个哈希表变量,你只是通过它来访问同一个命名

空间。

由引入的模板打印的输出内容将会被忽略(不会在包含它的地方被插入)。模板的执行

是来用变量填充命名空间,而不是写到输出中。

例如:

<#import "/libs/mylib.ftl" asmy>

<@my.copyrightdate="1999-2002"/>

path 参数可以是一个相对路径,比如"foo.ftl" 和 "../foo.ftl" ,或者是像

"/foo.ftl" 一样的绝对路径。相对路径是相对于使用 import 指令模板的目录。绝对

路径是程序员配置 FreeMarker 时定义的相对于根路径(通常指代“模板的根目录”)的路径。

通常使用 / (斜杠)来分隔路径组成,而不是 \ (反斜杠)。如果你从你本地的文件系统

中加载模板,那么它使用反斜杠(比如在 Windows 环境下),FreeMarker 将会自动转换它们。

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

注意,对于所有模板来说,它可以自动做通用的引入操作,使用 Configuration

的“自动引入”设置就行了。

如果你命名空间不是很了解,你应该阅读:模板开发指南/其他/命名空间部分的内容。

2.6 noparse  指令

2.6.1  概要

<#noparse>

...

</#noparse>

2.6.2  描述

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

noparse 的结束标记。

例如:

<#import "/libs/mylib.ftl" asmy>

<@my.copyrightdate="1999-2002"/>

Example:

--------

<#noparse>

<#list animals as being>

<tr><td>${being.name}<td>${being.price}Euros

</#list>

</#noparse>

将会输出:

Example:

--------

<#list animals as being>

<tr><td>${being.name}<td>${being.price}Euros

</#list>

2.7 compress  指令

2.7.1  概要

<#compress>

...

</#compress>

2.7.2  描述

当你使用了对空白不敏感的格式(比如 HTML 或 XML)时压缩指令对于移除多余的空白

是很有用的。它捕捉在指令体(也就是在开始标签和结束标签中)中生成的内容,然后缩小

所有不间断的空白序列到一个单独的空白字符。如果被替代的序列包含换行符或是一段空间,

那么被插入的字符也会是一个换行符。开头和结尾的不间断的空白序列将会完全被移除。

<#assign x = " moo \n\n ">

(<#compress>

1 2 3 4 5

${moo}

test only

I said, test only

</#compress>)

会输出:

(1 2 3 4 5

moo

test only

I said, test only)

2.8 escape ,noescape  指令

2.8.1  概要

<#escape identifier as expression>

...

<#noescape>...</#noescape>

...

</#escape>

2.8.2  描述

当你使用 escape 指令包围模板中的一部分时,在块中出现的插值( ${...} )会和

转义表达式自动结合。这是一个避免编写相似表达式的很方便的方法。它不会影响在字符串

形式的插值(比如在 <#assign x = "Hello ${user}!"> )。而且,它也不会影

响数值插值( #{...} )。

例如:

<#escape x as x?html>

First name: ${firstName}

Last name: ${lastName}

Maiden name: ${maidenName}

</#escape>

事实上它等同于:

First name: ${firstName?html}

Last name: ${lastName?html}

Maiden name: ${maidenName?html}

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

数。

当你在 include 指令中调用宏时, 理解 在模 板文 本中转 义仅 仅对 出现在

<#escape ...> 和</#escape> 中的插值起作用是很重要的。也就是说,它不会转义

文本中 <#escape ...> 之前的东西或 </#escape> 之后的东西,也不会从 escape -d

部分中来调用。

<#assign x ="<test>">

<#macro m1>

m1: ${x}

</#macro>

<#escape x as x?html>

<#macro m2>m2: ${x}</#macro>

${x}

<@m1/>

</#escape>

${x}

<@m2/>

输出将是:

&lt;test&gt;

m1: <test>

<test>

m2: &lt;test&gt;

从更深的技术上说, escape 指令的作用是用在模板解析的时间而不是模板处理的时

间。这就表示如果你调用一个宏或从一个转义块中包含另外一个模板,它不会影响在宏 / 被

包含模板中的插值,因为宏调用和模板包含被算在模板处理时间。另外一方面,如果你用一

个转义区块包围一个或多个宏声明(算在模板解析时间,和宏调用想法),那么那些宏中的

插值将会和转义表达式合并。

有时需要暂时为一个或两个在转义区块中的插值关闭转义。你可以通过关闭,过后再重

新开启转义区块来达到这个功能,但是那么你不得不编写两遍转义表达式。你可以使用非转

义指令来替代:

<#escape x as x?html>

From: ${mailMessage.From}

Subject: ${mailMessage.Subject}

<#noescape>Message:

${mailMessage.htmlFormattedBody}</#noescape>

...

</#escape>

和这个是等同的:

From: ${mailMessage.From?html}

Subject: ${mailMessage.Subject?html}

Message: ${mailMessage.htmlFormattedBody}

...

转义可以被嵌套(尽管你不会在罕见的情况下来做)。因此,你可以编写如下面代码(这

个例子固然是有点伸展的,正如你可能会使用 list 来迭代序列中的每一项,但是我们现

在所做的是阐述这个观点)的东西:

<#escape x as x?html>

Customer Name: ${customerName}

Items to ship:

<#escape x as itemCodeToNameMap[x]>

${itemCode1}

${itemCode2}

${itemCode3}

${itemCode4}

</#escape>

</#escape>

实际上和下面是等同的:

Customer Name: ${customerName?html}

Items to ship:

${itemCodeToNameMap[itemCode1]?html}

${itemCodeToNameMap[itemCode2]?html}

${itemCodeToNameMap[itemCode3]?html}

${itemCodeToNameMap[itemCode4]?html}

当你在嵌入的转义区块内使用非转义指令时,它仅仅不处理一个单独层级的转义。因此,

为了在两级深的转义区块内完全关闭转义,你需要使用两个嵌套的非转义指令。

2.9 assign  指令

2.9.1  概要

<#assign name=value>

or

<#assign name1=value1 name2=value2 ...nameN=valueN>

or

<#assign same as above... in namespacehash>

or

<#assign name>

capture this

</#assign>

or

<#assign name in namespacehash>

capture this

</#assign>

这里:

  name :变量的名字。不是表达式。而它可以本写作是字符串,如果变量名包含保

留字符这是很有用的,比如 <#assign "foo-bar" = 1> 。注意这个字符串

没有展开插值(如 "${foo}" )。

  value :存储的值。是表达式。

  namespacehash :(通过 import )为命名空间创建的哈希表。是表达式。

2.9.2  描述

使用这个指令你可以创建一个新的变量,或者替换一个已经存在的变量。注意仅仅顶级

变量可以被创建/替换(也就是说你不能创建/替换 some_hash.subvar ,除了

some_hash )。

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

例如: seasons 变量可以存储一个序列:

<#assign seasons = ["winter","spring", "summer", "autumn"]>

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

<#assign test = test + 1>

作为一个方便的特性,你可以使用一个 assign 标记来进行多次定义。比如这个会做

上面两个例子中相同的事情:

<#assign

seasons = ["winter","spring", "summer", "autumn"]

test = test + 1

如果你知道什么是命名空间: assign 指令在命名空间中创建变量。通常它在当前的

命名空间(也就是和标签所在模板关联的命名空间)中创建变量。但如果你是用了 in

namespacehash ,那么你可以用另外一个命名空间来创建/替换变量。比如,这里你在

命名空间中 /mylib.ftl 创建/替换了变量 bgColor 。

<#import "/mylib.ftl" asmy>

<#assign bgColor="red" inmy>

assign 的极端使用是当它捕捉它的开始标记和结束标记中间生成的输出时。也就是

说,在标记之间打印的东西将不会在页面上显示,但是会存储在变量中。比如:

<#macro myMacro>foo</#macro>

<#assign x>

<#list 1..3 as n>

${n} <@myMacro />

</#list>

</#assign>

Number of words: ${x?word_list?size}

${x}

 

将会打印:

Number of words: 6

1 foo

2 foo

3 foo

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

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

你可以这么来写:

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

2.10 global  指令

2.10.1  概要

<#global name=value>

or

<#global name1=value1 name2=value2 ...nameN=valueN>

or

<#global name>

capture this

</#global>

这里:

  name :变量的名称。它不是表达式。但它可以被写作是字符串形式,如果变量名

包含保留字符这是很有用的,比如 <#global "foo-bar" = 1> 。注意这个

字符串没有扩展插值(如 "${foo}" )。

  value :存储的值,是表达式。

2.10.2  描述

这个指令和 assign 相似,但是被创建的变量在所有的命名空间中都可见,但又不会

存在于任何一个命名空间之中。精确地说,正如你会创建(或替换)一个数据模型变量。因

此,这个变量是全局的。如果在数据模型中,一个相同名称的变量存在的话,它会被使用这

个指令创建的变量隐藏。如果在当前的命名空间中,一个相同名称的变量存在的话,那么会

隐藏由 global 指令创建的变量。

比如 <#global x = 1> ,用创建了一个变量,那么在所有命名空间中x 都可见,

除非另外一个称为 x 的变量隐藏了它(比如你已经用 <#assign x = 2> 创建了一个变

量)。这种情形下,你可以使用特殊变量 globals ,比如 ${.globals.x} 。注意使用

globals 你看到所有全局可访问的变量;不但由global 指令创建的变量,而且是数据

模型中的变量。

自定义 JSP 标记的用户请注意:用这个指令创建的变量集合和 JSP 页面范围对应。这就

意味着,如果自定义 JSP 标记想获得一个页面范围的属性(page-scope bean),在当前命名

空间中一个相同名称的变量,从 JSP 标记的观点出发,将不会隐藏。

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

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

2.11 local  指令

2.11.1  概要

<#local name=value>

or

<#local name1=value1 name2=value2 ...nameN=valueN>

or

<#local name>

capture this

</#local>

这里:

  name :在 root 中局部对象的名称。它不是一个表达式。但它可以被写作是字符串

形式,如果变量名包含保留字符这是很有用的,比如 <#global "foo-bar" =

1> 。注意这个字符串没有扩展插值(如"${foo}" )。

  value :存储的值,是表达式。

2.11.2  描述

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

才会有作用。

要获得更多关于变量的信息,可以阅读:模板开发指南/其他/在模板中定义变量部分内

容。

2.12 setting  指令

2.12.1  概要

<#setting name=value>

这里:

  name :设置的名称。不是表达式!

  value :设置的值,是表达式。

2.12.2  描述

为进一步的处理而设置。设置是影响 FreeMarker 行为的值。新值仅仅在被设置的模板

处理时出现,而且不触碰模板本身。设置的初始值是由程序员设定的(参加:程序开发指南

/配置/设置信息部分的内容)。

支持的设置有:

  locale :输出的本地化(语言)。它可以影响数字,日期等显示格式。它的值是

由语言编码(小写两个字母的 ISO-639 编码)和可选的国家码(大写的两个字母

ISO-3166 编码)组成的字符串,它们以下划线相分隔,如果我们已经指定了国家那

么一个可选的不同编码(不是标准的)会以下划线分隔开国家。合法的值示例:

en , en_US , en_US_MAC 。FreeMarker 会尝试使用最特定可用的本地化设置,

所以如果你指定了 en_US_MAC ,但是它不被知道,那么它会尝试 en_US ,然

后尝试 en ,然后是计算机(可能是由程序员设置的)默认的本地化设置。

  number_format :当没有指定确定的格式化形式时,用来转化数字到字符串形

式的数字格式化设置。可以是下列中的一个预定义值 number (默认的),

computer , currency ,或 percent 。此外,以 Java 小数数字格式化语法

书写的任意的格式化形式也可以被指定。更多的格式形式请参考处理数字的内建函

数 string 。

  boolean_format :以逗号分隔的一对字符串来分别展示true 和 false 值,当

没有指定确定的格式时,转换布尔值到字符串。默认值是 "true,false" 。也

可以参考处理布尔值的内建函数 string 。

  date_format , time_format ,datetime_format :,当没有指定确定

的格式时,用来转换日期到字符串的日期/时间格式形式。如 ${someDate} .

date_format 这个情形,它只影响和日期相关的日期(年,月,日),

time_format 只 影 响 和 时 间 相 关 的 日 期 ( 时 , 分 , 秒 , 毫 秒 ),

datetime_format 只影响时间日期类型的日期(年,月,日,时,分,秒,

毫秒)。这个设置的可能值和处理日期的内建函数 string 的参数相似;可以在

那部分参考更多内容。比如 "short" , "long_medium" ,"MM/dd/yyyy" 。

  time_zone :时区的名称来显示并格式化时间。默认情况下,使用系统的时区。

也可以是 Java 时区 API 中的任何值。比如: "GMT" , "GMT+2" , "GMT-1:30" ,

"CET" ,"PST" , "America/Los_Angeles" 。

  url_escaping_charset :用来 URL 转义(比如${foo?url} )的字符集,

来计算转义( %XX )的部分。通常包含 FreeMarker 的框架应该设置它,所以你不

应该在模板中来设置。(程序员可以阅读程序开发指南/其他/字符集问题部分来获

取更多内容)

  classic_compatible :这是对专业人员来说的,它的值应该是一个布尔值。

参见 freemarker.template.Configurable 的文档来获取更多信息。

示例:假设模板的初始本地化是 hu(Hungarian,匈牙利),那么这个:

${1.2}

<#setting locale="en_US">

${1.2}

将会输出:

1,2

1.2

因为匈牙利人使用逗号作为小数的分隔符,而美国人使用点。

2.13  用户自定义指令(<@...>)

2.13.1  概要

<@user_def_dir_exp param1=val1param2=val2 ... paramN=valN/>

(注意 XML 风格, > 之前的 / )

如果你需要循环变量,请参考 2.13.2.2 节内容。

<@user_def_dir_exp param1=val1param2=val2 ... paramN=valN ;

lv1, lv2, ..., lvN/>

或者和上面两个相同但是使用结束标签,请参考 2.13.2.1 节内容。

<@user_def_dir_exp ...>

...

</@user_def_dir_exp>

<@user_def_dir_exp ...>

...

</@>

或和上面的相同但是使用位置参数传递,请参考 2.13.2.3 节内容

<@user val1, val2, ..., valN/>

等…

这里:

  user_def_dir_exp :表达式算作是自定义指令(比如宏),将会被调用。

  param1 , param2 等:参数的名称,它们不是表达式。

  val1 , val2 等:参数的值,它们是表达式。

  lv1 , lv2 等:循环变量的名称,它们不是表达式。

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

参数的顺序并不重要(除非你使用了位置参数传递)。参数名称必须唯一。在参数名中

小写和大写的字母被认为是不同的字母(也就是 Color 和 color 是不同的)。

2.13.2  描述

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

的自定义指令。

你可以阅读模板开发指南/其他/定义你自己的指令部分。

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

<@html_escape>

a < b

Romeo & Juliet

</@html_escape>

输出:

a &lt; b

Romeo &amp; Juliet

示例 2:调用有参数的宏

<@list items=["mouse","elephant", "python"] title="Animals"/>

...

<#macro list title items>

<p>${title?cap_first}:

<ul>

<#list items as x>

<li>${x?cap_first}

</#list>

</ul>

</#macro>

输出:

<p>Animals:

<ul>

<li>Mouse

<li>Elephant

<li>Python

</ul>

...

2.13.2.1  结束标签

你可以在结束标签中忽略 user_def_dir_exp 。也就是说,你可以写 </@> 来替代

</@anything> 。这个规则当表达式user_def_dir_exp 太复杂时非常有用,因为

你不需要在结束标签中重复表达式。此外,如果表达式包含比简单变量名和点还多的表达式,

你就 不 能 再 重 复 它 们 了 。 比 如

<@a_hash[a_method()]>...</@a_hash[a_method()]>就是错的,你必须

写为 <@a_hash[a_method()]>...</@> 。 但 是

<@a_hash.foo>...</@a_hash.foo> 是可以的。

2.13.2.2  循环变量

一些自定义指令创建循环变量(和 list 指令相似)。正如预定义指令(如 list )一

样,当你调用这个指令(如 <#list foos as foo>...</#list> 中的 foo )时循

环变量的名称就给定了,而变量的值是由指令本身设置的。在自定义指令的情形下,语法是

循环变量的名称在分号之后给定。比如:

<@myRepeatMacro count=4 ; x, last>

${x}. Something... <#if last> Thiswas the last!</#if>

</@myRepeatMacro>

注意由自定义指令创建的循环变量数量和分号之后指定的循环变量数量需要不匹配。也

就是说,如果你对重复是否是最后一个不感兴趣,你可以简单来写:

<@myRepeatMacro count=4 ; x>

${x}. Something...

</@myRepeatMacro>

或者你可以:

<@myRepeatMacro count=4>

Something...

</@myRepeatMacro>

此外,如果你在分号之后指定更多循环变量而不是自定义指令创建的,也不会引起错误,

只是最后的循环变量不能被创建(也就是在嵌套内容中那些将是未定义的)。尝试使用未定

义的循环变量,就会引起错误(除非你使用如 ?default 这样的内建函数),因为你尝试

访问了一个不存在的变量。

请参考模板开发指南/其他/定义你自己的指令部分来获取更多内容。

2.13.2.3  位置参数传递

位置参数传递(如 <@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"/> ;如果你使用参数名称,那顺序就不重要了)是相等的。要注意

位置参数传递现在仅仅支持宏定义。

2.14 macro ,nested ,return  指令

2.14.1  概要

<#macro name param1 param2 ...paramN>

...

<#nested loopvar1, loopvar2, ...,loopvarN>

...

<#return>

...

</#macro>

这里:

  name :宏变量的名称,它不是表达式。然而,它可以被写成字符串的形式,如果

<@myRepeatMacro count=4 ; x>

${x}. Something...

</@myRepeatMacro>

<@myRepeatMacro count=4>

Something...

</@myRepeatMacro>

宏名称中包含保留字符时这是很有用的,比如 <#macro "foo-bar">... 。

注意这个字符串没有扩展插值(如 "${foo}" )。

  param1 , param2 等: 局部变量的名称,存储参数的值(不是表达式),在 =

号后面和默认值(是表达式)是可选的。默认值也可以是另外一个参数,比如

<#macro section title label=title> 。

  paramN ,最后一个参数,可以可选的包含一个尾部省略( ... ),这就意味着宏

接受可变的参数数量。如果使用命名参数来调用, paramN 将会是包含给宏的所

有未声明的键/值对的哈希表。如果使用位置参数来调用, paramN 将是额外参数

的序列。

  loopvar1 , loopvar2 等:可选的循环变量的值,是nested 指令想为嵌套

内容创建的。这些都是表达式。

return 和 nested 指令是可选的,而且可以在<#macro> 和 </#macro> 之间被

用在任意位置和任意次数。

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

2.14.2  描述

创建一个宏变量(在当前命名空间中,如果你知道命名空间的特性)。如果你对宏和自

定义指令不了解,你应该阅读模板开发指南/其他/定义你自己的指令部分。

宏变量存储模板片段(称为宏定义体)可以被用作自定义指令。这个变量也存储自定义

指令的被允许的参数名。当你将这个变量作为指令时,你必须给所有参数赋值,除了有默认

值的参数。默认值当且仅当你调用宏而不给参数赋值时起作用。

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

也可以:

<#-- call the macro; the macro variableis already created: -->

<@test/>

...

<#-- create the macro variable: -->

<#macro test>

Test text

</#macro>

然而,如果宏定义被插在 include 指令中,它们直到 FreeMarker 执行 include 指

令时参会可用。

例如:没有参数的宏:

<#macro test>

Test text

</#macro>

<#-- call the macro: -->

<@test/>

输出:

Test text

示例:有参数的宏:

<#macro test foo bar baaz>

Test text, and the params: ${foo}, ${bar},${baaz}

</#macro>

<#-- call the macro: -->

<@test foo="a"bar="b" baaz=5*5-2/>

输出:

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

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

<#macro test foo bar="Bar"baaz=-1>

Test text, and the params: ${foo}, ${bar},${baaz}

</#macro>

<@test foo="a"bar="b" baaz=5*5-2/>

<@test foo="a"bar="b"/>

<@test foo="a" baaz=5*5-2/>

<@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

示例:一个复杂的宏。

<#macro list title items>

<p>${title?cap_first}:

<ul>

<#list items as x>

<li>${x?cap_first}

</#list>

</ul>

</#macro>

<@list items=["mouse","elephant", "python"] title="Animals"/>

输出:

<p>Animals:

<ul>

<li>Mouse

<li>Elephant

<li>Python

</ul>

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

<#macro img src extra...>

<img src="/context${src?html}"

<#list extra?keys as attr>

${attr}="${extra[attr]?html}"

</#list>

</#macro>

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

输出:

<imgsrc="/context/images/test.png"

alt="Test"

height="50"

width="100"

2.14.2.1 nested

nested 指令执行自定义指令开始和结束标签中间的模板片段。嵌套的片段可以包含

模板中合法的任意内容:插值,指令…等。它在上下文环境中被执行,也就是宏被调用的地

方,而不是宏定义体的上下文中。因此,比如,你不能看到嵌套部分的宏的局部变量。如果

你没有调用 nested 指令,自定义指令开始和结束标记中的部分将会被忽略。

示例:

<#macro do_twice>

1. <#nested>

2. <#nested>

</#macro>

<@do_twice>something</@do_twice>

输出:

1. something

2. something

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

<#macro do_thrice>

<#nested 1>

<#nested 2>

<#nested 3>

</#macro>

<@do_thrice ; x>

${x} Anything.

</@do_thrice>

这会打印:

1 Anything.

2 Anything.

3 Anything.

一个更为复杂的示例:

<#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!

2.14.2.2 return

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

<#macro test>

Test text

<#return>

Will not be printed.

</#macro>

<@test/>

输出:

Test text

2.15 function ,return  指令

2.15.1  概要

<#function name param1 param2 ...paramN>

...

<#return returnValue>

</#function>

这里:

  name :方法变量的名称(不是表达式)

  param1 , param2 等:局部变量的名称,存储参数的值(不是表达式),在 = 号

后面和默认值(是表达式)是可选的。

  paramN ,最后一个参数,可以可选的包含一个尾部省略( ... ),这就意味着宏

接受可变的参数数量。局部变量 paramN 将是额外参数的序列。

  returnValue :计算方法调用值的表达式。

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

和任意次数。

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

2.15.2  描述

创建一个方法变量(在当前命名空间中,如果你知道命名空间的特性)。这个指令和

macro 指令的工作方式一样,除了 return 指令必须有一个参数来指定方法的返回值,

而且视图写入输出的将会被忽略。如果到达 </#function> (也就是说没有 return

returnValue ),那么方法的返回值就是未定义变量。

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

<#function avg x y>

<#return (x + y) / 2>

</#function>

${avg(10, 20)}

将会打印:

15

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

<#function avg nums...>

<#local sum = 0>

<#list nums as num>

<#local sum = sum + num>

</#list>

<#if nums?size != 0>

<#return sum / nums?size>

</#if>

</#function>

${avg(10, 20)}

${avg(10, 20, 30, 40)}

${avg()!"N/A"}

会打印:

15

25

N/A

2.16 flush  指令

2.16.1  概要

<#flush>

2.16.2  描述

当 FreeMarker 生成输出时,他/她通常存储生成的输出内容然后以一个或几个大片段送

到客户端。这种发送的行为被称为冲洗(就像冲厕所)。尽管冲洗是自动发生的,有时你想

在模板处理时的一点强制执行,这就是 flush 指令要做的。如果需要它,那么程序员会告

诉你;通常你不必使用这个指令。自动冲洗的机制和 flush 指令的明确效果是在程序员的

控制之下的。

冲洗简单调用当前使用 java.io.Writer 实例的 flush方法。整体的缓冲和冲洗机制在 writer

(就是传递给 Template.process 方法的参数)中已经实现了;FreeMarker 不用来处

理它。

2.17 stop  指令

2.17.1  概要

<#stop>

<#stop reason>

这里:

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

2.17.2  描述

中止模板的处理。这是一种像紧急中断的机制:不要在普通情况下使用。抛出

StopException 异常会发生中止,而且StopException 会持有 reason 参数的值。

15

25

N/A

2.18 ftl  指令

2.18.1  概要

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

这里:

  param1 , param2 等:参数的名字,不是表达式。允许的参数有 encoding ,

strip_whitespace , strip_text 等。参加下面。

  value1 , value2 等:参数的值。必须是一个常量表达式(如 true ,或

"ISO-8859-5" ,或 {x:1, y:2})。它不能用变量。

2.18.2  描述

告诉 FreeMarker 和其他工具关于模板的信息,而且帮助程序员来自动检测一个文本文

件是否是 FTL 文件。这个指令,如果存在,必须是模板的第一句代码。该指令前的任何空白

将被忽略。这个指令的老式语法(#-less)格式是不被支持的。

一些设置(编码方式,空白剥离等)在这里给定的话就有最高的优先级,也就是说,它

们直接作用于模板而不管其他任何 FreeMarker 配置的设置。

参数:

  encoding :使用这个你可以在模板文件中为模板指定编码方式(字符集) (也就

是说 , 这 是 新 创 建 Template 的 encoding 设 置 , 而 且

Configuration.getTemplate 中的 encoding 参数不能覆盖它。) 。要

注意,FreeMarker 会尝试会和自动猜测的编码方式(这依赖于程序员对 FreeMarker

的配置)找到 ftl 指令并解释它,然后就会发现 ftl 指令会让一些东西有所不

同,之后以新的编码方式来读取模板。因此,直到 ftl 标记使用第一个编码方式

读取到结尾,模板必须是合法的 FTL。这个参数的合法值是从 IANA 字符集注册表

中参考 MIME 中的字符集名称。比如 ISO-8859-5,UTF-8 或 Shift_JIS。

  strip_whitespace :这将开启/关闭空白剥离。合法的值是布尔值常量 true

和 flase (为了向下兼容,字符串 "yes" , "no" , "true" , "false" 也是

可以的)。默认值(也就是当你不使用这个参数时)是依赖于程序员对 FreeMarker

的配置,但是对新的项目还应该是 true 。

  strip_text :当开启它时,当模板被解析时模板中所有顶级文本被移除。这个

不会影响宏,指令,或插值中的文本。合法值是布尔值常量 true 和 flase (为

了向下兼容,字符串 "yes" , "no" , "true" , "false" 也是可以的)。默认

值(也就是当你不使用这个参数时)是 flase 。

  strict_syntax :这会开启/关闭“严格的语法”。合法值是布尔值常量 true

和 flase (为了向下兼容,字符串 "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 查询,但是它也影响visit 和 recurse 指令

的工作。相同节点的命名空间只能注册一个前缀(否则会发生错误),所以在前缀

和节点命名空间中是一对一的关系。前缀 D 和 N 是保留的。如果你注册前缀 D ,

那么除了你可以使用前缀 D 来关联节点命名空间,你也可以设置默认的节点命名

空间。前缀 N 就不能被注册;当且仅当前缀 D 被注册时, N 被用来表示在特定位

置没有节点命名空间的节点。(要参考默认节点命名空间的用法, N ,一般的前缀,

可以看 XML 处理中 visit 和 recurse 指令。) ns_prefixes 的作用限制在

单独的 FTL 命名空间内,换句话说,就是为模板创建的 FTL 命名空间内。这也意味

着 ns_prefixes 当一个 FTL 命名空间为包含它的模板所创建时才有作用,否则

ns_prefixes 参数没有效果。FTL 命名空间当下列情况下为模板创建:(a)模

板是“主”模板,也就是说它不是被 <#include ...> 来调用的模板,但是直

接被调用的(和 process 一起的 Template 或 Environment 类的方法);

(b)模板直接被 <#import ...> 调用。

  attributes :这是关联模板任意属性(名-值对)的哈希表。属性的值可以是

任意类型(字符串,数字,序列等)。FreeMarker 不会尝试去理解属性的含义。它

是由封装 FreeMarker(比如一个 Web 应用框架)的应用程序决定的。因此,允许

的属性的设置是它们依赖的应用(Web 应用框架)的语义。程序员:你可以通过

关联 Template 对象 的getCustomAttributeNames 和

getCustomAttribute 方法(从freemarker.core.Configurable

继承而来)获得属性。如当模板被解析时,关联 Template 对象的模板属性,属

性可以在任意时间被读取,而模板不需要被执行。上面提到的方法返回未包装的属

性值,也就是说,使用 FreeMarker 独立的类型,如 java.util.List 。

这个指令也决定模板是否使用尖括号语法(比如 <#include 'foo.ftl'> )或方

括号语法(如 [#include 'foo.ftl'] )。简单而言,这个指令使用的语法将会是整

个模板使用的语法,而不管 FreeMarker 是如何配置的。

2.19 t ,lt ,rt  指令

2.19.1  概要

<#t>

<#lt>

<#rt>

<#nt>

2.19.2  描述

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

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

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

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

这里:

  “首部空白”表示本行所有空格和制表符(和其他根据 UNICODE 中的空白字符,

除了换行符)在第一个非空白字符之前。

  “尾部空白”表示本行所有的空格和制表符(和其他根据 UNICODE 中的空白字符,

除了换行符)在最后一个非空白字符之后,还有行末尾的换行符。

理解这些检查模板本身的指令是很重要的,而不是当你合并数据模型时,模板生成的输

出。(也就是说,空白的移除发生在解析阶段)

比如这个:

--

1 <#t>

2<#t>

3<#lt>

4

5<#rt>

6

--

将会输出:

--

1 23

4

5 6

--

这些指令在行内的放置不重要。也就是说,不管你是将它们放在行的开头,或是行的末

尾,或是在行的中间,效果都是一样的。

2.20 nt  指令

2.20.1  概要

<#nt>

2.20.2  描述

“不要削减”。这个指令关闭行中出现的空白削减。它也关闭其他同一行中出现的削减

指令( t , lt , rt 的效果)。

2.21 attempt ,recover  指令

2.21.1  概要

<#attempt>

attempt block

<#recover>

recover block

</#attempt>

这里:

  attempt block :任意内容的模板块。这是会被执行的,但是如果期间发生了

错误,那么这块内容的输出将会回滚,之后 recover block 就会被执行。

  recover block : 任意内容的模板块。这个仅在attempt block 执行期

间发生错误时被执行。你可以在这里打印错误信息或其他操作。

recover 是命令的。 attempt/recover可以嵌套在其他 attempt s 或

recover s 中。

注意:

上面 的 格 式 是 从 2.3.3 版 本 开 始 支 持 的 , 之 前 它 是

<#attempt>...<#recover>...</#recover>,也支持向下兼容。此外,这些

指令在 FreeMarker 2.3.1 版本时引入的,在 2.3 版本中是不存在的。

2.12.2  描述

如果你想让页面成功输出内容,尽管它在页面特定位置发生错误也这样,那么这些指令

就是有用的。如果一个错误在 attempt block 执行期间发生,那么模板执行就会中止,

但是 recover block 会代替 attempt block 执行。如果在 attempt block

执行期间没有发生错误,那么 recover block 就会忽略。一个简单的示例如下:

Primary content

<#attempt>

Optional content: ${thisMayFails}

<#recover>

Ops! The optional content is not available.

</#attempt>

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!”之前输出。( 这是在 attemptblock 块内,侵入的输出缓冲的实现,就连 flush

指令也会送输出到客户端。)

为了阻止来自上面示例的误解: attempt / recover 不仅仅是处理未定义变量(对

于那个可以使用不存在变量控制符来处理)。它可以处理发生在块执行期间的各种类型的错

误(而不是语法错误,这会在执行之前被检测到)。它的目的是包围更大的模板段,错误可

能发生在很多地方。比如,你在模板中有一个部分,来处理打印广告,但是它不是页面的主

要内容,所以你不想你的页面因为一些打印广告(也可能是短暂的数据库故障)的错误而挂

掉。所以你将整个广告区域放在 attempt block 块中。

在一些环境下,程序员配置 FreeMarker,所以对于特定的错误,它不会中止模板的执行,

在打印一些错误提示信息到输出(请参考:程序开发指南/配置/错误处理部分)中之后,而

是继续执行。 attempt 指令不会将这些抑制的错误视为错误。

在 recover block 块中,错误的信息存在特殊变量 error 中。不要忘了以点开

始引用特殊变量(比如: ${.error} )

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

块中。

2.22 visit ,recurse ,fallback  指令

2.22.1  概要

<#visit node using namespace>

<#visit node>

<#recurse node using namespace>

<#recurse node>

<#recurse using namespace>

<#recurse>

Primary content

Ops! The optional content is not available.

Primary content continued

Primary content

Optional content: 123

Primary content continued

<#fallback>

这里:

  node :算作节点变量的表达式。

  namespace :一个命名空间,或者是命名空间的序列。命名空间可以以命名空

间哈希表(又称为根哈希表)给定,或者可以引入一个存储模板路径的字符串。代

替命名空间哈希表,你也可以使用普通哈希表。

2.22.2  描述

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

2.22.2.1 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 是可用的。比如:

<#-- 假设nodeWithNameX?node_name是"x" -->

<#visit nodeWithNameX>

Done.

<#macro x>

Now I'm handling a node that has the name"x".

Just to show how to access this node: thisnode has

${.node?children?size} children.

</#macro>

输出就像:

Now I'm handling a node that has the name"x".

Just to show how to access this node: thisnode has

${.node?children?size} children.

</#macro>

如果使用可选的 using 从句来指定一个或多个命名空间,那么 visit 就会在那么命

名空间中寻找指令,和先前列表中指定的命名空间都获得优先级。如果指定 using 从句,

对最后一个未完成的 visit 调用的用 using 从句指定命名空间的命名空间或序列被重用

了。如果没有这样挂起的 visit 调用,那么当前的命名空间就被使用。比如,如果你执行

这个模板:

<#import "n1.ftl" as n1>

<#import "n2.ftl" as n2>

<#-- 这会调用 n2.x (因为没有 n1.x):-->

<#visit nodeWithNameX using [n1, n2]>

<#-- 这会调用当前命名空间中的 x:-->

<#visit nodeWithNameX>

<#macro x>

Simply x

</#macro>

这是 n1.ftl :

<#macro y>

n1.y

</#macro>

这是 n2.ftl :

<#macro x>

n2.x

<#-- 这会调用call n1.y,因为它 从等待访问调用中继承了"using[n1,

n2]"-->

<#visit nodeWithNameY>

<#-- 这会调用n2.y: -->

<#visit nodeWithNameY using.namespace>

</#macro>

<#macro y>

n2.y

</#macro>

这会打印:

n2.x

n1.y

n2.y

Simply x

如果 visit 既没有在和之前描述规则的名称扣除相同名字的FTL 命名空间发现自定义

指令,那么它会尝试用名称 @node_type 来查找,又如果节点不支持节点类型属性(也

就是 node?node_type 返回未定义变量),那么使用名称 @default 。对于查找来说,

它使用和之前描述相同的机制。如果仍然没有找到处理节点的自定义指令,那么 visit 停

止模板执行,并抛出错误。一些 XML 特定的节点类型在这方面有特殊的处理;参考:XML

处理指南/声明的 XML 处理/详细内容部分。示例:

<#-- 假设nodeWithNameX?node_name是"x" -->

<#visit nodeWithNameX>

<#-- 假设nodeWithNameY?node_type 是"foo" -->

<#visit nodeWithNameY>

<#macro x>

Handling node x

</#macro>

<#macro @foo>

There was no specific handler for node${node?node_name}

</#macro>

这会打印:

Handling node x

There was no specific handler for node y

2.22.2.2 recurse  指令

<#recurse> 指令是真正纯语义上的指令。它访问节点的所有子节点(而没有节点本

身)。所以来写:

<#recurse someNode using someLib>

和这个是相等的:

<#list someNode?children aschild><#visit child using

someLib></#list>

而目标节点在 recurse 指令中是可选的。如果目标节点没有指定,那就仅仅使

用 .node 。因此, <#recurse> 这个精炼的指令和下面这个是相同的:

<#list .node?children aschild><#visit child></#list>

对于 熟 悉 XSLT 的 用 户 的 评 论 , <#recurse> 是 和 XSLT 中

<xsl:apply-templates/> 指令相当类似的。

2.22.2.3 fallback  指令

正如前面所学的,在 visit 指令的文档中,自定义指令控制的节点也许在多个 FTL 命

名空间中被搜索。 fallback 指令可以被用在自定义指令中被调用处理节点。它指挥

FreeMarker 在更多的命名空间(也就是,在当前调用列表中自定义指令命名空间之后的命名

空间)中来继续搜索自定义指令。如果节点处理器被发现,那么就被调用,否则 fallback

不会做任何事情。

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

<#import "/lib/docbook.ftl" asdocbook>

<#-- 我们使用 docbook 类库,但是我们覆盖一些这个命名空间中的处理器-->

<#visit document using [.namespace,docbook]>

<#-- 覆盖"programlisting"处理器,但是要注意它的"role"属性是"java"

-->

<#macro programlisting>

<#if .node.@role[0]!"" =="java">

<#-- 在这里做一些特殊的事情... -->

...

<#else>

<#-- 仅仅使用原来的(覆盖的)处理器-->

<#fallback>

</#if>

</#macro>

展开阅读全文

没有更多推荐了,返回首页

©️2019 CSDN 皮肤主题: Age of Ai 设计师: meimeiellie
应支付0元
点击重新获取
扫码支付

支付成功即可阅读