freeMarker(五)——模板开发指南补充知识
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net
模板开发指南补充知识
1. 自定义指令
自定义指令可以使用 macro
指令来定义, 这是模板设计者所关心的内容。 Java程序员若不想在模板中实现定义指令,而是在Java语言中实现指令的定义, 这时可以使用 freemarker.template.TemplateDirectiveModel
类来扩展 (请参考 后续章节)。
1.1 基本内容
宏是有一个变量名的模板片段。可以在模板中使用宏作为自定义指令, 这样就能进行重复性的工作。例如,创建一个宏变量来输出大字号的''Hello Joe!'':
<#macro greet> <font size="+2">Hello Joe!</font> </#macro>
macro
指令自身不输出任何内容, 它只是用来创建宏变量,所以就会有一个名为 greet
的变量。在 <#macro greet>
和 </#macro>
之间的内容 (称为 宏定义体) 将会在使用该变量作为指令时执行。可以在FTL标记中通过 @
代替#
来使用自定义指令。 使用变量名作为指令名。而且,自定义指令的 结束标记 也是需要的。那么, 就可以这样来使用 greet
:
<@greet></@greet>
因为 <anything></anything>
和 <anything/>
是相同的, 也可以使用单标记形式(如果你了解 XML,那么就应该很熟悉了):
<@greet/>
将会输出:
<font size="+2">Hello Joe!</font>
宏能做的事情还有很多,因为在 <#macro ...>
和 </#macro>
之间的东西是模板片段,也就是说它可以包含插值 (${...}
) 和FTL标签 (如 <#if ...>...</#if>
)。
程序员通常将使用 <@...>
这称为 宏 调用。
1.2 参数
我们来改进 greet
宏使之可以使用任意的名字, 而不仅仅是''Joe''。为了实现这个目的,就要使用到 参数。在 macro
指令中,宏名称的后面位置是用来定义参数的。这里我们仅在 greet
宏中定义一个参数,person
:
<#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
)放在宏定义体中的。使用 预定义指令时,参数的值 (=
号后边的值)可以是 FTL 表达式。 那么,不像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"/>
参数的顺序不重要,下面的这个和上面的含义也是相同的:
<@greet color="black" person="Fred"/>
当调用这个宏的时候,只能使用在 macro
指令中定义的参数(本例中是:person
和 color
)。 那么当你尝试 <@greet person="Fred" color="black" background="green"/>
的时候就会发生错误,因为并没有在 <#macro...>
中提及参数 background
。
同时也必须给出在宏中定义所有参数的值。如果尝试 <@greet person="Fred"/>
时也会发生错误, 因为忘记指定 color
的值了。 很多情况下需要给一个参数指定一个相同的值,所以我们仅仅想在这个值发生变化后重新赋给变量。 那么要达到这个目的,在macro
指令中必须这么来指定变量: param_name=usual_value
。 例如,当没有特定值的时候,我们想要给 color
赋值为 "black"
,那么 greet
指令就要这么来写:
<#macro greet person color="black"> <font size="+2" color="${color}">Hello ${person}!</font> </#macro>
现在,我们这么使用宏就可以了: <@greet person="Fred"/>
,因为它和 <@greet person="Fred" color="black"/>
是相等的, 这样参数 color
的值就是已知的了。 如果想给 color
设置为 "red"
, 那么就写成: <@greet person="Fred" color="red"/>
, 这时 macro
指令就会使用这个值来覆盖之前设置的通用值, 参数 color
的值就会是 "red"
了。
根据已知的 FTL 表达式规则, 明白下面这一点是至关重要的。someParam=foo
和 someParam="${foo}"
是不同的。第一种情况, 是把变量 foo
的值作为参数的值来使用。第二种情况则是使用 插值形式的字符串, 那么参数值就是字符串了,这个时候, foo
的值呈现为文本, 而不管 foo
是什么类型的(数字,日期等)。看下面这个例子: someParam=3/4
和 someParam="${3/4}"
是不同的。 如果指令需要 someParam
是一个数字值, 那么就不要用第二种方式。切记不要改变这些。
宏参数的另外一个重要的方面是它们是局部变量。 更多局部变量的信息可以阅读:在模板中定义变量
1.3 嵌套内容
自定义指令可以嵌套内容,和预定义指令相似:<#if ...>nested content</#if>
。 例如,下面这个例子中是创建了一个可以为嵌套的内容画出边框的宏:
<#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>
将会输出:
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>
在嵌套的内容中,宏的 局部变量 是不可见的。为了说明这点,我们来看:
<#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: ? ? ?
因为 y
, x
和 count
是宏的局部(私有)变量,从宏外部定义是不可见的。 此外不同的局部变量的设置是为每个宏自己调用的,所以不会导致混乱:
<#macro test foo>${foo} (<#nested>) ${foo}</#macro> <@test foo="A"><@test foo="B"><@test foo="C"/></@test></@test>
将会输出:
A (B (C () C) B) A
1.4 宏和循环变量
像 list
这样的预定义指令可以使用循环变量; 可以阅读 在模板中定义变量 来理解循环变量。
自定义指令也可以有循环变量。比如我们来扩展先前例子中的 do_thrice
指令,就可以拿到当前的循环变量的值。 而对于预定义指令(如list
),当调用指令时,循环变量的 name是给定的(比如 <#list foos as foo>...</#list>
中的 foo
),变量 value 的设置是由指令本身完成的。
<#macro do_thrice> > > > </#macro> <@do_thrice ; x> <#-- user-defined directive uses ";" instead of "as" --> ${x} Anything. </@do_thrice>
将会输出:
1 Anything. 2 Anything. 3 Anything.
语法规则是给确定"循环"的循环变量传递真实值(比如重复嵌套内容)来作为 nested
指令的参数(当然参数可以是任意的表达式)。 循环变量的名称是在自定义指令的开始标记(<@...>
) 的参数后面通过分号确定的。
一个宏可以使用多个循环变量(变量的顺序是很重要的):
<#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
指令还多的变量, 那么最后的循环变量将不会被创建(在嵌套内容中不会被定义)。
1.5 自定义指令和宏进阶
现在你也许已经阅读过 FreeMarker 参考手册的相关部分了:
也可以在FTL中定义方法,参见 function
指令。
也许你对命名空间感兴趣: 命名空间。 命名空间可以帮助你组织和重用经常使用的宏。
2. 在模板中定义变量
正如我们已经描述过的,模板可以使用在数据模型中定义的变量。 在数据模型之外,模板本身也可以定义变量来使用。 这些临时变量可以使用FTL指令来创建和替换。请注意每一次的 模板执行 工作都维护它自己的私有变量, 同时来渲染页面。变量的初始值是空,当模板执行工作结束这些变量便被销毁了。
可以访问一个在模板里定义的变量,就像是访问数据模型根root上的变量一样。 这个变量比定义在数据模型中的同名参数有更高的优先级,也就是说, 如果恰巧定义了一个名为''foo''的变量,而在数据模型中也有一个名为''foo''的变量, 那么模板中的变量就会将数据模型根上的变量隐藏(而不是覆盖!)。 例如,${foo}
将会输出在模板中定义的变量。
在模板中可以定义三种类型的变量:
''简单''变量: 它能从模板中的任何位置来访问,或者从使用
include
指令引入的模板访问。可以使用assign
指令来创建或替换这些变量。因为宏和方法只是变量,那么macro
指令 和function
指令 也可以用来设置变量,就像assign
那样。局部变量:它们只能被设置在 宏定义体内, 而且只在宏内可见。一个局部变量的生命周期只是宏的调用过程。可以使用
local
指令 在宏定义体内创建或替换局部变量。循环变量:循环变量是由如
list
指令自动创建的,而且它们只在指令的开始和结束标记内有效。宏 的参数是局部变量而不是循环变量。全局变量:这是一个高级话题了, 并且这种变量最好别用。即便它们属于不同的命名空间, 全局变量也被所有模板共享,因为它们是被
import
进来的, 不同于include
进来的。那么它们的可见度就像数据模型那样。 全局变量通过global
指令来定义。
示例:使用 assign
创建和替换变量:
<#assign x = 1> <#-- create variable x --> ${x} <#assign x = x + 3> <#-- replace variable x --> ${x}
输出为:
1 4
局部变量也会隐藏(不是覆盖)同名的''简单''变量。 循环变量也会隐藏(不是覆盖)同名的''简单''变量。例如:
<#assign x = "plain"> 1. ${x} <#-- we see the plain var. here --> <@test/> 6. ${x} <#-- the value of plain var. was not changed --> <#list ["loop"] as x> 7. ${x} <#-- now the loop var. hides the plain var. --> <#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here --> 8. ${x} <#-- it still hides the plain var. --> </#list> 9. ${x} <#-- the new value of plain var. --> <#macro test> 2. ${x} <#-- we still see the plain var. here --> <#local x = "local"> 3. ${x} <#-- now the local var. hides it --> <#list ["loop"] as x> 4. ${x} <#-- now the loop var. hides the local var. --> </#list> 5. ${x} <#-- now we see the local var. again --> </#macro>
输出为:
1. plain 2. plain 3. local 4. loop 5. local 6. plain 7. loop 8. loop 9. plain2
内部循环变量可以隐藏外部循环变量:
<#list ["loop 1"] as x> ${x} <#list ["loop 2"] as x> ${x} <#list ["loop 3"] as x> ${x} </#list> ${x} </#list> ${x} </#list>
输出为:
loop 1 loop 2 loop 3 loop 2 loop 1
请注意,循环变量的设置是通过指令调用时创建的(本例中的 <list ...>
标签)。 没有其他的方式去改变循环变量的值(也就是说,不能使用定义指令来改变它的值)。 从上面的示例来看,也可以使用一个循环变量来暂时隐藏另外一个。
有时会发生一个变量隐藏数据模型中的同名变量, 但是如果想访问数据模型中的变量,此时就可以使用 特殊变量 globals
。例如,假设我们在数据模型中有一个名为 user
的变量,值为''Big Joe'':
<#assign user = "Joe Hider"> ${user} <#-- prints: Joe Hider --> ${.globals.user} <#-- prints: Big Joe -->
通过 global
指令 设置的变量可以隐藏数据模型中的同名变量。通常,全局变量的设置会有精确的目的。 但仍然可以使用如下方式来访问数据模型变量:.data_model.user
。
3.命名空间
当运行FTL模板时,就会有使用 assign
和 macro
指令创建的变量的集合(可能是空的), 可以从 前一章 来看如何使用它们。 像这样的变量集合被称为 命名空间。 简单的情况下可以只使用一个命名空间,称之为 主命名空间。因为通常只使用该命名空间, 所以就没有意识到这点。
如果想创建可以重复使用的宏,函数和其他变量的集合, 通常用术语来说就是引用 库。 使用多个命名空间是必然的。只要考虑你在一些项目中, 或者想和他人共享使用的时候,你是否有一个很大的宏的集合。 但要确保库中没有宏(或其他变量)名和数据模型中变量同名, 而且也不能和模板中引用其他库中的变量同名是不可能的。 通常来说,变量因为名称冲突时也会相互冲突。 所以要为每个库中的变量使用不同的命名空间。
3.1 创建一个库
我们来建立一个简单的库。假设你需要通用的变量 copyright
和 mail
(在你有疑问之前,宏 当作是 变量):
<#macro copyright date> <p>Copyright (C) ${date} Julia Smith. All rights reserved.</p> </#macro> <#assign mail = "jsmith@acme.com">
把上面的这些定义存储在文件 lib/my_test.ftl
中 (目录是存放模板的位置)。假设想在 aWebPage.ftl
中使用这个模板。如果在 aWebPage.ftl
中使用 <#include "/lib/my_test.ftl">
, 那么就会在主命名空间中创建两个变量,这样就不是很好, 因为想让它们只在同一个命名空间''My Test Library''中。所以就不得不使用 import
指令 来代替 include
了。乍一看,这个指令和 include
很相似,但是它会为 lib/my_test.ftl
创建一个空的命名空间,然后在那里执行。lib/my_test.ftl
会发现它自己在一个新的环境中,那里只有数据模型的变量可以找到 (因为它们在哪儿都是可见的),然后会在这个环境中创建两个变量。现在来看这很不错, 但是如果想访问 aWebPage.ftl
中的两个变量, 而它们使用的是主命名空间,就不能看到其他命名空间中的变量。 解决方法是 import
指令不仅仅创建命名空间,而且要通过 import
的调用者(本例中的主命名空间)创建一个新的哈希表变量, 这就成为进入新的命名空间的大门。那么 aWebPage.ftl
就像下面这样:
<#import "/lib/my_test.ftl" as my> <#-- the hash called "my" will be the "gate" --> <@my.copyright date="1999-2002"/> ${my.mail}
要注意它是怎么访问为 /lib/my_test.ftl
创建的命名空间中的变量的,使用新创建的命名空间访问哈希表, my
。将会输出:
<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved.</p> jsmith@acme.com
如果在主命名空间中有一个变量,名为 mail
或 copyright
,那么就不会引起混乱了, 因为两个模板使用了不同的命名空间。例如,在 lib/my_test.ftl
中修改 copyright
成如下这样:
<#macro copyright date> <p>Copyright (C) ${date} Julia Smith. All rights reserved. <br>Email: ${mail}</p> </#macro>
然后替换 aWebPage.ftl
中的内容:
<#import "/lib/my_test.ftl" as my> <#assign mail="fred@acme.com"> <@my.copyright date="1999-2002"/> ${my.mail} ${mail}
将会输出:
<p>Copyright (C) 1999-2002 Julia Smith. All rights reserved. <br>Email: jsmith@acme.com</p> jsmith@acme.com fred@acme.com
当调用了 copyright
宏之后,输出和上面的是相似的, 因为 FreeMarker 已经暂时转向由 import
指令为 /lib/my_test.ftl
生成的命名空间了。因此, copyright
宏看到这里存在的变量 mail
, 而不是主命名空间中存在的其它 mail
3.2 在引入的命名空间中编写变量
偶尔想要在一个被包含的命名空间上创建或替换一个变量。 那么可以使用 assign
指令, 如果用到了它的 namespace
变量,例如下面这样:
<#import "/lib/my_test.ftl" as my> ${my.mail} <#assign mail="jsmith@other.com" in my> ${my.mail}
将会输出:
jsmith@acme.com jsmith@other.com
3.3 命名空间和数据模型
数据模型中的变量在任何位置都是可见的。例如, 如果在数据模型中有一个名为 user
的变量,那么 lib/my_test.ftl
也能访问它, aWebPage.ftl
当然也能:
<#macro copyright date> <p>Copyright (C) ${date} ${user}. All rights reserved.</p> </#macro> <#assign mail = "${user}@acme.com">
如果 user
是 ''Fred''的话,下面这个例子:
<#import "/lib/my_test.ftl" as my> <@my.copyright date="1999-2002"/> ${my.mail}
将会输出:
<p>Copyright (C) 1999-2002 Fred. All rights reserved.</p> Fred@acme.com
不要忘了在模板的命名空间(可以使用 assign
或 macro
指令来创建的变量) 中的变量有着比数据模型中的变量更高的优先级。因此, 数据模型的内容不会干涉到由库创建的变量。
在一些不寻常的应用中,也许想在模板中创建所有命名空间都可见的变量, 就像数据模型中的变量一样。但是你不能在模板中改变数据模型, 却可以通过 global
指令来达到相似的效果, 可以阅读 参考手册 来获得更多信息。
3.4 命名空间的生命周期
命名空间由使用 import
指令中所写的路径来识别。 如果想多次 import
这个路径,那么只会为第一次 import
引用创建命名空间并执行模板。后面相同路径的 import
只是创建一个哈希表当作访问相同命名空间的“门”。 例如,在 aWebPage.ftl
中:
<#import "/lib/my_test.ftl" as my> <#import "/lib/my_test.ftl" as foo> <#import "/lib/my_test.ftl" as bar> ${my.mail}, ${foo.mail}, ${bar.mail} <#assign mail="jsmith@other.com" in my> ${my.mail}, ${foo.mail}, ${bar.mail}
将会输出:
jsmith@acme.com, jsmith@acme.com, jsmith@acme.com jsmith@other.com, jsmith@other.com, jsmith@other.com
这里可以看到通过 my
, foo
和 bar
访问相同的命名空间。
请注意,命名空间是不分层次的,它们相互之间是独立存在的。 那么,如果在命名空间N1中 import
命名空间N2, 那N2也不在N1中,N1只是可以通过哈希表来访问N2。这和在主命名空间中 import
N2,然后直接访问命名空间N2是一样的过程。
每一次 模板的执行过程, 它都有一个私有的命名空间的集合。每一次模板执行工作都是一个分离且有序的过程, 它们仅仅存在一段很短的时间,同时页面用以渲染内容, 然后就和所有填充过的命名空间一起消失了。因此,无论何时我们说第一次调用 import
,一个单一模板执行工作的内容都是这样。
3.5 为他人编写库
如果你已经为其他人员编写一个有用的,高质量的库,你也许想把它放在网络上 (就像 http://freemarker.org/libraries.html)。 为了防止和其他作者使用库的命名相冲突,而且引入其他库时要书写简单, 这有一个事实上的标准,那就是指定库路径的格式。这个标准是: 库的路径必须对模板和其他库可用(可引用),就像这样:
/lib/yourcompany.com/your_library.ftl
如果你为Example公司工作,它们拥有www.example.com网的主页, 你的工作是开发一个部件库,那么要引入你所写的FTL的路径应该是:
/lib/example.com/widget.ftl
请注意,www已经被省略了。第三次路径分割后的部分可以包含子目录,可以像下面这样写:
/lib/example.com/commons/string.ftl
一个重要的规则就是路径不应该包含大写字母,为了分隔词语, 使用下划线 _
,就像 wml_form
(而不是 wmlForm
)。
请注意,如果你的工作不是为公司或组织开发库,你应该使用项目主页的URL,比如 /lib/example.sourceforge.net/example.ftl
,或 /lib/geocities.com/jsmith/example.ftl
。
4. 空白处理
在运行中,模板中的 空白 处理在某种程度上来说是纠缠所有模板引擎的一个问题。
我们来看这个模板。
<p>List of users:[BR] <#assign users = [{"name":"Joe", "hidden":false},[BR] {"name":"James Bond", "hidden":true},[BR] {"name":"Julia", "hidden":false}]>[BR] <ul>[BR] <#list users as user>[BR] <#if !user.hidden>[BR] <li>${user.name}[BR] </#if>[BR] </#list>[BR] </ul>[BR] <p>That's all.
如果 FreeMarker 能按照规则输出所有的 文本,那将会输出:
<p>List of users:[BR] [BR] <ul>[BR] [BR] [BR] <li>Joe[BR] [BR] [BR] [BR] [BR] [BR] <li>Julia[BR] [BR] [BR] </ul>[BR] <p>That's all.
这里有太多的不想要的空格和换行了。幸运的是,HTML和XML对空白都不是敏感的, 但是这么多多余的空白是很令人头疼的,而且处理后的HTML文件大小增加也是没必要的。 当然,对于空白敏感方式的输出这依旧是个大问题。
FreeMarker 提供下面的工具来处理这个问题:
忽略某些模板文件的空白的工具 (解析阶段空白就被移除了):
从输出中移除空白的工具 (移除临近的空白):
compress
指令。
4.1 剥离空白
如果对模板开启这个特性,那么它就会自动忽略 (也就是不在输出中打印出来)两种典型的多余空白:
缩进空白和在行末尾的尾部空白(包括换行符)在只包含FTL标签的行中会被忽略 (比如
<@myMacro/>
,<#if ...>
)和FTL注释(如<#-- blah -->
), 除了被忽略的空白本身。例如,如果一行只包含一个<#if ...>
, 那么在标签前面的缩进和标签后面的换行符将会被忽略。 但是,如果这行上包含<#if ...>x
, 那么空白就不会被忽略,因为这个x
不是FTL标签。请注意,根据这些规则, 一行上包含<#if ...><#list ...>
,空白就会被忽略, 而一行上有<#if ...> <#list ...>
这样的就不会, 因为在两个FTL标签之间的空白是嵌入的空白,而不是缩进的或尾部空白。加在下面这些指令之间的空白会被忽略:
macro
,function
,assign
,global
,local
,ftl
,import
, 但也是指令之间仅仅只有一个空白或FTL注释。实际应用中, 它意味着你可以在宏定义和参数定义之间放置空行,因为行间距是为了更好的可读性, 不包括在输出中打印不必要的空行(换行符)。
使用了剥离空白后,上面那个例子中的输出就会是:
<p>List of users:[BR] <ul>[BR] <li>Joe[BR] <li>Julia[BR] </ul>[BR] <p>That's all.
这是因为在剥离之后,模板就变成这样了:
<p>List of users:[BR] <#assign users = [{"name":"Joe", "hidden":false},[BR] {"name":"James Bond", "hidden":true},[BR] {"name":"Julia", "hidden":false}]>[BR] <ul>[BR] <#list users as user>[BR] <#if !user.hidden>[BR] <li>${user.name}[BR] </#if>[BR] </#list>[BR] </ul>[BR] <p>That's all.
剥离空白功能可以通过 ftl
指令 在模板中开启或关闭。如果你没有通过 ftl
指令来指定, 那么剥离空白功能是开启还是关闭就依据程序员是如何设置 FreeMarker 的了。 默认的情况下剥离空白是开启的,程序员可以留着不管(建议这样做)。请注意开启剥离空白时 不会 降低模板执行的效率,剥离空白的操作在模板加载时就已经完成了。
剥离空白可以为单独的一行关闭,就是使用nt
指令(对没有去掉空白的行来说)。
4.2 使用compress指令
另外一种方法就是使用compress
指令。和剥离空白相反,这个工作是直接基于生成的输出内容, 而不是对于模板进行。也就是说,它会动态地检查输出内容, 而不会检查生成输出FTL的程序。它会很强势地移除缩进, 空行和重复的空格/制表符(可以阅读参考手册 部分来获取更多信息)。所以对于下面这段代码:
<#compress> <#assign users = [{"name":"Joe", "hidden":false}, {"name":"James Bond", "hidden":true}, {"name":"Julia", "hidden":false}]> List of users: <#list users as user> <#if !user.hidden> - ${user.name} </#if> </#list> That's all. </#compress>
将会输出:
List of users: - Joe - Julia That's all.
请注意 compress
是完全独立于剥离空白特性的。 所以它剥离模板中的空白是可能的,那么之后输出的内容就是被 压缩
过的。
此外,在默认情况下,名为 compress
的用户自定义指令是可以在数据模型中存在的(由于向下兼容特性)。 这和指令是相同的,除了可以选择设置 single_line
参数, 这将会移除所有的介于其中的换行符。在最后那个例子中,如果使用<@compress single_line=true>...</@compress>
来代替<#compress>...</#compress>
, 那么就会得到如下输出:
List of users: - Joe - Julia That's all.
5.替换(方括号)语法
这个特性从 FreeMarker 2.3.4 版本后才存在。
FreeMarker支持一个替换的语法。就是在FreeMarker的指令和注释中用 [
和 ]
来代替 <
和 >
,例如下面这个例子:
- 调用预定义指令:
[#list animals as animal]...[/#list]
- 调用自定义指令:
[@myMacro /]
- 注释:
[#-- the comment --]
为了使用这种语法从而代替默认语法,从模板开始,使用 ftl
指令 都要使用这用语法。 如果你不知道什么是 ftl
指令,那么就用 [#ftl]
来开始模板,要记住这个要放在文件的最前面(除了white-space可以在它前面)。 例如,下面的示例是 入门 章节的最后一个例子使用这种替换语法的样子(假设这是一个完整的模板,而不是一个片段):
[#ftl] <p>We have these animals: <table border=1> <tr><th>Name<th>Price [#list animals as animal] <tr> <td> [#if animal.size == "large"]<b>[/#if] ${animal.name} [#if animal.size == "large"]</b>[/#if] <td>${animal.price} Euros [/#list] </table>
这种替换语法(方括号)和默认语法(尖括号)在一个模板中是相互排斥的。 那就是说,整个模板要么全部使用替换语法,要么全部使用默认语法。 如果模板使用了替换语法,那么如 <#if ...>
这样的部分就会被算作是静态文本,而不是FTL标签了。类似地,如果模板使用默认语法, 那么如 [#if ...]
这样的也会被算作是静态文本, 而不是FTL标签。
如果你以[#ftl ...]
(...
代表可选的参数列表, 当然仅用 [#ftl]
也行)来开始文件,那文件就会使用替换(方括号)语法。 如果使用 <#ftl ...>
来开始, 那么文件就会使用正常(尖括号)语法。如果文件中没有 ftl
指令, 那么程序员可以通过配置FreeMarker (程序员可以参看 javadoc API 文档中的 Configuration.setTagSyntax(int)
) 来决定使用哪种语法。 但是大多数情况,程序员可能使用默认配置。FreeMarker 2.3.x 版本默认配置使用常规语法。 而2.4版本中的默认配置将会自动检测,也就是说第一个FreeMarker标签决定了语法形式 (它可以是任意的,而不仅仅是ftl
)。
译自 Email: ddekany at users.sourceforge.net
freeMarker(五)——模板开发指南补充知识的更多相关文章
- FreeMarker:模板开发指南
ylbtech-FreeMarker:模板开发指南 1.返回顶部 1. Section Contents 入门 模板 + 数据模型 = 输出 数据模型一览 模板一览 数值,类型 基本内容 类型 模板 ...
- freeMarker(九)——程序开发指南补充知识
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 1.变量.范围 本章介绍当模板在访问变量时发生了什么事情,还有变量是如 ...
- FreeMarker模板开发指南知识点梳理
freemarker是什么? 有什么用? 怎么用? (问得好,这些都是我想知道的问题) freemarker是什么? FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生 ...
- freeMarker(四)——模板开发指南之模板
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 模板开发指南之模板 1. 总体结构 实际上用程序语言编写的程序就是模板 ...
- freeMarker(三)——模板开发指南之数值、类型
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 模板开发指南——数值.类型 1.基本内容 1.1 什么是数值? 正如你 ...
- freeMarker(二)——模板开发指南之入门
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 模板开发指南-入门 1.模板+数据模型=输出 假设在一个在线商店的应 ...
- 《Node.js开发指南》知识整理
Node.js简介 Node是一个可以让JavaScript运行在服务器端的平台,抛弃了传统平台依靠多线程来实现高并发的设计思路,而采用单线程.异步式I/O.事件驱动式的程序设计模型. 安装和配置No ...
- freeMarker(八)——程序开发指南之配置(Configuration)
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 1.基本内容 配置(configuration)就是 freemark ...
- freeMarker(六)——程序开发指南入门
学习笔记,选自freeMarker中文文档,译自 Email: ddekany at users.sourceforge.net 1.创建Configuration实例 首先,你应该创建一个 free ...
随机推荐
- web安全之SQL注入---第五章 如何预防SQL注入 ?
5-1严格检查输入变量的类型和格式总结:其实就是做一些判断正则表达式:验证密码:/^[a-zA-Z]{6,}$/5-1严格检查输入变量的类型和格式总结:其实就是做一些判断正则表达式:验证密码:/^[a ...
- python+pip+adb
最近开始玩python,用它写一些小程序游戏的辅助,现在做下总结 下面进入正文. 本文适用对象为WIN10系统,安卓用户.目的在于让丝毫没有接触过Python的小伙伴都能成功运行,如果你恰好是这样的对 ...
- redis 集群 搭建
环境: centos6.5 192.168.16.11 centos6.5 192.168.16.12 centos6.5 192.168.16.13 三台虚拟机模拟9个节点,一台机器3个节点,创建出 ...
- fzu2020( c(n,m)%p,其中n, m, p (1 <= m <= n <= 10^9, m <= 10^4, m < p < 10^9, p是素数) )
基本的模板题,统计分子分母中p出现的次数,然后求逆元取模. // // main.cpp // fzu2020 // // Created by 陈加寿 on 15/12/27. // Copyrig ...
- Jmeter 03 Jmeter脚本开发
JMeter 工作区介绍 JMeter Http 协议录制 JMeter 脚本调测 JMeter 关联 JMeter 参数化 JMeter 检查点 JMeter 事务 JMeter 集合点 JMete ...
- Jquery禁用所有checkbox
$("input[type=checkbox]").each(function(){ $(this).attr("disabled",false);});
- 在C语言中使用syslog打印日志到日志文件
参见 <unix 环境高级编程>第13 章 精灵进程 Syslog为每个事件赋予几个不同的优先级: LOG_EMERG——紧急情况 LOG_ALERT——应该被立即改正的问题,如系统数据库 ...
- linux基础part2
linux基础 一.linux基础命令 1.pwd:用来显示当前目录位置 2.cd:用来切换目录位置.(eg:cd...cd../...cd-.cd~) 3.ls:用来查看目录或文件信息(eg:ls ...
- 小程序连接百度ai
function getTextFromImage(res) { var access_token = '24.c649256d2e*****0.282335-11449805'; var url = ...
- php类和对象(一)
对象:任何东西都可以称为对象,类实例化出来的东西类:对所有同类的对象抽象出来的东西 Info: Code,Name,Sex,Nation,Birthday对象:一条具体的信息 p001 张三 男 汉族 ...