On this page
Expressions
Page Contents
当提供插值或指令参数的值时,可以使用变量或更复杂的表达式。例如,如果 x 为 8,y 为 5,则(x + y)/2
的值解析为数值 6.5.
在详细介绍之前,让我们看一些具体的例子:
当您提供插值时:插值的用法为
${expression}
,其中 expression 表示要作为文本插入到输出中的值。因此${(5 + 8)/2}
在输出中显示“ 6.5”(如果输出的语言不是美国英语,则可能显示“ 6.5”)。当您为指令参数提供值时:您已经在“入门”部分中看到了
if
指令。该指令的语法为:<#if expression>...</#if>
。此处的表达式必须计算为布尔值。例如,在<#if 2 < 3>
中,2 < 3
(2 小于 3)是一个计算结果为true
的表达式。
快速概述(备忘单)
这是对那些已经了解 FreeMarker 或只是经验丰富的程序员的人们的提醒:
Strings:
"Foo"
或'Foo'
或"It's \"quoted\""
或'It\'s "quoted"'
或r"C:\raw\string"
Top-level variables:
user
从哈希中检索数据:
user.name
,user["name"]
从序列中检索数据:
products[5]
Special variable:
.main
插值和级联:
"Hello ${user}!"
(或"Hello " + user + "!"
)获得角色:
name[0]
String slice:包含结尾:
name[0..4]
,包含结尾结尾:name[0..<5]
,基于长度(宽松):name[0..*5]
,删除开头:name[5..]
Concatenation:
users + ["guest"]
- Sequence slice:包含结尾:
products[20..29]
,排除结尾:products[20..<30]
,基于长度的(宽松的):products[20..*10]
,删除开头:products[20..]
- Sequence slice:包含结尾:
Concatenation:
passwords + { "joe": "secret42" }
Arithmetical calculations:
(x * 1.5 + 10) / 2 - y % 100
Comparison:
x == y
,x != y
,x < y
,x > y
,x >= y
,x <= y
,x lt y
,x lte y
,x gt y
,x gte y
,...等Logical operations:
!registered && (firstVisit || fromEurope)
Built-ins:
name?upper_case
,path?ensure_starts_with('/')
Method call:
repeat("What", 3)
Default value:
name!"unknown"
或(user.name)!"unknown"
或name!
或(user.name)!
- 缺失值测试:
name??
或(user.name)??
- 缺失值测试:
Assignment operators:
=
,+=
,-=
,*=
,/=
,%=
,++
,--
Local lambdas:
x -> x + 1
,(x, y) -> x + y
另请参阅:Operator precedence
直接指定值
通常,您希望直接指定一个值,而不是某些计算的结果。
Strings
要直接指定字符串值,您可以在文本中使用双引号,例如:"some text"
或在单引号中,例如'some text'
。这两种形式是等效的。如果文本本身包含用于引号的字符("
或'
)或反斜杠,则必须在其前面加上反斜杠;这称为转义。您可以直接在文本中 Importing 其他任何字符,包括line breaks。例:
${"It's \"quoted\" and
this is a backslash: \\"}
${'It\'s "quoted" and
this is a backslash: \\'}
will print:
It's "quoted" and
this is a backslash: \
It's "quoted" and
this is a backslash: \
Note:
当然,您可以简单地在模板中 Importing 上述文本,而无需使用${...}
。但是我们在这里只是为了举例说明表达式。
这是所有受支持的转义序列的列表。字符串 Literals 中反冲的所有其他用法都是错误,并且使用模板的任何尝试都会失败。
Escape sequence | Meaning |
---|---|
\" |
引号(u0022) |
\' |
撇号(又名撇号)(u0027) |
\{ |
左大括号:{ |
\= |
等于字符:= (自 FreeMarker 2.3.28 开始支持.) |
\\ |
反斜线(u005C) |
\n |
换行(u000A) |
\r |
回车(u000D) |
\t |
水平制表(又称制表)(u0009) |
\b |
Backspace (u0008) |
\f |
换页(u000C) |
\l |
小于号:< |
\g |
大于号:> |
\a |
&符:& |
\xCode |
字符以其十六进制的Unicode代码(UCS代码)给出 |
\x
之后的Code
是 1 到 4 个十六进制数字。例如,所有这些都在字符串"\xA9 1999-2001"
,"\x0A9 1999-2001"
,"\x00A9 1999-2001"
中添加了版权符号。当最后一个十六进制数字后的字符可以解释为十六进制数字时,您必须使用所有 4 位数字,否则 FreeMarker 会误解您。
请注意,字符序列${
和#{
(很少是[=
取决于配置的语法)具有特殊含义。它们用于插入表达式的值(通常是:变量的值,如"Hello ${user}!"
)。将在later上对此进行解释。如果要打印${
或#{
(或[=
),则应按以下说明使用原始字符串 Literals,或者像"foo $\{bar}"
中那样转义{
(或像"foo [\=bar]"
中那样转义=
)。
一种特殊的字符串 Literals 是原始字符串 Literals。在原始字符串 Literals 中,反斜杠和${
没有特殊含义,它们被视为纯字符。为了表明字符串 Literals 是原始字符串 Literals,您必须在开头的引号或撇号之前直接加上r
。例:
${r"${foo}"}
${r"C:\foo\bar"}
will print:
${foo}
C:\foo\bar
Numbers
要直接指定数值,请键入不带引号的数字。您必须使用点作为小数点分隔符,并且不得使用任何分组分隔符。您可以使用-
或+
来表示符号(+
是多余的)。尚不支持科学计数法(因此1E3
是错误的)。另外,您不能在小数点分隔符前省略 0(因此.5
是错误的)。
有效数字 Literals 的示例:0.08
,-5.013
,8
,008
,11
,+11
请注意,诸如08
,+8
,8.00
和8
之类的数字 Literals 完全相等,因为它们都表示数字 8.因此,${08}
,${+8}
,${8.00}
和${8}
将全部打印完全相同。
Booleans
要指定布尔值,请 Importingtrue
或false
。不要使用引号。
Sequences
要指定 Literals 序列,请列出用逗号分隔的sub variables,然后将整个列表放在方括号中。例如:
<#list ["foo", "bar", "baz"] as x>
${x}
</#list>
will print:
foo
bar
baz
列表中的项目是表达式,因此您可以执行以下操作:[2 + 2, [1, 2, 3, 4], "foo"]
。在这里,第一个子变量将是数字 4,第二个子变量将是另一个序列,第三个子变量将是字符串“ foo”。
Ranges
范围只是序列,但它们是通过指定它们包含的整数范围来创建的,而不是一一指定其项。例如,假设m
变量存储 5,则0..<m
将给出包含[0, 1, 2, 3, 4]
的序列。范围主要用于用<#list ...>
以及slicing sequences和slicing strings对数字范围进行迭代。
范围表达式的通用形式为(其中start
和end
可以是任何求值为数字的表达式):
start..end
:范围(含结尾)。例如,1..4
给出[1, 2, 3, 4]
,而4..1
给出[4, 3, 2, 1]
。当心,包含端点的范围永远不会给出空序列,因此0..length-1
是* WRONG *,因为当长度为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
给出[]
。当使用这些范围进行切片时,如果在达到指定范围长度之前到达切片序列或字符串的末尾,切片将无错误结束。有关详情,请参见slicing sequences。
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
是好的。由于技术限制(32 位),报告的右边界范围的大小为 2147483647(如果
incompatible_improvements
小于 2.3.21,则为 0)。但是,列出它们时,它们的实际大小是无限的。范围并没有 true 存储它们组成的数字,因此例如
0..1
和0..100000000
创建起来同样快,并且占用了相同的内存量。
Hashes
要在模板中指定哈希,请列出用逗号分隔的键/值对,并将列表放在大括号中。键/值对中的键和值用冒号分隔。这是一个示例:{ "name": "green mouse", "price": 150 }
。请注意,名称和值都是表达式。键必须是字符串。值可以是任何类型。
Retrieving variables
Top-level variables
要访问顶级变量,只需使用变量名。例如,表达式user
将求值为根中名称为“ user”的变量的值。因此,这将打印您在那里存储的内容:
${user}
如果没有这样的顶级变量,那么当 FreeMarker 尝试评估表达式时将导致错误,并且中止模板处理(除非程序员对 FreeMarker 进行了不同配置)。
在这种表达式中,变量名称只能包含字母(包括非拉丁字母),数字(包括非拉丁数字),下划线(_
),美元($
),符号(@
)。此外,第一个字符不能是 ASCII 数字(0
-9
)。从 FreeMarker 2.3.22 开始,变量名还可以在任何位置包含减号(-
),点(.
)和冒号(:
),但是必须用反斜杠(\
)对其进行转义,否则它们将被解释为操作员。例如,要读取名称为“ data-id”的变量,表达式为data\-id
,因为data-id
将被解释为“数据减去 id”。 (请注意,这些转义仅适用于标识符,不适用于字符串 Literals.)
从哈希中检索数据
如果表达式已经有哈希值,则可以用点和子变量的名称获取其子变量。假设我们有这个数据模型:
(root)
|
+- book
| |
| +- title = "Breeding green mouses"
| |
| +- author
| |
| +- name = "Julia Smith"
| |
| +- info = "Biologist, 1923-1985, Canada"
|
+- test = "title"
现在我们可以使用book.title
读取title
,因为 book 表达式将返回一个哈希值(如上一章所述)。进一步应用此逻辑,我们可以使用以下表达式读取作者的名字:book.author.name
。
如果要使用表达式book["title"]
指定子变量名称,则有另一种语法。在方括号中,您可以给出任何表达式,只要它的计算结果为字符串即可。因此,使用此数据模型,您还可以使用book[test]
读取标题。更多例子;这些都是等效的:book.author.name
,book["author"].name
,book.author["name"]
,book["author"]["name"]
。
使用点语法时,与顶级变量一样,变量名也受到相同的限制(名称只能包含字母,数字,_
,$
,@
,但不能以0
-9
开头,也从 2.3 开始.22 您还可以使用\-
,\.
和\:
)。使用方括号语法时没有这种限制,因为名称是任意表达式的结果。 (请注意,为了帮助 FreeMarker XML 支持,如果子变量名称是*
(星号)或**
,则不必使用方括号语法。)
与顶级变量一样,尝试访问不存在的子变量会导致错误并中止模板的处理(除非程序员对 FreeMarker 进行了不同配置)。
从序列中检索数据
这与散列相同,但是您只能使用方括号语法,并且括号中的表达式必须计算为数字,而不是字符串。例如,要获取example data-model的第一个动物的名称(请记住,第一个动物的编号是 0,而不是 1):animals[0].name
Special variables
特殊变量是 FreeMarker 引擎本身定义的变量。要访问它们,请使用.variable_name
语法。
通常,您不需要使用特殊变量。它们是针对专业用户的。特殊变量的完整列表可以在reference中找到。
String operations
插值和级联
如果要将表达式的值插入字符串,则可以在字符串 Literals 中使用${...}
(以及不推荐使用的#{...}
)。 ${...}
在字符串 Literals行为与 Literals 部分相似中(因此它使用相同的*“对语言环境敏感”的数字和日期/时间格式)。
Note:
可以将 FreeMarker 的插值语法配置为使用[=...]
来代替; see here。
示例(假设用户是“ Big Joe”):
<#assign s = "Hello ${user}!">
${s} <#-- Just to see what the value of s is -->
这将打印:
Hello Big Joe!
Warning!
用户经常犯的错误是在不需要/不应该/不能使用的地方使用插值。插值仅在text sections(例如<h1>Hello ${name}!</h1>
)和字符串 Literals(例如<#include "/footer/${company}.html">
)中起作用。典型的* WRONG 用法是<#if ${big}>...</#if>
,这将导致语法错误。您应该只写<#if big>...</#if>
。另外,<#if "${big}">...</#if>
是 WRONG *,因为它将参数值转换为字符串,并且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"
之类的东西。
就像在字符串* expressions *中使用${...}
一样,这只是使用+
运算符的简写,而在其上未应用auto-escaping。
获得角色
您可以像读取序列的子变量一样在给定的索引处获得字符串的单个字符,例如user[0]
。结果将是一个长度为 1 的字符串。 FTL 没有单独的字符类型。与序列子变量一样,索引必须是一个至少为 0 且小于字符串长度的数字,否则错误将中止模板处理。
由于序列子变量语法和字符获取器语法冲突,因此,仅当变量也不是序列时,才可以使用字符获取器语法(这是可能的,因为 FTL 支持多类型值),因为在这种情况下,序列行为占优势。 (要变通解决此问题,您可以使用内置字符串,例如user?string[0]
。如果您还不了解这一点,请不要担心;内置的功能将在后面讨论。)
示例(假设用户是“ Big Joe”):
${user[0]}
${user[4]}
将打印(请注意第一个字符的索引为 0):
B
J
字符串切片(子字符串)
您只能以与切片序列相同的方式对字符串进行切片(请参见此处),而不是使用字符来处理序列项。一些区别是:
字符串切片不允许减小范围。 (这是因为与序列不同,您很少要显示反向的字符串,因此,如果发生这种情况,几乎总是由于疏忽而导致的.)
如果值既是字符串又是序列(多类型值),则切片将对序列(而不是字符串)进行切片。在处理 XML 时,此类值很常见。在这种情况下,您可以使用
someXMLnode?string[range]
来进行字符串切片。有一个遗留错误,结尾为*且比起始索引小 1 且非负(例如
"abc"[1..0]
)的范围将给出一个空字符串而不是一个错误。 (这应该是一个错误,因为它的范围正在减小.)目前,此错误是为向后兼容而模拟的,但您不应使用它,因为将来肯定会出现错误。
Example:
<#assign s = "ABCDEF">
${s[2..3]}
${s[2..<4]}
${s[2..*3]}
${s[2..*100]}
${s[2..]}
will print:
CD
CD
CDE
CDEF
CDEF
Note:
方便的内置方法涵盖了一些字符串切片的典型用例:remove_beginning,remove_ending,keep_before,keep_after,keep_before_last,keep_after_last
Sequence operations
Concatenation
您可以使用与+
相同的方式连接字符串。例:
<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
- ${user}
</#list>
will print:
- Joe
- Fred
- Julia
- Kate
请注意,序列级联不能用于许多重复的级联,例如将项目附加到循环内的序列中。仅用于<#list users + admins as person>
之类的东西。尽管级联序列速度快且时间恒定(其速度与级联序列的大小无关),但所得序列的读取速度总是比原始两个序列慢一些。因此,在数十或数百次重复的连接之后,结果对于 Reader 而言可能是不切实际的缓慢。
Sequence slicing
使用seq[range]
,如果range
是范围值如此处所述,则可以获取该序列的一部分。结果序列将包含原始序列(seq
)中索引范围在范围内的项目。例如:
<#assign seq = ["A", "B", "C", "D", "E"]>
<#list seq[1..3] as i>${i}</#list>
will print
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。
Hash operations
Concatenation
您可以使用与+
相同的方式来连接哈希。如果两个哈希都包含相同的密钥,则+
右侧的哈希优先。例:
<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}
will print:
- Joe is 30
- Fred is 25
- Julia is 18
请注意,哈希级联不能用于许多重复的级联,例如在循环内将项目添加到哈希中。虽然将散列加在一起是快速且恒定的时间(与所添加的散列的大小无关),但是所生成的哈希值比加在一起的散列要慢一些。因此,在添加数十或数百次之后,读取结果可能会不切实际地变慢。
Arithmetical calculations
这是基本的四功能计算器算法加上模运算符。因此,运算符为:
加法:
+
减法:
-
乘法:
*
部门:
/
整数(余数)的整数操作数:
%
Example:
${100 - x * x}
${x / 2}
${12 % 10}
假设x
为 5,它将打印:
75
2.5
2
两个操作数都必须是计算结果为数值的表达式。因此,以下示例将在 FreeMarker 尝试对其进行评估时导致错误,因为"5"
是字符串而不是数字 5:
${3 * "5"} <#-- WRONG! -->
上述规则有一个 exception。 +
运算符也用于concatenate strings。如果+
的一侧是字符串,而+
的另一侧是数值,则它将数值转换为字符串(使用适合页面语言的格式),然后将+
用作字符串串联运算符。例:
${3 + "5"}
35
通常,FreeMarker 永远不会将字符串自动转换为数字,但可能会将数字自动转换为字符串。
人们通常只需要除法(或其他计算)结果的整数部分。内置int
可以实现。 (内置插件的说明later):
${(x/2)?int}
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}
假设x
为 5,它将打印:
2
1
1
-1
-1
由于历史原因,%
运算符的工作方式是先将操作数截断为整数,然后返回除法的余数:
${12 % 5} <#-- Prints 2 -->
${12.9 % 5} <#-- Prints 2 -->
${12.1 % 5} <#-- Prints 2 -->
${12 % 6} <#-- Prints 0 -->
${12 % 6.9} <#-- Prints 0 -->
%
的结果的符号与左操作数的符号相同,并且其绝对值与两个操作数都为正的情况相同:
${-12 % -5} <#-- Prints -2 -->
${-12 % 5} <#-- Prints -2 -->
${12 % -5} <#-- Prints 2 -->
关于操作的精度:默认情况下,FreeMarker 对所有算术计算都使用BigDecimal
-s,以避免舍入和上溢/下溢伪影,并将结果保持为BigDecimal
-s。因此+
(加法),-
(减法)和*
(乘法)是“无损的”。同样,默认情况下,/
(除法)结果通过向上取整计算为 12 个小数(除非某些操作数的小数甚至更多,在这种情况下,它使用的小数也是如此)。所有这些行为取决于arithmetic_engine
配置设置(Configurable.setArithmericEngine(ArithmericEngine)
),并且某些应用程序可能使用与默认值不同的值,尽管这种情况非常少见。
Comparison
有时您想知道两个值是否相等,或者哪个值更大。
为了显示具体示例,我将在此处使用if
指令。 if
伪指令的用法是:<#if expression>...</#if>
,其中 expression 必须计算为布尔值,否则错误将中止模板的处理。如果 expression 的值为true
,则将处理开始标记和结束标记之间的内容,否则将跳过它们。
要测试两个值的相等性,请使用==
(或=
作为不推荐使用的替代方法)。要测试两个值的不平等性,请使用!=
。例如,假设user
是“大乔”:
<#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)>
一样将表达式放入parentheses,尽管它被认为不太优雅。
FreeMarker 支持更多语法选择:
也可以使用
>
和<
,例如<#if x > y>
或<#if x >= y>
。这并不意味着需要手动 Importing。它可以解决以下情况:模板转义了 XML/HTML 并且用户无法轻松地防止这种情况的发生。注意,一般而言,FTL 不支持 FTL 标签中的实体引用(&...;
事物)。这些运算符只是一个 exception。不推荐使用的形式:
\lt
,\lte
,\gt
和\gte
。这些与没有反斜杠的那些相同。
Logical operations
只是通常的逻辑运算符:
逻辑或:
||
逻辑与:
&&
逻辑不正确:
!
运算符将仅使用布尔值。否则,错误将中止模板处理。
Example:
<#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 支持更多语法选择:
\and
(自 FreeMarker 2.3.27 起):在某些使用&&
的应用程序中会导致问题,因为它在 XML 或 HTML 中无效。尽管 FreeMarker 模板从来都不是有效的 XML/HTML,但它们的输出应该是,实际上,有些应用程序希望模板本身是有效的 XML/HTML。对于这种情况,此语法是一种解决方法。另请注意,与比较运算符不同,由于向后兼容性限制,不支持and
不带\
。&&
(自 FreeMarker 2.3.27 起):这不是要手动 Importing;它可以解决以下情况:模板转义了 XML/HTML 并且用户无法轻松地防止这种情况的发生。请注意,一般而言,FTL 不支持 FTL 标签中的实体引用(&...;
事物)。这些运算符只是一个 exception。不建议使用的表单:
&
和|
。不再使用它们。
Built-ins
内建函数类似于 FreeMarker 添加到对象中的方法。为防止名称与实际方法和其他子变量发生冲突,请使用问号(?
)将它们与父对象分开,而不要使用点(.
)。例如,如果要确保path
具有初始/
,则可以编写path?ensure_starts_with('/')
。 path
(最常见的是String
)后面的 Java 对象没有这种方法,FreeMarker 添加了它。为简便起见,如果该方法没有参数,则必须*省略()
,例如,要获得path
的长度,则必须编写path?length
,而不能写path?length()
。
内置程序至关重要的另一个原因是,通常(尽管它取决于配置设置),FreeMarker 不会公开对象的 Java API。因此,尽管 Java 的String
具有length()
方法,但它已从模板中隐藏,您必须而是使用path?length
。这样做的好处是模板不会依赖于底层 Java 对象的确切类型。 (就像path
在幕后可能是java.nio.Path
,但是如果程序员已配置 FreeMarker 将Path
对象作为 FTL 字符串公开,则模板将不会意识到这一点,尽管java.nio.Path
没有类似的方法,但?length
仍然可以工作.)
您可以找到这里提到的最常用的内置插件和参考中内置的完整列表。现在,仅举几个更重要的事情:
Example:
${testString?upper_case}
${testString?html}
${testString?upper_case?html}
${testSequence?size}
${testSequence?join(", ")}
假设testString
存储字符串“ Tom&Jerry”,而 testSequnce 存储字符串“ foo”,“ bar”和“ baz”,则输出为:
TOM & JERRY
Tom & Jerry
TOM & JERRY
3
foo, bar, baz
请注意上面的test?upper_case?html
。由于test?upper_case
的结果是一个字符串,因此可以在其上应用内置的html
。
当然,内置的左侧可以是任意表达式,而不仅仅是变量名:
${testSeqence[1]?cap_first}
${"horse"?cap_first}
${(testString + " & Duck")?html}
Bar
Horse
Tom & Jerry & Duck
Method call
如果您有方法,则可以在其上使用方法调用操作。方法调用操作是用逗号分隔的括号中的表达式列表。这些值称为参数。方法调用操作将这些值传递给方法,该方法将依次返回结果。该结果将是整个方法调用表达式的值。
例如,假设程序员提供了一个名为repeat
的方法变量。您将字符串作为第一个参数,将数字作为第二个参数,然后返回一个字符串,该字符串将第一个参数重复第二个参数指定的次数。
${repeat("Foo", 3)}
will print:
FooFooFoo
在这里repeat
被评估为方法变量(根据您访问顶级变量的方式),然后("What", 3)
调用了该方法。
我想强调一下,方法调用只是普通表达式,就像其他所有内容一样。所以这:
${repeat(repeat("x", 2), 3) + repeat("Foo", 4)?upper_case}
将打印此:
xxxxxxFOOFOOFOOFOO
处理缺失值
Note:
这些操作符自 FreeMarker 2.3.7 起(替换内置default
,exists
和if_exists
)存在。
如前所述,如果尝试访问丢失的变量,将发生错误并中止模板处理。但是,两个特殊的运算符可以抑制此错误,并处理有问题的情况。处理的变量也可以是顶级变量,哈希子变量或序列子变量。此外,这些运算符可以处理方法调用不返回值的情况(从 Java 程序员的角度来看:它返回null
或返回类型为void
),因此说这些运算符通常处理缺失值更为正确,而不只是缺少变量。
对于那些了解 Java null
的人,可以使用 FreeMarker 2.3. * x *将它们视为缺失值。简而言之,模板语言不了解null
的概念。例如,如果您有一个具有maidenName
属性的 bean,并且该属性的值为null
,那么就模板而言,这就像根本没有此类属性一样(假设您没有将 FreeMarker 配置为使用某些极端对象包装器.返回null
的方法调用的结果也被视为缺少的变量(同样,假设您使用一些常规的对象包装器)。查看更多在常见问题中。
Note:
如果您想知道 FreeMarker 为什么对缺失的变量如此挑剔,阅读此常见问题解答条目。
默认值运算符
简介:unsafe_expr!default_expr
或unsafe_expr!
或(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"
如果color
在product
内部丢失(如果返回则返回"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)??
。
Assignment Operators
这些实际上不是表达式,而是赋值指令语法的一部分,例如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>
的简写。
Note:
从 FreeMarker 2.3.23 开始,仅支持速记运算符(如+=
,++
等)。在此之前,您只能像<#assign x = x + 1>
一样单独使用=
。
Local lambdas
FreeMarker 不支持通用 lambda(与 Java 不同)。 lambda 的使用仅限于某些built-ins的参数,例如:filter,map,take_while,drop_while。
此限制的原因是 FreeMarker 不会实现从 lambda 引用的绑定/捕获变量,而是确保对 lambda 的求值发生在封闭变量范围结束之前。因此,为了将它们与“实际” lambda 区分开来,这些被称为* local * lambda。
lambdas 的语法类似于(name1, name2, ..., nameN) -> expression
。如果只有一个参数,则可以省略括号:name1 -> expression
。
由于->
的右侧只是一个表达式,因此如果您需要在其中使用复杂的逻辑,则可能需要将其移至function,因为您可以使用if
,list
等指令,但在这种情况下,您不必不需要 lambda 表达式,因为所有支持 lambda 参数的内置程序也都支持直接传递函数。例如,您应该只写seq?map(myMapper)
而不是seq?map(it -> myMapper(it))
。
在 lambda 表达式中指定的参数可以保留缺少的(Java null
)值。读取 lambda 参数永远不会退回到更高的范围,因此具有相同名称的变量在访问 lambda 参数时不会受到干扰。因此,像seq?filter(it -> it??)
这样的东西可以可靠地工作,它可以从序列中过滤掉丢失的元素。
Parentheses
括号可用于对任何表达式进行分组。一些例子:
<#-- 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 忽略表达式中多余的white-space。因此,这些完全等效:
${x + ":" + book.title?upper_case}
and
${x+":"+book.title?upper_case}
and
${
x
+ ":" + book . title
? upper_case
}
表达式中的 Comments
表达式可以在任何可以包含忽略的空格(see above)的地方包含 Comments。Comments 看起来像<#-- ... -->
或[#-- ... --]
。例:
<#assign x <#-- A comment --> = 123 <#-- A comment -->>
<#function f(x <#-- A comment -->, y <#-- A comment -->)>
<#return <#-- A comment --> 1 <#-- A comment -->>
</#function>
<#assign someHash = {
"foo": 123, <#-- A comment -->
"bar": x <#-- A comment --> + 1,
<#-- A comment -->
"baaz": f(1 <#-- A comment -->, 2 <#-- A comment -->)
} <#-- A comment -->>
Operator precedence
下表显示了分配给运算符的优先级。该表中的运算符按优先级 Sequences 列出:表中出现的运算符越高,其优先级越高。具有较高优先级的运算符将在具有相对较低优先级的运算符之前进行评估。同一行上的运算符具有相同的优先级。当具有相同优先级的二元运算符(具有两个“参数”的运算符,如+
和-
)并排出现时,它们将按从左到右的 Sequences 求值。
Operator group | Operators |
---|---|
最高优先级运算符 | [subvarName] [subStringRange] . ? (methodParams) expr! expr?? |
一元前缀运算符 | +expr -expr !expr |
multiplicative operators | * / % |
additive operators | + - |
numerical ranges | .. ..< ..! ..* |
relational operators | < > <= >= (和等效项:gt ,lt 等) |
equality operators | == != (和等效项:= ) |
逻辑“与”运算符 | && |
逻辑“或”运算符 | || |
local lambda | -> |
对于那些了解 C,Java 语言或 JavaScript 的人,请注意优先规则与那些语言相同,不同之处在于 FTL 具有某些在这些语言中不存在的运算符。
由于编程错误,默认值运算符(exp!exp
)不在表中,由于向后兼容性限制,该值仅在 FreeMarker 2.4 中得到修复。它本意是“最高优先级运算符”,但是在 FreeMarker 2.3.x 中,其右侧的优先级偶然是很低的。因此,如果您在右侧有一个复合表达式,请始终使用括号,例如x!(y + 1)
或(x!y) + 1
。切勿只写x!y + 1
。