@yacent
2016-07-11T11:45:48.000000Z
字数 12177
阅读 1469
网易|前端
Freemarker是一款模板引擎:即一种基于模板和要改变的数据,并用生来成输出文本(eg: HTML等)的通用工具。
简单点来说,模板和数据模型是FreeMarker来生成输出所必须的组成部分:模板 + 数据类型 = 输出
an example
Freemarker使用的数据模型实际上是实现了 freemarker.template.TemplateModel
接口的对象,后台传入的数据模型通常会被内部转换成 TemplateModel
类型的对象,该功能叫做 object wrapping
,即实例代码中的高级功能,设置wrapper,设置configuration的包装类的读取方式。cfg.setObjectWrapper(ObjectWrapper.BEANS_WRAPPER)
要成功转换对象,必须选取适合的包裹类,TemplateModel主要有三个子类,每一个子类分别表示一种FreeMarker的基本类型,有 TemplateHashModel
、TemplateSequenceModel
、TemplateNumberModel
标量:不可变类,赋值后不可改变
+ Boolean
+ Number
+ String
+ Date
容器类型:
+ Hashes
+ Sequences
+ Collections
方法变量:
可以写在root当中直接写入方法
转换器变量(很少使用)
节点变量(很少使用)
bla bla 不需要了解太多,会常用的匹配就好
基本组成部分
插值(interpolations):${...}
注释(Comments):<#-- -->
FTL tags标签(区分大小写):一般以符号#开头,用户自定义的用 @
Directives指令:if else | list | inlcude
处理不存在的变量:<#if user??><h1>Welcome{$user}!</h1></#if>
指令(注意区分 模板自带和自定义的符号的不同)
标签:<#directivename parameters>
自定义标签:<@mydirective>
<#assign ages = ["Joe":23, "Fred":25]>
- Joe is ${ages.Joe}
</#assign>
表达式
当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式
eg: ${r"${foo}"}
,在字符串前添加r传入表达式
1) 字符串操作:插值 或 连接
${"hello ${user}!"}
${"Hello " + user + "!"}
2) 序列操作
<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
- ${user}
</#list>
3) 哈希表操作
连接: + 如果两个哈希表含有键相同的项,那么在+号右侧的哈希表中的项目优先
<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}
</#assign>
4) 算数运算: + - * / %
${3 + "5"} -> 35
${1.6?int} 使用内建函数 int来解决
5) 比较运算符: = != < > <= >=
<#if (x >y)>
6) 内建函数:使用内建函数的语法和访问哈希表子变量的语法很像,除了使用?号来代替点,其他都一样 test?upper_case?html
字符串的内建函数
html: 字符串中所有的特殊HTML字符都需要用实体来代替
cap_first: 字符串第一个字母变大写
lower_case:字符串的小写形式
upper_case:字符串的大写形式
trim:去掉字符串首尾的空格
序列的内建函数
size: 序列中元素的个数
数字的内建函数
int: 数字的整数部分
7) 方法调用(参数、返回值):${repeat("What", "3")}
8) 不存在的值
检测不存在的值:
unsafe_expr?? 或 (unsafe_expr)??
处理不存在的值
unsafe_expr!default_expr
! 的优先级特别低,所以写的时候尽量带上括号
`${(x!1) + y}` 即表示 如果x存在,则使用x,x不存在则使用y
自定义指令
a)无参数:
<#macro greet>
<font size="+2">Hello Joe!</font>
</#macro>
<@greet></@greet>
或
<@greet/>
b)有参数:
<#macro greet person color>
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro><@greet person="Fred" color="black"/>
默认值:
<#macro greet person color="black">
<font size="+2" color="${color}">Hello ${person}!</font>
</#macro>
<@greet person="Fred"/>
宏参数注意点:
someParam=foo和someParam="${foo}"是不同的 数值/字符串
宏参数是局部变量
c)嵌套内容
<#macro border>
<table border=4 cellspacing=0 cellpadding=4>
<tr><td>
<#nested>
</td></tr>
</table>
</#macro>
<@border>The bordered text</@border>
=>
<table border=4 cellspacing=0 cellpadding=4><tr><td>
The bordered text
</td></tr></table>
nested指令也可以多次被调用
嵌套的内容可以是任意有效的FTL,包含其他的用户自定义指令
在嵌套的内容中,宏的局部变量是不可见的
不同的局部变量的设置是为每个宏自己调用的,所以不会导致混乱
d)宏和循环变量
循环变量的名称是在自定义指令的开始标记(<@...>)的参数后面通过分号确定的, 一个宏可以使用多个循环变量
<#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>
如果在分号之后指定的循环变量少,那么就看不到nested指令提供的最后的值
如果在分号后面指定了比nested指令还多的变量,那么最后的循环变量将不会被创建
在模板中定义变量
简单变量 <#assign x = "plain">
局部变量 <#local x = "local">
循环变量 <#list ["loop"] as x>
局部变量也会隐藏(不是覆盖)同名的简单变量。循环变量也会隐藏(不是覆盖)同名的局部变量和简单变量
内部循环变量可以隐藏外部循环变量
有时会发生一个变量隐藏数据模型中的同名变量,但是如果想访问数据模型中的变量,就可以使用特殊变量globals ??
<#assign user = "Joe Hider">
${user}
打印: Joe Hider
${.globals.user}
打印: Big Joe
命名空间
a) 创建一个库:
<#macro copyright date>
<p>Copyright (C) ${date} Julia Smith. All rights reserved.
<br>Email: ${mail}</p>
</#macro>
<#assign mail = "jsmith@acme.com">
<#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
b) 在引入的命名空间上编写变量:
<#import "/lib/my_test.ftl" as my>
${my.mail}
<#assign mail="jsmith@other.com" in my>
${my.mail}
c) 命名空间和数据模型
数据模型中的变量在任何位置都是可见的
在模板的命名空间(可以使用assign或macro指令来创建的变量)中的变量有着比数据模型中的变量更高的优先级。因此,数据模型的内容不会干涉到由库创建的变量。
d) 命名空间的生命周期
命名空间由使用的import指令中所写的路径来识别。如果想多次import这个路径,那么只会为第一次的import引用创建命名空间执行模板。后面相同路径的import只是创建一个哈希表当作访问相同命名空间的“门”。
<#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}
通过my,foo和bar访问相同的命名空间。
空白处理
a) 忽略某些模板文件的空白的工具: 剥离空白(默认开启)
b) 从输出中移除空白的工具: compress指令
<#compress>...</#compress>
<@compress single_line=true>...</@compress>
//设置single_line属性,这将会移除所有的介于其中的换行符
替换(方括号)语法
用[#ftl]来开始模板,要记住这个要放在文件的最前面(除了它前面的空格)
2.4版本中的默认配置将会自动检测,也就是说第一个FreeMarker标签决定了语法形式(它可以是任意的,而不仅仅是ftl)。
说明: 加粗为内建函数名
substring 取子串:
exp?substring(from, toExclusive) , exp?substring(from)
cap_first: 字符串中的第一个单词的首字母大写
exp?cap_first 例如 ${" green mouse"?cap_first}
uncap_first 首字母小写
capitalize 字符串的所有单词首字母大写,其余小写
${"GreEN mouse"?capitalize} -> Green Mouse
chop_linebreak 切断换行符: 如果在末尾没有换行符的字符串,那么可以换行,否则不改变字符串
date,time,datetime 日期,时间,时间日期, 字符串转换成日期值, 建议指定一个确定格式参数:
<#assign test1 = "10/25/1995"?date("MM/dd/yyyy")>
<#assign test2 = "15:05:30"?time("HH:mm:ss")>
<#assign test3 = "1995-10-25 03:05 PM"?datetime("yyyy-MM-dd hh:mm a")>
ends_with 以…结尾, 返回是否这个字符串以指定的子串结尾
html HTML格式的转义文本, 字符串按照HTML标记输出: 注意在HTML页面中,通常你想对所有插值使用这个内建函数, 所以你可以使用escape指令来节约很多输入,减少偶然错误的机会。
group 分组, 这个函数只和内建函数matches的结果使用(matches 匹配)
index_of 索引所在位置, 返回第一次字符串中出现子串时的索引位置, 你可以指定开始搜索的索引位置
将"abcabc"?index_of("bc", 2)会返回4
last_index_of 最后的索引所在位置, 返回最后一次(最右边)字符串中出现子串时的索引位置:
例如"abcabc"?last_index_of("ab"):将会返回3。而且你可以指定开始搜索的索引。"abcabc"?last_index_of("ab", 2),将会返回0。要注意第二个参数暗示了子串开始的最大索引
length 字符串长度
lower_case 小写形式, 字符串全部字符小写
left_pad 距左边:
如果它仅仅用1个参数,那么它将在字符串的开始插入空白,直到整个串的长度达到参数指定的值, [${"a"?left_pad(5)}]
如果使用了两个参数,那么第二个参数指定用什么东西来代替空白字符;第二个参数也可以是个长度比1大的字符串,那么这个字符串会周期性的插入
right_pad 距右边:和left_pad相同,但是它从末尾开始插入字符而不是从开头
contains 包含: 如果函数中的参数可以作为源字符串的子串,那么返回true
number 数字格式: 字符串转化为数字格式
replace 替换:
在源字符串中,用另外一个字符串来替换源字符串中出现它的部分。它不处理词的边界
如果第一个参数是空字符串,那么所有的空字符串将会被替换,比如"foo"?replace("","|")
,就会得到"|f|o|o|"
。
replace接受一个可选的标记参数,作为第三个参数。
rtf 富文本:
字符串作为富文本(RTF 文本),也就是说,下列字符串:\替换为
, {替换为\{ , }替换为\}
url URL转义: 在URL之后的字符串进行转义
注意它会转义所有保留的URL字符(/,=,&等),所以编码可以被用来对查询参数的值进行,比如:Click here...
split 分割: 根据另外一个字符串的出现将原字符串分割成字符串序列,
split函数接受一个可选的标记参数作为第二个参数。
starts_with 以…开头: 如果字符串以指定的子字符串开头,那么返回true
e.g. "redhead"?starts_with("red")
string 返回和其内容一致的字符串
trim 去掉字符串首尾的空格
upper_case 字符串的大写形式
word_list 词列表: 包含字符串词的列表,并按它们在字符串中的顺序出现。词是连续的字符序列,包含任意字符,但是不包括空格
xhtml XHTML格式: 字符串作为XHTML格式文本输出
<替换为<\;
>替换为>\;
&替换为&\;
"替换为"\;
'替换为'\;
xml XML格式: 字符串作为XML格式文本输出
<替换为<\;
>替换为>\;
&替换为&\;
"替换为"\;
'替换为&apos\;
通用标记(*):
i:大小写不敏感
f:仅第一个;替换/查找等,只争对第一次出现的那个
r:查找的子串是正则表达式
m:正则表达式多行模式
s:启用正则表达式的do-tall模式
c:在正则表达式中许可空白和注释
c 数字转字符:${x?c}
string 数字转字符串:
有四种预定义的数字格式computer,currency,number和percent; ${x?string.number}; ${x?string("0.####")};
round,floor,ceiling 数字的舍入处理:
${result?floor}
这些内建函数在分页处理时也许有用
string(当用于日期值时)日期转字符串
预定义的格式是short,medium,long和full;
${lastUpdated?string.long}
${lastUpdated?datetime?string.short}
date,time,datetime: 用来指定日期变量中的哪些部分被使用
<#assign x = openingTime> //no problem can occur here
${openingTime?time} // without ?time it would fail
// For the sake of better understanding, consider this:
<#assign openingTime = openingTime?time>
${openingTime} // this will work now
iso_... 内建函数族:这些内建函数转换日期,时间或时间日期值到字符串,并遵循ISO 8601的扩展格式
string(当被用作是布尔值时) 转换布尔值为字符串:
foo?string
,true被翻译为"true",而false被翻译为"false";
foo?string("yes", "no")
:如果布尔值是true,这会返回第一个参数(这里是:"yes"),否则就返回第二个参数(这里是:"no")。注意返回的的值是一个字符串;如果参数是数字类型,首先它会被转换成字符串
first 序列的第一个子变量:
last 序列的最后一个子变量:
seq_contanis 序列包含…
辨别序列中是否包含指定值,和contains区分开(查找字符串中是否存在某子串);
<#assign x = ["red", 16, "blue", "cyan"]>
"blue": ${x?seq_contains("blue")?string("yes", "no")}
seq_index_of 第一次出现…时的位置
返回序列中第一次出现该值时的索引位置,如果序列不包含指定的值时返回-1;搜索开始的地方可以由第二个可选的参数来确定;和index_of区分开(针对字符串函数);
<#assign colors = ["red", "green", "blue"]>
${colors?seq_index_of("blue")} // 2
<#assign names = ["Joe", "Fred", "Joe", "Susan"]>
No 2nd param:
${names?seq_index_of("Joe")} // 0
${names?seq_index_of("Joe", 1)} // 2
seq_last_index_of 最后一次出现..的位置:
返回序列中最后一次出现值的索引位置,如果序列不包含指定的值时返回-1;支持可选的第二个参数来确定从哪里开始搜索的索引位置;和last_index_of区分开;
reverse 反转序列:返回序列的反序形式
size 序列大小
序列中子变量的数量(作为一个数值)。假设序列中至少有一个子变量,那么序列s中最大的索引是s?size - 1(因为第一个子变量的序列是0)。
sort 排序:以升序方式返回。
(要使用降序排列时,使用它之后还要使用reverse内建函数)这仅在子变量都是字符串时有效(通常是大小写不敏感的),或者子变量都是数字/日期值/布尔值
sort_by 以…来排序
返回由给定的哈希表子变量来升序排序的哈希表序列(要降序排列使用这个内建函数后还要使用reverse内建函数)
<#assign ls = [
{"name":"whale", "weight":2000},
{"name":"Barbara", "weight":53},
{"name":"zeppelin", "weight":-200},
{"name":"aardvark", "weight":30},
{"name":"beetroot", "weight":0.3}
]>
Order by name:
<#list ls?sort_by("name") as i>
- ${i.name}: ${i.weight}
</#list>
<#assign members = [
{"name": {"first": "Joe", "last": "Smith"}, "age": 40},
{"name": {"first": "Fred", "last": "Crooger"}, "age": 35},
{"name": {"first": "Amanda", "last": "Fox"}, "age": 25}
]>
Sorted by name.last:
<#list members?sort_by(['name', 'last']) as m>
- ${m.name.last}, ${m.name.first}: ${m.age} years old
</#list>
chunk 区块
分割序列到多个 大小为函数的第一个参数给定的 序列,(就像mySeq?chunk(3))。结果是包含这些序列的一个序列。
最后一个序列可能比给定的大小要小,除非第二个参数也给定了(比如mySeq?chunk(3,'-')),那个'-'就是用来填充最后一个序列,以达到给定的大小。
keys 键的集合:一个包含哈希表中 '查找到的键' 的序列
<#assign h = {"name":"mouse", "price":50}>
<#assign keys = h?keys>
<#list keys as key>${key} = ${h[key]}; </#list>
=> name = mouse; price = 50;
values 值的集合: 一个包含哈希表中 '子变量' 的序列
children 子节点序列:
一个包含该节点所有子节点(也就是直接后继节点)的序列
parent 父节点:
返回该节点的直接父节点
root 根节点:
ancestors 祖先节点:
一个包含节点的所有祖先节点的序列,以直接父节点开始,以根节点结束
node_name 节点名称:
node_type 节点类型
node_namespace 节点命名空间
在特殊情况下(调试,高级宏)它们会有用。如果你需要在普通页面模板中使用这些函数,你可能会重新访问数据模型,所以你不要使用它们。
说明: 加粗为内建函数名
if,else,elseif
指令:
switch,case,default,break
指令:
list,break
指令:
a)在list循环中,有两个特殊的循环变量可用:
item_index:这是一个包含当前项在循环中的步进索引的数值。
item_has_next:来辨别当前项是否是序列的最后一项的布尔值。
b)你可以使用list在两个数字中来计数,使用一个数字范围序列表达式
<#assign x=3>
<#list 1..x as i>
${i}
</#list> -> 1 2 3
include指令:
<#include path>
or
<#include path options> options: parse, encoding
a) 获得机制: *
b) 本地化查找: e.g. en_US
import 指令:
<#import path as hash>
noparse指令:
<#noparse> ...</#noparse>
FreeMarker不会在这个指令体中间寻找FTL标签,插值和其他特殊的字符序列
compress指令:
压缩指令对于移除多余的空白是很有用的(缩小所有不间断的空白序列到一个单独的空白字符。如果被替代的序列包含换行符或是一段空间,那么被插入的字符也会是一个换行符,开头和结尾的不间断的空白序列将会完全被移除)
<#compress>...</#compress>
escape,noescape指令:
<#escape identifier as expression> // <#escape x as x?html>
...
<#noescape>...</#noescape>
// 有时需要暂时为一个或两个在转义区块中的插值关闭转义
...
</#escape>
assign 指令:
<#assign name=value>
or
<#assign name1=value1 name2=value2 ... nameN=valueN>
or
<#assign same as above... in namespacehash>
or
<#assign name>
// assign的极端使用是它捕捉它的开始标记和结束标记中间生成的输出。也就是说,在标记之间打印的东西将不会在页面上显示,但是会存储在变量name中
capture this
</#assign>
or
<#assign name in namespacehash>
capture this
</#assign>
global 指令:
这个指令和assign相似,但是被创建的变量在所有的命名空间中都可见,但又不会存在于任何一个命名空间之中。如果在当前的命名空间中,一个相同名称的变量存在的话,那么会隐藏由global指令创建的变量。
这种情形下,你可以使用特殊变量globals,比如${.globals.x}
,注意使用globals你看到所有全局可访问的变量,不但由global指令创建的变量
<#global name=value>
or
<#global name1=value1 name2=value2 ... nameN=valueN>
or
<#global name>
capture this
</#global>
local 指令:
同global,但是它创建或替换局部变量,这仅仅在宏和方法的内部定义才会有作用。
setting 指令:
<#setting name=value>
,如 locale,number_format,boolean_format,...
用户自定义指令(<@...>):
<@user_def_dir_exp param1=val1 param2=val2 ... paramN=valN/>
// 如果你需要循环变量,
<@user_def_dir_exp param1=val1 param2=val2 ... paramN=valN ; lv1, lv2, ..., lvN/>
// 或者和上面两个相同但是使用结束标签,
<@user_def_dir_exp ...>...</@user_def_dir_exp>
// 或
<@user_def_dir_exp ...>...</@>
// 或和上面的相同但是使用位置参数传递,
<@user val1, val2, ..., valN/>
// 位置参数传递(如<@heading "Preface", 1/>)是正常命名参数传递(如<@heading title="Preface" level=1/>)的速记形式,这里忽略了参数的名称
macro,nested,return 指令:
<#macro name param1 param2 ... paramN>
...
<#nested loopvar1, loopvar2, ..., loopvarN>
...
<#return>
...
</#macro>
function,return 指令:
这个指令和macro指令的工作方式一样,除了return指令必须有一个参数来指定方法的返回值,
<#function name param1 param2 ... paramN>
...
<#return returnValue>
...
</#function>
flush 指令:
stop 指令:
ftl 指令:
t,lt,rt 指令:
这些指令在行内的放置不重要, 也就是说,不管你是将它们放在行的开头,或是行的末尾,或是在行的中间,效果都是一样的。
<#t>
<#lt>
<#rt>
<#nt> // 这个指令关闭行中出现的空白削减
attempt,recover 指令:
<#attempt>
attempt block
// 这是会被执行的,但是如果期间发生了错误,那么这块内容的输出将会回滚; 之后recover block就会被执行
<#recover>
recover block
// 仅在attempt block执行期间发生错误时被执行,可以在这里打印错误信息或其他操作
</#attempt>
visit,recurse,fallback 指令:
visit和recurse指令是用来递归处理树的; 在实践中,这通常被用来处理XML。
<#visit node using namespace>
// 或
<#visit node>
<#recurse node using namespace>
// 或
<#recurse node>
// 或
<#recurse using namespace>
// 或
<#recurse>
<#fallback>
visit:
a) 如果visit既没有在和之前描述规则的名称相同名字(node_name)的FTL命名空间发现自定义指令,
b) 那么它会尝试用名称@node_type来查找,
c) 又如果节点不支持节点类型属性(也就是node?node_type返回未定义变量),那么使用名称@default。
d) 如果仍然没有找到处理节点的自定义指令,那么visit停止模板执行,并抛出错误。
recurse: 它访问节点的所有子节点(而没有节点本身)
<#recurse someNode using someLib> 等价于 <#list someNode?children as child><#visit child using someLib>
fallback:
特殊变量是由FreeMarker引擎自己定义的变量。要访问它们,你可以使用.variable_name
语法。比如,你不能仅仅写version
,而必须写.version
支持的特殊变量有:
globals(.globals),language(.lang),now( The current time is ${.now?time} )
等
true:布尔值“true”
false:布尔值“false”
gt:比较运算符“大于”
gte:比较运算符“大于或等于”
lt:比较运算符“小于”
lte:比较运算符“小于或等于”
as:由少数指令使用
in:由少数指令使用
using:由少数指令使用