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

  • 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转义版本, 比如 & 会由 &amp; 来代替。

  • 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

赋值操作符

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

括号

括号可以用来给任意表达式分组。

表达式中的空格

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>

 

posted @ 2021-03-27 10:53  youqc  阅读(186)  评论(0编辑  收藏  举报