FreeMarker基础入门知识2 -表达式

1:表达式


当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。 例如,我们设x为8,y为5,那么 (x + y)/2 的值就会被处理成数字类型的值6.5。

在我们展开细节之前,先来看一些具体的例子:

  • 当给插值提供值时:插值的使用方式为 ${expression}, 把它放到你想输出文本的位置上,然后给值就可以打印出来了。 即 ${(5 + 8)/2} 会打印出 ''6.5'' 来 (如果输出的语言不是美国英语,也可能打印出''6,5''来)。

  • 当给指令参数提供值时:在入门章节我们已经看到 if 指令的使用了。这个指令的语法是:<#if expression>...</#if>。 这里的表达式计算结果必须是布尔类型的。比如 <#if 2 < 3> 中的 2 <3 (2小于3)是结果为 true 的布尔表达式。

快速浏览(备忘单)

这里给已经了解 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: \
Note:

这里当然可以直接在模板中输入文本而不需要 ${...}。 但是我们在这里用它只是为了示例来说明表达式的使用。

下面的表格是FreeMarker支持的所有转义字符。 在字符串使用反斜杠的其他所有情况都是错误的,运行这样的模板都会失败。

转义序列 含义
\" 引号 (u0022)
\' 单引号(又称为撇号) (u0027)
\{ 起始花括号:{
\\ 反斜杠 (u005C)
\n 换行符 (u000A)
\r 回车 (u000D)
\t 水平制表符(又称为tab) (u0009)
\b 退格 (u0008)
\f 换页 (u000C)
\l 小于号:<
\g 大于号:>
\a &符:&
\xCode 字符的16进制 Unicode 码 (UCS 码)

在 \x 之后的 Code 是1-4位的16进制码。下面这个示例中都是在字符串中放置版权符号: "\xA9 1999-2001", "\x0A9 1999-2001""\x00A9 1999-2001"。 如果紧跟16进制码后一位的字符也能解释成16进制码时, 就必须把4位补全,否则FreeMarker就会误解你的意图。

请注意,字符序列 ${ (和 #{) 有特殊的含义,它们被用做插入表达式的数值(典型的应用是变量的值: "Hello ${user}!")。这将在 后续章节中解释。 如果想要打印 ${ 或 #{, 就要使用下面所说的原生字符串,或者进行转义。就像 "foo $\{bar}"中的 {

原生字符串是一种特殊的字符串。在原生字符串中, 反斜杠和 ${ 没有特殊含义, 它们被视为普通的字符。为了表明字符串是原生字符串, 在开始的引号或单引号之前放置字母r,例如:

${r"${foo}"}
${r"C:\foo\bar"}

将会输出:

${foo}
C:\foo\bar

数字

输入不带引号的数字就可以直接指定一个数字, 必须使用点作为小数的分隔符而不能是其他的分组分隔符。 可以使用 - 或 + 来表明符号 (+是多余的)。 科学记数法暂不支持使用 (1E3 就是错误的), 而且也不能在小数点之前不写0(.5 也是错误的)。

下面的数字都是合法的:0.08, -5.0138, 00811, +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 表示[]。当这些值域被用来切分时, 如果切分后的序列或者字符串结尾在指定值域长度之前,则切分不会有问题;请参考 序列切分 来获取更多信息。

    Note:

    限定长度的值域是在 FreeMarker 2.3.21版本中引入的。

  • start..: 无右边界值域。这和限制长度的值域很像,只是长度是无限的。 比如 1.. 就是 [1, 2, 3, 4, 5, 6, ... ],直到无穷大。 但是处理(比如列表显示)这种值域时要万分小心,处理所有项时, 会花费很长时间,直到内存溢出应用程序崩溃。 和限定长度的值域一样,当它们被切分时, 遇到切分后的序列或字符串结尾时,切分就结束了。

    Warning!

    无右边界值域在 FreeMarker 2.3.21 版本以前只能用于切分, 若用于其它用途,它就像空序列一样了。要使用新的特性, 使用 FreeMarker 2.3.21 版本是不够的,程序员要设置incompatible_improvements 至少到2.3.21版本。

值域的进一步注意事项:

  • 值域表达式本身并没有方括号,比如这样编写代码 <#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"。 (请注意,这些转义仅在标识符中起作用,而不是字符串中。)

从哈希表中检索数据

如果有一个表达式的结果是哈希表, 那么我们可以使用点和子变量的名字得到它的值, 假设我们有如下的数据模型:

(root)
 |
 +- book
 |   |
 |   +- title = "Breeding green mouses"
 |   |
 |   +- author
 |       |
 |       +- name = "Julia Smith"
 |       |
 |       +- info = "Biologist, 1923-1985, Canada"
 |
 +- test = "title"

现在,就可以通过book.title 来读取 title,book表达式将返回一个哈希表 (就像上一章中解释的那样)。按这种逻辑进一步来说,我们可以使用表达式 book.author.name 来读取到auther的name。

如果我们想指定同一个表达式的子变量,那么还有另外一种语法格式: book["title"]。在方括号中可以给出任意长度字符串的表达式。 在上面这个数据模型示例中还可以这么来获取title: book[test]。 下面这些示例它们含义都是相等的: book.author.namebook["author"].name, book.author.["name"], book["author"]["name"]

当使用点式语法时,顶层变量名的命名也有相同的限制 (命名时只能使用字母,数字,_$, @,但是不能使用 0-9开头, 同时,从2.3.22版本开始,也可以使用 \-\. 和 \:)。当使用方括号语法时,则没有这样的限制, 因为名称可以是任意表达式的结果。(请注意,对于FreeMarker的XML支持来说, 如果子变量名称是 * (星号) 或者 **,那么就不要使用方括号语法。)

对于顶层变量来说,如果尝试访问一个不存在的变量也会引起错误导致解析执行模板中断 (除非程序员事先配置过FreeMarker)。

从序列中检索数据

这和从哈希表中检索是相同的,但是只能使用方括号语法形式来进行, 而且方括号内的表达式最终必须是一个数字而不是字符串。比如,要从示例数据模型 中获取第一个动物的名字 (记住第一项数字索引是0而不是1),可以这么来写: animals[0].name

特殊变量

特殊变量是由FreeMarker引擎本身定义的。 使用它们,可以按照如下语法形式来进行: .variable_name。.

通常情况下是不需使用特殊变量,而对专业用户来说可能用到。 所有特殊变量的说明可以参见 参考手册

字符串操作

插值 (或连接)

如果要在字符串中插入表达式的值,可以在字符串的文字中使用 ${...} (已经废弃的 #{...})。 ${...} 在字符串中的作用和在 文本 区是相同的 (它遵守相同的 本地化敏感 的数字和日期/时间格式), 而不是 自动转义

示例 (假设user是 ''Big Joe''):

<#assign s = "Hello ${user}!">
${s} <#-- Just to see what the value of s is -->

将会输出:

Hello Big Joe!
Warning!

用户所犯的一个常见错误是将插值放在了不需要/不应该使用的地方。 插值  在 文本 区 中有效。(比如, <h1>Hello ${name}!</h1>) 还有在字符串值中 (比如, <#include "/footer/${company}.html">)。 典型的 错误 使用是 <#if ${big}>...</#if>, 这会导致语法错误。简单写为 <#if big>...</#if>即可。 而且, <#if "${big}">...</#if> 也是 错误的, 因为它将参数值转换为字符串,但是 if 指令只接受布尔值, 那么这将导致运行时错误。

另外,也可以使用 + 号来达到类似的效果:

<#assign s = "Hello " + user + "!">

这样的效果和使用 ${...} 是一样的。

Warning!

因为 + 和使用 ${...} 的规则相同,附加的字符串受到 localenumber_formatdate_format, time_formatdatetime_format 和 boolean_format等等设置的影响, 这是对人来说的,而不是通常机器的解析。默认情况下,这会导致数字出问题, 因为很多地区使用分组(千分位分隔符),那么 "someUrl?id=" + id 就可能会是"someUrl?id=1 234"。 要预防这种事情的发生,请使用 ?c (对计算机来说)内建函数,那么在"someUrl?id=" + id?c 或 "someUrl?id=${id?c}"中, 就会得到如 "someUrl?id=1234"这样的输出, 而不管本地化和格式的设置是什么。

获取字符

在给定索引值时可以获取字符串中的一个字符,这和 序列的子变量是相似的, 比如 user[0]。这个操作执行的结果是一个长度为1的字符串, FTL并没有独立的字符类型。和序列中的子变量一样,这个索引也必须是数字, 范围是从0到字符串的长度,否则模板的执行将会发生错误并终止。

由于序列的子变量语法和字符的getter语法冲突, 那么只能在变量不是序列时使用字符的getter语法(因为FTL支持多类型值,所以它是可能的), 这种情况下使用序列方式就比较多。(为了变通,可以使用 内建函数 string ,比如 user?string[0]。不必担心你不理解这是什么意思, 内建函数将会在后续章节中讨论。)

示例(假设 user 是 "Big Joe"):

${user[0]}
${user[4]}

将会输出(请注意第一个字符的索引是0):

B
J

字符串切分 (子串)

可以按照 切分序列 (请参看)的相同方式来切分字符串,这就是使用字符来代替序列。不同的是:

  • 降序域不允许进行字符串切分。 (因为不像序列那样,很少情况下会想反转字符串。 如果真要这样做了,那就是疏忽。)

  • 如果变量的值既是字符串又是序列(多类型值), 那么切分将会对序列进行,而不是字符串。当处理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
Note:

下面包括了一些使用内建函数来方便字符串切分的典型用例: remove_beginningremove_ending, keep_before, keep_after, keep_before_lastkeep_after_last

序列操作

连接

序列的连接可以按照字符串那样使用 + 号来进行,例如:

<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
- ${user}
</#list>

将会输出:

- Joe
- Fred
- Julia
- Kate
 

请注意,不要在很多重复连接时使用序列连接操作, 比如在循环中往序列上追加项目,而这样的使用是可以的: <#list users + admins as person>。 尽管序列连接的速度很快,而且速度是和被连接序列的大小相独立的, 但是最终的结果序列的读取却比原先的两个序列慢那么一点。 通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。

序列切分

使用 seq[range], 这里 range 是一个值域 此处有说明, 就可以得到序列的一个切分。结果序列会包含原序列 (seq)中的项, 而且索引在值域中。例如:

<#assert seq = ["A", "B", "C", "D", "E"]>
<#list seq[1..3] as i>${i}</#list>

将会输出:

BCD 

此外,切分后序列中的项会和值域的顺序相同。 那么上面的示例中,如果值域是 3..1 将会输出 DCB

值域中的数字必须是序列可使用的合法索引, 否则模板的处理将会终止并报错。像上面的示例那样, seq[-1..0] 就会出错, 而 seq[-1]就是合法的。 seq[1..5] 也不对, 因为 seq[5] 是非法的。 (请注意,尽管100已经越界,但是 seq[100..<100] 或 seq[100..*0] 是合法的,因为那些值域都是空。)

限制长度的值域 (start..*length) 和无右边界值域 (start..) 适用于切分后序列的长度。它们会切分可用项中尽可能多的部分:

<#assign seq = ["A", "B", "C"]>

Slicing with length limited ranges:
- <#list seq[0..*2] as i>${i}</#list>
- <#list seq[1..*2] as i>${i}</#list>
- <#list seq[2..*2] as i>${i}</#list> <#-- Not an error -->
- <#list seq[3..*2] as i>${i}</#list> <#-- Not an error -->

Slicing with right-unlimited ranges:
- <#list seq[0..] as i>${i}</#list>
- <#list seq[1..] as i>${i}</#list>
- <#list seq[2..] as i>${i}</#list>
- <#list seq[3..] as i>${i}</#list>

将会输出:

Slicing with length limited ranges:
- AB
- BC
- C
-

Slicing with right-unlimited ranges:
- ABC
- BC
- C
-

请注意,上面的有限长度切分和无右边界切分都允许开始索引超过最后项 一个 (但不能再多了)。

Note:

要对序列进行给定大小的切分,就应该使用内建函数 chunk

哈希表操作

连接

像连接字符串那样,也可以使用 + 号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在 + 号右侧的哈希表中的项优先。例如:

<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}

将会输出:

- Joe is 30
- Fred is 25
- Julia is 18

请注意,很多项连接时不要使用哈希表连接, 比如在循环时往哈希表中添加新项。这和序列连接 的情况是一致的。

算数运算

算数运算包含基本的四则运算和求模运算,运算符有:

  • 加法: +
  • 减法: -
  • 乘法: *
  • 除法: /
  • 求模 (求余): %

示例:

${100 - x * x}
${x / 2}
${12 % 10}

假设 x 是 5,将会输出:

75
2.5
2

要保证两个操作数都是结果为数字的表达式。 下面的这个例子在运行时,FreeMarker就会发生错误, 因为是字符串 "5" 而不是数字5:

${3 * "5"} <#-- WRONG! -->

但这种情况也有一个例外,就是 + 号,它是用来 连接字符串的。 如果 + 号的一端是字符串,+ 号的另外一端是数字,那么数字就会自动转换为字符串类型(使用当前页面语言的适当格式), 之后使用 + 号作为字符串连接操作符。示例如下:

${3 + "5"}

将会输出:

35

通常来说,FreeMarker不会自动将字符串转换为数字,反之会自动进行。

有时我们只想获取除法计算(或其它运算)的整数部分, 这可以使用内建函数 int 来解决。(关于内建函数 后续章节会来解释):

${(x/2)?int}
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}

假设 x 是 5,将会输出:

2
1
1
-1
-1

比较运算

有时我们需要知道两个值是否相等,或者哪个值更大一点。

为了演示具体的例子,我们在这里使用 if 指令。 if 指令的用法是: <#if expression>...</#if>, 其中的表达式的值必须是布尔类型,否则将会出错,模板执行中断。 如果表达式的结果是 true , 那么在开始和结束标记内的内容将会被执行,否则就会被跳过。

测试两个值相等使用 = (或者采用Java和C语言中的 == ;二者是完全等同的。) 测试两个值不等使用 !=。比如, 假设 user 是 ''Big Joe'':

<#if user == "Big Joe">
  It is Big Joe
</#if>
<#if user != "Big Joe">
  It is not Big Joe
</#if>

<#if ...> 中的表达式 user = "Big Joe" 就是布尔值 true,面的代码将会输出 ''It is Big Joe''。

= 或 != 两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型 (也就是说字符串只能和字符串来比较,数字只能和数字来比较等)否则将会出错, 模板执行中断。例如 <#if 1 = "1"> 就会导致错误。 请注意FreeMarker进行的是精确的比较,所以字符串在比较时要注意大小写和空格: "x" 和 "x " 和 "X" 是不同的值。

对数字和日期类型的比较,也可以使用 <, <=>= 和 >。不能把它们当作字符串来比较。比如:

<#if x <= 12>
  x is less or equivalent with 12
</#if>

使用 >= 和 > 的时候有一点小问题。FreeMarker解释 > 的时候可以把它当作FTL标签的结束符。为了避免这种问题,可以使用 lt 代替 <lte 代替 <=, gt 代替 > 还有 gte 代替 >=, 例如 <#if x gt y>。另外一个技巧是将表达式放到 圆括号 中, 尽管这么写并不优雅,例如 <#if (x > y)>

Note:

FreeMarker 也支持一些其它的选择,但是这些已经废弃了:

  • 在可能出问题的关系标记处使用 &gt; 和 &lt; ,就像: <#if x &gt; y> 或 <#if x &gt;= y>。 请注意通常FTL不支持标签中的实体引用(如 &... 这些东西); 做算术比较时就会有异常。

  • \lt, \lte, \gt 和 \gte 使用他们时, 不带反斜杠的效果一样。

逻辑操作

常用的逻辑操作符:

  • 逻辑 或: ||
  • 逻辑 与: &&
  • 逻辑 非: !

逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。

例如:

<#if x < 12 && color == "green">
  We have less than 12 things, and they are green.
</#if>
<#if !hot> <#-- here hot must be a boolean -->
  It's not hot.
</#if>

内建函数

内建函数就像FreeMarker在对象中添加的方法一样。 要防止和实际方法和其它子变量的命名冲突,则不能使用点 (.),这里使用问号 (?)来和父对象分隔开。 比如,想要保证 path 有起始的 / ,那么可以这么来写: path?ensure_starts_with('/')。 path 后的Java对象(通常就是 String) 并没有这样的方法,这是FreeMarker添加的。为了简洁,如果方法没有参数, 那么就可以忽略 (),比如想要获取 path 的长度,就可以写作:path?length, 而不是 path?length()

内建函数关键性的另外一个原因是常见的(尽管它依赖于配置的设置), FreeMarker不会暴露对象的Java API。那么尽管Java的 String 类有length() 方法,但在模板中却是不可见的, 就 不得不 使用 path?length 来代替。 这里的优点是模板不依赖下层Java对象的精确类型。(比如某些场景中, path 也可能是 java.nio.Path 类型, 如果程序员配置了FreeMarker去暴露 Path 对象作为FTL字符串类型,那么模板就不会在意了,使用 ?length 也是可以的, 即便 java.nio.Path 没有类似的方法。)

可以找到一些 此处提及的常用内建函数,还有 完整的内建函数参考。 现在,我们只需了解一些重要的内建函数就行了:

比如:

${testString?upper_case}
${testString?html}
${testString?upper_case?html}

${testSequence?size}
${testSequence?join(", ")}

假设 testString 中存储了字符串 ''Tom & Jerry'', 而testSequnce中存储了字符串 "foo", "bar" 和 "baz", 将会输出:

TOM & JERRY
Tom &amp; Jerry
TOM &amp; JERRY

3
foo, bar, baz

请注意:上面的 testString?upper_case?html。因为 test?upper_case 的结果是字符串,那么就可以在它的上面 使用内建函数html

很自然可以看到,内建函数的左侧可以是任意的表达式,而不仅仅是变量名:

${testSeqence[1]?cap_first}
${"horse"?cap_first}
${(testString + " & Duck")?html}
Bar
Horse
Tom &amp; Jerry &amp; Duck

方法调用

如果有一个方法,那么可以使用方法调用操作。 方法调用操作是使用逗号来分割在括号内的表达式而形成参数列表,这些值就是参数。 方法调用操作将这些值传递给方法,然后返回一个结果。 这个结果就是整个方法调用表达式的值。

假设程序员定义了一个可供调用的方法 repeat。 第一个参数是字符串类型,第二个参数是数字类型。方法的返回值是字符串类型, 而方法要完成的操作是将第一个参数重复显示,显示的次数是第二个参数设定的值。

${repeat("Foo", 3)}

将会输出:

FooFooFoo

这里的 repeat 就是方法变量(根据如何 访问顶层变量), ("What", 3) 就调用了该方法。

这里需要强调方法调用也是普通表达式,和其它都是一样的,所以:

${repeat(repeat("x", 2), 3) + repeat("Foo", 4)?upper_case}

将会输出:

xxxxxxFOOFOOFOOFOO

处理不存在的值

Note:

这些操作符是从 FreeMarker 2.3.7 版本开始引入的(用来代替内建函数 default, exists 和if_exists )。

正如我们前面解释的那样,当试图访问一个不存在的变量时, FreeMarker 将会报错而导致模板执行中断。 通常我们可以使用两个特殊操作符来压制这个错误,控制这种错误情况。 被控制的变量可以是顶层变量,哈希表或序列的子变量。 此外这些操作符还能处理方法调用的返回值不存在的情况 (这点对Java程序员来说: 返回值是 null 或者返回值为 void 类型),通常来说,我们应该使用这些操作符来控制可能不存在的值, 而不仅仅是不存在的变量。

对于知道Java中 null 的人来说,FreeMarker 2.3.x 版本把它们视为不存在的变量。单地说,模板语言中没有 null 这个概念。比如有一个bean,bean中有一个 maidenName 属性, 对于模板而言(假设没有配置FreeMarker来使用一些极端的对象包装), 该属性的值是 null,和不存在这个属性的情况是一致的。 调用方法的返回值如果是 null 的话 FreeMarker 也会把它当作不存在的变量来处理 (假定只使用了普通的对象包装)。可以在 FAQ 中了解更多内容。

Note:

如果想知道为什么 FreeMarker 对不存在的变量如此挑剔, 请阅读 FAQ 部分。

默认值操作符

使用形式: unsafe_expr!default_expr 或 unsafe_expr! or (unsafe_expr)!default_expr 或 (unsafe_expr)!

这个操作符允许你为可能不存在的变量指定一个默认值。

例如,假设下面展示的代码中没有名为 mouse 的变量:

${mouse!"No mouse."}
<#assign mouse="Jerry">
${mouse!"No mouse."}

将会输出:

No mouse.
Jerry

默认值可以是任何类型的表达式,也可以不必是字符串。 也可以这么写:hits!0 或 colors!["red", "green", "blue"]。 默认值表达式的复杂程度没有严格限制,还可以这么来写: cargo.weight!(item.weight * itemCount + 10)

Warning!

如果在 !后面有复合表达式, 如 1 + x通常 使用括号,如 ${x!(1 + y)} 或 ${(x!1) + y)},这样就根据你的意图来确定优先级。 由于FreeMarker 2.3.x 版本的源码中的小失误所以必须这么来做。 ! (作为默认值操作) 右侧的优先级非常低。 这就意味着 ${x!1 + y} 会被 FreeMarker 误解为 ${x!(1 + y)},而真实的意义是 ${(x!1) + y}。 这个源码错误在FreeMarker 2.4中会得到修正。 在编程中注意这个错误,要么就使用FreeMarker 2.4!

如果默认值被省略了,那么结果将会是空串,空序列或空哈希表。 (这是 FreeMarker 允许多类型值的体现)请注意,如果想让默认值为 0 或false,则不能省略它。例如:

(${mouse!})
<#assign mouse = "Jerry">
(${mouse!})

将会输出:

()
(Jerry)
Warning!

因为语法的含糊 <@something a=x! b=y /> 将会解释为 <@something a=x!(b=y) />,那就是说 b=y 将会被视为是比较运算,然后结果作为 x的默认值,而不是想要的参数 b。 为了避免这种情况,如下编写代码即可: <@something a=(x!) b=y />

用于非顶层变量时,默认值操作符可以有两种使用方式:

product.color!"red"

如果是这样的写法,那么在 product 中, 当 color 不存在时(返回 "red" ), 将会被处理,但是如果连 product 都不存在时将不会处理。 也就是说这样写时变量 product 必须存在,否则模板就会报错。

(product.color)!"red"

这时,如果当 product.color 不存在时也会被处理, 那就是说,如果 product 不存在或者 product 存在而 color 不存在,都能显示默认值 "red" 而不会报错。 本例和上例写法的重要区别在于用括号时, 就允许其中表达式的任意部分可以未定义。而没有括号时, 仅允许表达式的最后部分可以不被定义。

当然,默认值操作也可以作用于序列子变量,比如:

<#assign seq = ['a', 'b']>
${seq[0]!'-'}
${seq[1]!'-'}
${seq[2]!'-'}
${seq[3]!'-'}

将会输出:

a
b
-
-

如果序列索引是负数(比如 seq[-1]!'-') 也会发生错误,不能使用该运算符或者其它运算符去压制它。

不存在值检测操作符

使用形式: unsafe_expr?? 或 (unsafe_expr)??

这个操作符告诉我们一个值是否存在。基于这种情况, 结果是 true 或 false

示例如下,假设并没有名为 mouse 的变量:

<#if mouse??>
  Mouse found
<#else>
  No mouse found
</#if>
Creating mouse...
<#assign mouse = "Jerry">
<#if mouse??>
  Mouse found
<#else>
  No mouse found
</#if>

将会输出:

  No mouse found
Creating mouse...
  Mouse found

访问非顶层变量的使用规则和默认值操作符也是一样的, 也就是说,可以写 product.color?? 和 (product.color)??

赋值操作符

这些并不是表达式,只是复制指令语法的一部分,比如 assignlocal 和 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> 的简写。

括号

括号可以用来给任意表达式分组。示例如下:

                               <#-- Output will be: -->
${3 * 2 + 2}                   <#-- 8 -->
${3 * (2 + 2)}                 <#-- 12 -->
${3 * ((2 + 2) * (1 / 2))}     <#-- 6 -->
${"green " + "mouse"?upper_case}    <#-- green MOUSE -->
${("green " + "mouse")?upper_case}  <#-- GREEN MOUSE -->
<#if !(color == "red" || color == "green")>
  The color is nor red nor green
</#if>

请注意,方法调用表达式 使用的括号和给表达式分组的括号含义是完全不同的。

表达式中的空格

FTL 忽略表达式中的多余的 空格。下面的表示是相同的:

${x + ":" + book.title?upper_case}

${x+":"+book.title?upper_case}

${
   x
 + ":"   +  book   .   title
   ?   upper_case
      }

操作符的优先级

下面的表格显示了已定义操作符的优先级。 表格中的运算符按照优先程度降序排列:上面的操作符优先级高于它下面的。 高优先级的运算符执行要先于优先级比它低的。表格同一行上的两个操作符优先级相同。 当有相同优先级的二元运算符(运算符有两个''参数'',比如 +-)挨着出现时,它们按照从左到右的原则运算。

运算符组 运算符
最高优先级运算符 [subvarName] [subStringRange] . ? (methodParamsexprexpr??
一元前缀运算符 +expr -expr !expr
乘除法,求模运算符 * / %
加减法运算符 + -
数字值域 .. ..< ..! ..*
关系运算符 < > <= >= (and equivalents: gtlt, etc.)
相等,不等运算符 == != (and equivalents: =)
逻辑 "与" 运算符 &&
逻辑 "或" 运算符 ||

如果你熟悉C语言,Java语言或JavaScript语言, 请注意 FreeMarker 中的优先级规则和它们是相同的, 除了那些只有FTL本身含有的操作符。

因为编程的失误,默认值操作符 (exp!exp) 不在上面的表格中,按照向后兼容的原则,在 FreeMarker 2.4 版本中将会修正它。 而且它将是最高优先级的运算符,但是在 FreeMarker 2.3.x 版本中它右边的优先级由于失误就非常低。 所以在默认值操作符的右边中使用复杂表达式时可以使用括号, 可以是 x!(y + 1) 或者是 (x!y) + 1。 而不能是 x!y + 1


Freemarker页面语法 A 概念 最常用的 3 个概念 sequence 序列,对应java 里的list 、数组等非键值对的集合 hash 键值对的集合 namespace 对一个ftl 文件的引用, 利用这个名字可以访问到该ftl 文件的资源 B 指令 if, else, elseif 语法 Java代码 ... ... ... ... ... ... ... ... ... ... 用例 Freemarker代码 x is 1 x is 1 x is not 1 x is 1 x is 1 x is not 1 switch, case, default, break 语法 Freemarker代码 ... ... ... ... ... ... ... ... ... ... 用例 字符串 Freemarker代码 This will be processed if it is small This will be processed if it is medium This will be processed if it is large This will be processed if it is neither This will be processed if it is small This will be processed if it is medium This will be processed if it is large This will be processed if it is neither 数字 Freemarker代码 1 2 d 1 2 d 如果x=1 输出 1 2, x=2 输出 2, x=3 输出d list, break 语法 Freemarker代码 ... ... ... ... 关键字 item_index:是list当前值的下标 item_has_next:判断list是否还有值 用例 Freemarker代码 ${x_index + 1}. ${x}, ${x_index + 1}. ${x}, 输出: 1.winter, 2.spring, 3.summer, 4.autumn include 语法 Freemarker代码 或则 Java代码 options包含两个属性 encoding="GBK" 编码格式 parse=true 是否作为ftl语法解析,默认是true,false就是以文本方式引入.注意在ftl文件里布尔值都是直接赋值 的如parse=true,而不是parse="true" 用例 /common/copyright.ftl 包含内容 Ftl代码 Copyright 2001-2002 ${me} All rights reserved. Copyright 2001-2002 ${me} All rights reserved. 模板文件 Java代码 Some test Yeah ___________________________________________________________________________ Some test Yeah ___________________________________________________________________________ 输出结果: Some test Yeah. Copyright 2001-2002 Juila Smith All rights reserved. Import 语法 Freemarker代码 类似于java里的import,它导入文件,然后就可以在当前文件里使用被导入文件里的宏组件 用例 假设mylib.ftl 里定义了宏copyright 那么我们在其他模板页面里可以这样使用 Freemarker代码 <#-- "my"在freemarker里被称作namespace --> <#-- "my"在freemarker里被称作namespace --> compress 语法 Freemarker代码 ... ... 用来压缩空白空间和空白的行 escape, noescape 语法 Freemarker代码 ... ... ... ... ... ... 用例 主要使用在相似的字符串变量输出,比如某一个模块的所有字符串输出都必须是html安全的,这个时候就可以使用 该表达式 Freemarker代码 First name: ${firstName} Last name: ${lastName} Maiden name: ${maidenName} First name: ${firstName} Last name: ${lastName} Maiden name: ${maidenName} 相同表达式 Ftl代码 First name: ${firstName?html} Last name: ${lastName } Maiden name: ${maidenName?html} First name: ${firstName?html} Last name: ${lastName } Maiden name: ${maidenName?html} assign 语法 Freemarker代码 <#-- 或则 --> <#-- 或则 --> <#-- 或则 --> capture this <#-- 或则 --> capture this <#-- 或则 --> <#-- 或则 --> <#-- 或则 --> capture this <#-- 或则 --> capture this 用例 生成变量,并且给变量赋值 给seasons赋予序列值 Ftl代码 给变量test加1 Ftl代码 给my namespage 赋予一个变量bgColor,下面可以通过my.bgColor来访问这个变量 Ftl代码 将一段输出的文本作为变量保存在x里 Ftl代码 ${n} Number of words: ${x?word_list?size} ${x} Hello ${user}! error true ${n} Number of words: ${x?word_list?size} ${x} Hello ${user}! error true 同时也支持中文赋值,如: Ftl代码 java ${语法} java ${语法} 打印输出: java global 语法 Freemarker代码 <#--或则--> <#--或则--> capture this <#--或则--> <#--或则--> capture this 全局赋值语法,利用这个语法给变量赋值,那么这个变量在所有的namespace [A1] 中是可见的, 如果这个变量被当前的assign 语法覆盖 如 在当前页面里x=2 将被隐藏,或者通过${.global.x} 来访问 setting 语法 Freemarker代码 用来设置整个系统的一个环境 locale number_format boolean_format date_format , time_format , datetime_format time_zone classic_compatible 用例 假如当前是匈牙利的设置,然后修改成美国 Ftl代码 ${1.2} ${1.2} ${1.2} ${1.2} 输出 1,2 1.2 因为匈牙利是采用", "作为十进制的分隔符,美国是用". " macro, nested, return 语法 Freemarker代码 ... ... ... ... ... ... 用例 Ftl代码 Test text, and the params: ${foo}, ${bar}, ${baaz} Test text, and the params: ${foo}, ${bar}, ${baaz} 输出 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 定义循环输出的宏 Ftl代码 ${title?cap_first}: *${x?cap_first} ${title?cap_first}: *${x?cap_first} 输出结果: Animals: *Mouse *Elephant *Python 包含body 的宏 Ftl代码 ${c}. ${halfc} Last! ${c}. ${halfc} Last! 输出 1. 0.5 2. 1 3. 1.5 4. 2 Last! t, lt, rt 语法 Freemarkder代码 去掉左右空白和回车换行 去掉左边空白和回车换行 去掉右边空白和回车换行 取消上面的效果 去掉左右空白和回车换行 去掉左边空白和回车换行 去掉右边空白和回车换行 取消上面的效果 C 一些常用方法或注意事项 表达式转换类 ${expression} 计算expression 并输出 #{ expression } 数字计算#{ expression ;format} 安格式输出数字format 为M 和m M 表示小数点后最多的位数,m 表示小数点后最少的位数如#{121.2322;m2M2} 输出121.23 数字循环 1..5 表示从1 到5 ,原型number..number 对浮点取整数 ${123.23?int} 输出 123 给变量默认值 ${var?default("hello world")?html} 如果var is null 那么将会被hello world 替代 判断对象是不是 null Ftl代码 Mouse found Mouse found 也可以直接${mouse?if_exists})输出布尔形 -------------------------------------------- (1)解决输出中文乱码问题: freemarker乱码的原因: 没有使用正确的编码格式读取模版文件,表现为模版中的中文为乱码 解决方法:在classpath上放置一个文件freemarker.properties,在里面写上模版文件的编码方式,比如 default_encoding=UTF-8 locale=zh_CN 注意:eclipse中除了xml文件、java文件外,默认的文件格式iso8859-1 数据插入模版时,没有使用正确的编码,表现出模版中的新插入数据为乱码 解决方法:在result的配置中,指定charset,s2的FreemarkerResult.java会将charset传递freemarker /pages/Person/view.ftl text/html;charset=UTF-8 (2)提高freemarker的性能 在freemarker.properties中设置: template_update_delay=60000 避免每次请求都重新载入模版,即充分利用cached的模版 (3)尽量使用freemarker本身的提供的tag,使用S2 tags 的标签会在性能上有所损失 (4)freemarker的标签种类: ${..}:FreeMarker will replace it in the output with the actual value of the thing in the curly brackets. They are called interpolation s. # ,代表是FTL tags(FreeMarker Template Language tags) ,hey are instructions to FreeMarker and will not be printed to the output ... @ ,代表用户自定义的标签 <#-- --> 注释标签,注意不是<!-- --> (5)一些特殊的指令: r代表原样输出:${r"C:\foo\bar"} ${x} ?引出内置指令 String处理指令: html:特殊的html字符将会被转义,比如"<",处理后的结果是< cap_first 、lower_case 、upper_case trim :除去字符串前后的空格 sequences处理指令 size :返回sequences的大小 numbers处理指令 int:number的整数部分,(e.g. -1.9?int is -1) (6)对于null,或者miss value,freemarker会报错 ?exists:旧版本的用法 !:default value operator,语法结构为: unsafe_expr !default_expr,比如 ${mouse!"No mouse."} 当mouse不存在时,返回default value; (product.color)!"red" 这种方式,能够处理product或者color为miss value的情况; 而product.color!"red"将只处理color为miss value的情况 ??: Missing value test operator ,测试是否为missing value unsafe_expr ?? :product.color??将只测试color是否为null (unsafe_expr )??:(product.color)??将测试product和color是否存在null Ftl代码 <#if mouse??> Mouse found No mouse found Creating mouse... <#if mouse??> Mouse found No mouse found <#if mouse??> Mouse found No mouse found Creating mouse... <#if mouse??> Mouse found No mouse found (7)模版值插入方式 (interpolation) 通用方式 ( Universal interpolations): ${expression } 对于字符串:只是简单输出 对于数值,会自动根据local确定格式,称为human audience,否则称为computer audience,可以"?c", 比如, Details...,因此这里的id是给浏览器使用的,不需要进行格式化,注意?c只对数值有效 对于日期,会使用默认的日期格式转换,因此需要事先设置好默认的转换格式,包括date_format , time_format ,atetime_format 对于布尔值,不能输出,会报错并停止模版的执行,比如${a = 2} 会出错,但是可以 string built-in来进行转换 数值处理,具体参考:Built-ins for numbers http://freemarker.org/docs/ref_builtins_number.html#ref_builtin_string_for_number 数值处理的例子: ${answer} ${answer?string} <#-- the same as ${answer} --> ${answer?string.number} ${answer?string.currency} ${answer?string.percent} 除了使用内置的formate,可以使用任何用Java decimal number format syntax 书写的formate,比如 ${1234} ${12345?string("0.####E0")} 更加方便的格式: US people writes: ${12345678?string(",##0.00")} Hungarian people writes: ${12345678?string(",##0.00")} 日期处理,参考Built-ins for dates http://freemarker.org/docs/ref_builtins_date.html#ref_builtin_string_for_date 日期处理的例子: ${openingTime?string.short} ${openingTime?string.medium} ${openingTime?string.long} ${openingTime?string.full} ${nextDiscountDay?string.short} ${nextDiscountDay?string.medium} ${nextDiscountDay?string.long} ${nextDiscountDay?string.full} ${lastUpdated?string.short} ${lastUpdated?string.medium} ${lastUpdated?string.long} ${lastUpdated?string.full} 注意: 由于java语言中的Date类型的不足,freemarker不能根据Date变量判断出变量包含的部分(日期、时间还是全部),在这种情况下,freemarker 不能正确显示出${lastUpdated?string.short} 或者 simply ${lastUpdated},因此,可以通过?date, ?time and ?datetime built-ins 来帮助freemarker来进行判断,比如${lastUpdated?datetime?string.short} 除了使用内置的日期转换格式外,可以自己指定日期的格式,使用的是Java date format syntax,比如: ${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")} ${lastUpdated?string("EEE, MMM d, ''yy")} ${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")} 数值专用方式 ( Numerical interpolations):#{expression } or #{expression ; format },这是数值专用的输出方式,但是 最好使用通用方式的string built-in或者number_format 来完成转换,Numerical interpolations方式将会被停用 (8)创建自定义模版 Ftl代码 Hello Joe! 评论这张 转发至微博
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值