freemarker详细教程从入门到精通(四)数据类型与方法函数
数据类型
字符串
在文本中确定字符串值的方法是看双引号,比如: "some text",或单引号,比如: 'some text'。这两种形式是等同的。 如果文本自身包含用于字符引用的引号 ( " 或 ')或反斜杠时, 应该在它们的前面再加一个反斜杠;这就是转义。 转义允许直接在文本中输入任何字符, 也包括换行。
${"It's \"quoted\" and
this is a backslash: \\"}
${'It\'s "quoted" and
this is a backslash: \\'}
将会输出:
It's "quoted" and this is a backslash:\
It's "quoted" and this is a backslash: \
下面的表格是FreeMarker支持的所有转义字符。 在字符串使用反斜杠的其他所有情况都是错误的,运行这样的模板都会失败。
转义序列 含义
\" 引号 (u0022)
\' 单引号(又称为撇号) (u0027)
\{ 起始花括号:{
\\ 反斜杠 (u005C)
\n 换行符 (u000A)
\r 回车 (u000D)
\t 水平制表符(又称为tab) (u0009)
\b 退格 (u0008)
\f 换页 (u000C)
\l 小于号:<
\g 大于号:>
\a &符:&
\xCode 字符的16进制 Unicode 码 (UCS 码)
原生字符串是一种特殊的字符串。在原生字符串中, 反斜杠和 ${ 没有特殊含义, 它们被视为普通的字符。为了表明字符串是原生字符串, 在开始的引号或单引号之前放置字母r,例如:
${r"${foo}"}
${r"C:\foo\bar"}
将会输出:
${foo}
C:\foo\bar
数字
输入不带引号的数字就可以直接指定一个数字, 必须使用点作为小数的分隔符而不能是其他的分组分隔符。 可以使用 - 或 + 来表明符号 (+ 是多余的)。 科学记数法暂不支持使用 (1E3 就是错误的), 而且也不能在小数点之前不写0(.5 也是错误的)。
下面的数字都是合法的:0.08, -5.013,8, 008,11, +11
请注意,像 08、 +8、 8.00 和 8 这样的数值是完全等同的,它们都是数字8。 所以, ${08}、${+8}、 ${8.00} 和 ${8} 的输出都是一样的。
布尔值
直接写 true 或者 false 就表示一个布尔值了,不需使用引号。
序列
指定一个文字的序列,使用逗号来分隔其中的每个 子变量, 然后把整个列表放到方括号中。例如:
<#list ["foo", "bar", "baz"] as x>
${x}
</#list>
将会输出:
foo
bar
baz
列表中的项目是表达式,那么也可以这样做: [2 + 2, [1, 2, 3, 4], "foo"]。 其中第一个子变量是数字4,第二个子变量是一个序列, 第三个子变量是字符串"foo"。
值域
值域也是序列,但它们由指定包含的数字范围所创建, 而不需指定序列中每一项。比如: 0..<m,这里假定 m 变量的值是5,那么这个序列就包含 [0, 1, 2, 3, 4]。值域的主要作用有:使用 <#list...> 来迭代一定范围内的数字,序列切分 和 字符串切分。
值域表达式的通用形式是( start 和 end 可以是任意的结果为数字表达式):
-
start..end: 包含结尾的值域。比如1..4就是[1, 2, 3, 4], 而4..1就是[4, 3, 2, 1]。当心一点, 包含结尾的值域不会是一个空序列,所以0..length-1就是 错误的,因为当长度是0时, 序列就成了[0, -1]。 -
start..<end或start..!end: 不包含结尾的值域。比如1..<4就是[1, 2, 3],4..<1就是[4, 3, 2], 而1..<1表示[]。请注意最后一个示例; 结果可以是空序列,和..<和..!没有区别; 最后这种形式在应用程序中使用了<字符而引发问题(如HTML编辑器等)。 -
start..*length: 限定长度的值域,比如10..*4就是[10, 11, 12, 13],10..*-4就是[10, 9, 8, 7],而10..*0表示[]。当这些值域被用来切分时, 如果切分后的序列或者字符串结尾在指定值域长度之前,则切分不会有问题;请参考 序列切分 来获取更多信息。 start..: 无右边界值域。这和限制长度的值域很像,只是长度是无限的。 比如1..就是[1, 2, 3, 4, 5, 6, ... ],直到无穷大。 但是处理(比如列表显示)这种值域时要万分小心,处理所有项时, 会花费很长时间,直到内存溢出应用程序崩溃。 和限定长度的值域一样,当它们被切分时, 遇到切分后的序列或字符串结尾时,切分就结束了。-
值域的进一步注意事项:
-
值域表达式本身并没有方括号,比如这样编写代码
<#assign myRange = 0..<x>, 而不是<#assign myRange = [0..<x]>。 后者会创建一个包含值域的序列。方括号是切分语法的一部分,就像seq[myRange]。 -
可以在
..的两侧编写算术表达式而不需要圆括号, 就像n + 1 ..< m / 2 - 1。 -
..,..<,..!和..*是运算符, 所以它们中间不能有空格。就像n .. <m这样是错误的,但是n ..< m这样就可以。 -
无右边界值域的定义大小是2147483647 (如果
incompatible_improvements低于2.3.21版本,那么就是0), 这是由于技术上的限制(32位)。但当列表显示它们的时候,实际的长度是无穷大。 -
值域并不存储它们包含的数字,那么对于
0..1和0..100000000来说,创建速度都是一样的, 并且占用的内存也是一样的。
-
哈希表
在模板中指定一个哈希表,就可以遍历用逗号分隔开的"键/值"对, 把列表放到花括号内即可。键和值成对出现并以冒号分隔。比如: { "name": "green mouse", "price": 150 }。 请注意名和值都是表达式,但是用来检索的名称就必须是字符串类型, 而值可以是任意类型。
检索变量
顶层变量
访问顶层的变量,可以简单地使用变量名。例如, 用表达式 user 就可以在根上获取以 "user" 为名存储的变量值。然后打印出存储在里面的内容:
${user}
如果没有顶层变量,那么 FreeMarker 在处理表达式时就会发生错误, 进而终止模板的执行(除非程序员事先配置了 FreeMarker)。
在这种表达式中,变量名只可以包含字母(也可以是非拉丁文), 数字(也可以是非拉丁数字),下划线 (_), 美元符号 ($),at符号 (@)。 此外,第一个字符不可以是ASCII码数字(0-9)。 从 FreeMarker 2.3.22 版本开始,变量名在任何位置也可以包含负号 (-),点(.)和冒号(:), 但这些必须使用前置的反斜杠(\)来转义, 否则它们将被解释成操作符。比如,读取名为"data-id"的变量, 表达式为 data\-id,因为 data-id 将被解释成 "data minus id"。 (请注意,这些转义仅在标识符中起作用,而不是字符串中。)
从哈希表中检索数据
可以使用表达式 book.author.name 来读取到auther的name。
从序列中检索数据
这和从哈希表中检索是相同的,但是只能使用方括号语法形式来进行, 而且方括号内的表达式最终必须是一个数字而不是字符串。比如animals[0].name
特殊变量
特殊变量是由FreeMarker引擎本身定义的。 使用它们,可以按照如下语法形式来进行: .variable_name。.
通常情况下是不需使用特殊变量,而对专业用户来说可能用到。
字符串操作
插值 (或连接)
如果要在字符串中插入表达式的值,可以在字符串的文字中使用 ${...} (已经废弃的 #{...})。 ${...} 在字符串中的作用和在 文本 区是相同的
使用 + 号来达到类似的效果
获取字符
在给定索引值时可以获取字符串中的一个字符,这和 序列的子变量是相似的, 比如 user[0]。
字符串切分 (子串)
可以按照 序列切分 (请参看)的相同方式来切分字符串,这就是使用字符来代替序列。不同的是:
-
降序域不允许进行字符串切分。 (因为不像序列那样,很少情况下会想反转字符串。 如果真要这样做了,那就是疏忽。)
-
如果变量的值既是字符串又是序列(多类型值), 那么切分将会对序列进行,而不是字符串。当处理XML时, 这样的值就是普通的了。此时,可以使用
someXMLnode?string[range]。 -
一个遗留的bug:值域 包含 结尾时, 结尾小于开始索引并且是是非负的(就像在
"abc"[1..0]中), 会返回空字符串而不是错误。(在降序域中这应该是个错误。) 现在这个bug已经向后兼容,但是不应该使用它,否在就会埋下一个错误。
<#assign s = "ABCDEF">
${s[2..3]}
${s[2..<4]}
${s[2..*3]}
${s[2..*100]}
${s[2..]}
将会输出:
CD
CD
CDE
CDEF
CDEF
序列操作
连接
序列的连接可以按照字符串那样使用 + 号来进行,例如:
<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
- ${user}
</#list>
请注意,不要在很多重复连接时使用序列连接操作, 比如在循环中往序列上追加项目,而这样的使用是可以的: <#list users + admins as person>。 尽管序列连接的速度很快,而且速度是和被连接序列的大小相独立的, 但是最终的结果序列的读取却比原先的两个序列慢那么一点。 通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。
序列切分
使用 seq[range], 这里 range 是一个值域 , 就可以得到序列的一个切分。此外,切分后序列中的项会和值域的顺序相同。 值域中的数字必须是序列可使用的合法索引, 否则模板的处理将会终止并报错。
哈希表操作
连接
像连接字符串那样,也可以使用 + 号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在 + 号右侧的哈希表中的项优先。例如:
<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}
请注意,很多项连接时不要使用哈希表连接, 比如在循环时往哈希表中添加新项。这和序列连接 的情况是一致的。
算数运算
算数运算包含基本的四则运算和求模运算,运算符有:
- 加法:
+ - 减法:
- - 乘法:
* - 除法:
/ - 求模 (求余):
%
比较运算
有时我们需要知道两个值是否相等,或者哪个值更大一点。
为了演示具体的例子,我们在这里使用 if 指令。 if 指令的用法是: <#if expression>...</#if>, 其中的表达式的值必须是布尔类型,否则将会出错,模板执行中断。 如果表达式的结果是 true , 那么在开始和结束标记内的内容将会被执行,否则就会被跳过。
逻辑操作
常用的逻辑操作符:
- 逻辑 或:
|| - 逻辑 与:
&& - 逻辑 非:
!
逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。
内建函数
在页面上也可以多次使用指令,而且指令间也可以很容易地相互嵌套。 比如,在 list 指令中嵌套 if 指令:
<#list animals as animal>
<div<#if animal.protected>class="protected"</#if>>
${animal.name} for ${animal.price} Euros
</div>
</#list>
请注意,FreeMarker并不解析FTL标签以外的文本、插值和注释, 上面示例在HTML属性中使用FTL标签也不会有问题。
内建函数很像子变量(如果了解Java术语的话,也可以说像方法), 它们并不是数据模型中的东西,是 FreeMarker 在数值上添加的。 为了清晰子变量是哪部分,使用 ?(问号)代替 .(点)来访问它们。常用内建函数的示例:
-
user?html给出user的HTML转义版本, 比如&会由&来代替。 -
user?upper_case给出user值的大写版本 (比如 "JOHN DOE" 来替代 "John Doe") -
animal.name?cap_first给出animal.name的首字母大写版本(比如 "Mouse" 来替代 "mouse") -
user?length给出user值中 字符的数量(对于 "John Doe" 来说就是8) -
animals?size给出animals序列中 项目 的个数(我们示例数据模型中是3个) -
如果在
<#list animals as animal>和对应的</#list>标签中:-
animal?index给出了在animals中基于0开始的animal的索引值 -
animal?counter也像index, 但是给出的是基于1的索引值 -
animal?item_parity基于当前计数的奇偶性,给出字符串 "odd" 或 "even"。在给不同行着色时非常有用,比如在<td class="${animal?item_parity}Row">中。
-
一些内建函数需要参数来指定行为,比如:
-
animal.protected?string("Y", "N")基于animal.protected的布尔值来返回字符串 "Y" 或 "N"。 -
animal?item_cycle('lightRow','darkRow')是之前介绍的item_parity更为常用的变体形式。 -
fruits?join(", ")通过连接所有项,将列表转换为字符串, 在每个项之间插入参数分隔符(比如 "orange,banana") -
user?starts_with("J")根据user的首字母是否是 "J" 返回布尔值true或false。
内建函数应用可以链式操作,比如user?upper_case?html 会先转换用户名到大写形式,之后再进行HTML转义。(这就像可以链式使用 .(点)一样)
方法调用
如果有一个方法,那么可以使用方法调用操作。 方法调用操作是使用逗号来分割在括号内的表达式而形成参数列表,这些值就是参数。 方法调用操作将这些值传递给方法,然后返回一个结果。 这个结果就是整个方法调用表达式的值。
处理不存在的值
默认值操作符
使用形式: unsafe_expr!default_expr 或 unsafe_expr! or (unsafe_expr)!default_expr 或 (unsafe_expr)!
这个操作符允许你为可能不存在的变量指定一个默认值。
默认值可以是任何类型的表达式,也可以不必是字符串。 也可以这么写:hits!0 或 colors!["red", "green", "blue"]。 默认值表达式的复杂程度没有严格限制,还可以这么来写: cargo.weight!(item.weight * itemCount + 10)。
不存在值检测操作符
使用形式: unsafe_expr?? 或 (unsafe_expr)??
这个操作符告诉我们一个值是否存在。基于这种情况, 结果是 true 或 false。
赋值操作符
这些并不是表达式,只是复制指令语法的一部分,比如 assign, local 和 global。 照这样,它们不能任意被使用。
<#assign x += y> 是 <#assign x = x + y> 的简写,<#assign x *= y> 是 <#assign x = x * y>的简写等等。。。
<#assign x++> 和 <#assign x += 1> (或 <#assign x = x + 1>)不同,它只做算术加法运算 (如果变量不是数字的话就会失败),而其它的是进行字符串,序列连接和哈希表连接的重载。 <#assign x--> 是 <#assign x -= 1> 的简写。
括号
括号可以用来给任意表达式分组。
表达式中的空格
FTL 忽略表达式中的多余的 空格。
操作符的优先级
下面的表格显示了已定义操作符的优先级。 表格中的运算符按照优先程度降序排列:上面的操作符优先级高于它下面的。 高优先级的运算符执行要先于优先级比它低的。表格同一行上的两个操作符优先级相同。 当有相同优先级的二元运算符(运算符有两个''参数'',比如 +和-)挨着出现时,它们按照从左到右的原则运算。
运算符组 运算符 最高优先级运算符 [subvarName] [subStringRange] . ? (methodParams) expr! expr?? 一元前缀运算符 +expr -expr !expr 乘除法,求模运算符 * / % 加减法运算符 + - 数字值域 .. ..< ..! ..* 关系运算符 < > <= >= (and equivalents: gt, lt, etc.) 相等,不等运算符 == != (and equivalents: =) 逻辑 "与" 运算符 && 逻辑 "或" 运算符 ||
子程序
方法和函数
当一个值是方法或函数的时候,那么它就可以计算其他值,结果取决于传递给它的参数。
这部分是对程序员来说的:方法/函数是一等类型值, 就像函数化的编程语言。也就是说函数/方法也可以是其他函数/方法的参数或者返回值, 并可以把它们定义成变量等。
假设程序员在数据模型中放置了一个方法变量 avg, 该变量用来计算数字的平均值。如果给定3和5作为参数,访问 avg 时就能得到结果4。
方法的使用将会在 后续章节 中进行解释, 下面这个示例会帮助我们理解方法的使用:
The average of 3 and 5 is: ${avg(3, 5)}
The average of 6 and 10 and 20 is: ${avg(6, 10, 20)}
The average of the price of a python and an elephant is:
${avg(animals.python.price, animals.elephant.price)}
将会输出:
The average of 3 and 5 is: 4 The average of 6 and 10 and 20 is: 12 The average of the price of a python and an elephant is: 4999.5
那么方法和函数有什么区别呢?这是模板作者所关心的, 它们没有关系,但也不是一点关系都没有。 方法是来自于数据模型 (它们反射了Java对象的方法) 而函数是定义在模板内的 (使用 function 指令 -- 也是高级话题),但二者可以用同一种方式来使用。
用户自定义指令
这种类型的值可以作为用户自定义指令(换句话说,就是FreeMarker的标签) 用户自定义指令是一种子程序,一种可以复用的模板代码段。但这也是一个高级话题, 将会在 后续章节 中进行解释。
这部分是对程序员来说的: 用户自定义指令(比如宏)也是一等值类型,就像函数/方法一样。
这里仅仅对用户自定义指令有一个认识即可(如果现在还不能理解可以先忽略它)。 假设现在有一个变量 box,它的值是用户自定义的指令, 用来打印一些特定的HTML信息,包含标题和一条信息。那么, box 变量就可以在模板中使用(示例如下):
<@box title="Attention!"> Too much copy-pasting may leads to maintenance headaches. </@box>

浙公网安备 33010602011771号