当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。 例如,我们设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 2.3.21 版本以前只能用于切分, 若用于其它用途,它就像空序列一样了。要使用新的特性, 使用 FreeMarker 2.3.21 版本是不够的,程序员要设置 incompatible_improvements 至少到2.3.21版本。

    在模板中指定一个哈希表,就可以遍历用逗号分隔开的"键/值"对, 把列表放到花括号内即可。键和值成对出现并以冒号分隔。比如: { "name": "green mouse", "price": 150 } 。 请注意名和值都是表达式,但是用来检索的名称就必须是字符串类型, 而值可以是任意类型。

    美元符号 ( $ ),at符号 ( @ )。 此外,第一个字符不可以是ASCII码数字( 0 - 9 )。 从 FreeMarker 2.3.22 版本开始,变量名在任何位置也可以包含负号 ( - ),点( . )和冒号( : ), 但这些必须使用前置的反斜杠( \ )来转义, 否则它们将被解释成操作符。比如,读取名为"data-id"的变量, 表达式为 data\-id ,因为 data-id 将被解释成 "data minus id"。 (请注意,这些转义仅在标识符中起作用,而不是字符串中。)

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

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

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

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

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

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

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

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

    Warning!

    因为 + 和使用 ${ ... } 的规则相同,附加的字符串受到 locale number_format date_format time_format datetime_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):

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

    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:
    Slicing with right-unlimited ranges:
    - ABC
                

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

    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

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

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

    ${3 + "5"}

    将会输出:

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

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

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

    假设 x 是 5,将会输出:

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

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

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

    使用 >=> 的时候有一点小问题。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 使用他们时, 不带反斜杠的效果一样。

    We have less than 12 things, and they are green. <#if !hot> <#-- here hot must be a boolean --> It's not hot. 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
    foo, bar, baz

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

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

    ${testSeqence[1]?cap_first}
    ${"horse"?cap_first}
    ${(testString + " & Duck")?html}
    Horse Tom &amp; Jerry &amp; Duck
    null 的人来说,FreeMarker 2.3.x 版本把它们视为不存在的变量。单地说,模板语言中没有 null 这个概念。比如有一个bean,bean中有一个 maidenName 属性, 对于模板而言(假设没有配置FreeMarker来使用一些极端的对象包装), 该属性的值是 null,和不存在这个属性的情况是一致的。 调用方法的返回值如果是 null 的话 FreeMarker 也会把它当作不存在的变量来处理 (假定只使用了普通的对象包装)。可以在 FAQ 中了解更多内容。

    Note:

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

    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 允许多类型值的体现)请注意,如果想让默认值为 0false,则不能省略它。例如:

    (${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]!'-'}

    将会输出:

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

    <#assign x += 1> (或 <#assign x = x + 1> )不同,它只做算术加法运算 (如果变量不是数字的话就会失败),而其它的是进行字符串,序列连接和哈希表连接的重载。 <#assign x--> <#assign x -= 1> 的简写。

    ${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

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

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

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