当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。例如,我们设x为8,y为5,那么(x+y)/2的值就会被处理成数字类型的值6.5。
在我们展开细节之前,先来看一些具体的例子:
这里给已经了解FreeMarker的人或有经验的程序员的提个醒:
通常我们喜欢是使用直接确定的值而不是计算的结果。
下面的表格是FreeMarker支持的所有转义字符。在字符串使用反斜杠的其他所有情况都是错误的,运行这样的模板都会失败。
原生字符串是一种特殊的字符串。在原生字符串中,反斜杠和${没有特殊含义,它们被视为普通的字符。为了表明字符串是原生字符串,在开始的引号或单引号之前放置字母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"]asx>${x}#list>将会输出:
foobarbaz列表中的项目是表达式,那么也可以这样做:[2+2,[1,2,3,4],"foo"]。其中第一个子变量是数字4,第二个子变量是一个序列,第三个子变量是字符串"foo"。
值域表达式的通用形式是(start和end可以是任意的结果为数字表达式):
值域的进一步注意事项:
在模板中指定一个哈希表,就可以遍历用逗号分隔开的"键/值"对,把列表放到花括号内即可。键和值成对出现并以冒号分隔。比如:{"name":"greenmouse","price":150}。请注意名和值都是表达式,但是用来检索的名称就必须是字符串类型,而值可以是任意类型。
访问顶层的变量,可以简单地使用变量名。例如,用表达式user就可以在根上获取以"user"为名存储的变量值。然后打印出存储在里面的内容:
${user}如果没有顶层变量,那么FreeMarker在处理表达式时就会发生错误,进而终止模板的执行(除非程序员事先配置了FreeMarker)。
在这种表达式中,变量名只可以包含字母(也可以是非拉丁文),数字(也可以是非拉丁数字),下划线(_),美元符号($),at符号(@)。此外,第一个字符不可以是ASCII码数字(0-9)。从FreeMarker2.3.22版本开始,变量名在任何位置也可以包含负号(-),点(.)和冒号(:),但这些必须使用前置的反斜杠(\)来转义,否则它们将被解释成操作符。比如,读取名为"data-id"的变量,表达式为data\-id,因为data-id将被解释成"dataminusid"。(请注意,这些转义仅在标识符中起作用,而不是字符串中。)
如果有一个表达式的结果是哈希表,那么我们可以使用点和子变量的名字得到它的值,假设我们有如下的数据模型:
(root)|+-book|||+-title="Breedinggreenmouses"|||+-author|||+-name="JuliaSmith"|||+-info="Biologist,1923-1985,Canada"|+-test="title"现在,就可以通过book.title来读取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)。
特殊变量是由FreeMarker引擎本身定义的。使用它们,可以按照如下语法形式来进行:.variable_name。.
<#assigns="Hello${user}!">${s}<#--Justtoseewhatthevalueofsis-->将会输出:
另外,也可以使用+号来达到类似的效果:
<#assigns="Hello"+user+"!">这样的效果和使用${...}是一样的。
因为+和使用${...}的规则相同,附加的字符串受到locale,number_format,date_format,time_format,datetime_format和boolean_format等等设置的影响,这是对人来说的,而不是通常机器的解析。默认情况下,这会导致数字出问题,因为很多地区使用分组(千分位分隔符),那么"someUrlid="+id就可能会是"someUrlid=1234"。要预防这种事情的发生,请使用c(对计算机来说)内建函数,那么在"someUrlid="+idc或"someUrlid=${idc}"中,就会得到如"someUrlid=1234"这样的输出,而不管本地化和格式的设置是什么。
示例(假设user是"BigJoe"):
${user[0]}${user[4]}将会输出(请注意第一个字符的索引是0):
示例:
<#assigns="ABCDEF">${s[2..3]}${s[2..<4]}${s[2..*3]}${s[2..*100]}${s[2..]}将会输出:
序列的连接可以按照字符串那样使用+号来进行,例如:
<#list["Joe","Fred"]+["Julia","Kate"]asuser>-${user}#list>将会输出:
-Joe-Fred-Julia-Kate请注意,不要在很多重复连接时使用序列连接操作,比如在循环中往序列上追加项目,而这样的使用是可以的:<#listusers+adminsasperson>。尽管序列连接的速度很快,而且速度是和被连接序列的大小相独立的,但是最终的结果序列的读取却比原先的两个序列慢那么一点。通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。
<#assertseq=["A","B","C","D","E"]><#listseq[1..3]asi>${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..)适用于切分后序列的长度。它们会切分可用项中尽可能多的部分:
<#assignseq=["A","B","C"]>Slicingwithlengthlimitedranges:-<#listseq[0..*2]asi>${i}#list>-<#listseq[1..*2]asi>${i}#list>-<#listseq[2..*2]asi>${i}#list><#--Notanerror-->-<#listseq[3..*2]asi>${i}#list><#--Notanerror-->Slicingwithright-unlimitedranges:-<#listseq[0..]asi>${i}#list>-<#listseq[1..]asi>${i}#list>-<#listseq[2..]asi>${i}#list>-<#listseq[3..]asi>${i}#list>将会输出:
Slicingwithlengthlimitedranges:-AB-BC-C-Slicingwithright-unlimitedranges:-ABC-BC-C-请注意,上面的有限长度切分和无右边界切分都允许开始索引超过最后项一个(但不能再多了)。
像连接字符串那样,也可以使用+号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在+号右侧的哈希表中的项优先。例如:
<#assignages={"Joe":23,"Fred":25}+{"Joe":30,"Julia":18}>-Joeis${ages.Joe}-Fredis${ages.Fred}-Juliais${ages.Julia}将会输出:
算数运算包含基本的四则运算和求模运算,运算符有:
${100-x*x}${x/2}${12%10}假设x是5,将会输出:
752.52要保证两个操作数都是结果为数字的表达式。下面的这个例子在运行时,FreeMarker就会发生错误,因为是字符串"5"而不是数字5:
${3+"5"}将会输出:
35通常来说,FreeMarker不会自动将字符串转换为数字,反之会自动进行。
${(x/2)int}${1.1int}${1.999int}${-1.1int}${-1.999int}假设x是5,将会输出:
211-1-1比较运算有时我们需要知道两个值是否相等,或者哪个值更大一点。
为了演示具体的例子,我们在这里使用if指令。if指令的用法是:<#ifexpression>...#if>,其中的表达式的值必须是布尔类型,否则将会出错,模板执行中断。如果表达式的结果是true,那么在开始和结束标记内的内容将会被执行,否则就会被跳过。
=或!=两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型(也就是说字符串只能和字符串来比较,数字只能和数字来比较等)否则将会出错,模板执行中断。例如<#if1="1">就会导致错误。请注意FreeMarker进行的是精确的比较,所以字符串在比较时要注意大小写和空格:"x"和"x"和"X"是不同的值。
对数字和日期类型的比较,也可以使用<,<=,>=和>。不能把它们当作字符串来比较。比如:
FreeMarker也支持一些其它的选择,但是这些已经废弃了:
常用的逻辑操作符:
逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。
例如:
内建函数关键性的另外一个原因是常见的(尽管它依赖于配置的设置),FreeMarker不会暴露对象的JavaAPI。那么尽管Java的String类有length()方法,但在模板中却是不可见的,就不得不使用pathlength来代替。这里的优点是模板不依赖下层Java对象的精确类型。(比如某些场景中,path也可能是java.nio.Path类型,如果程序员配置了FreeMarker去暴露Path对象作为FTL字符串类型,那么模板就不会在意了,使用length也是可以的,即便java.nio.Path没有类似的方法。)
比如:
TOM&JERRYTom&JerryTOM&JERRY3foo,bar,baz请注意:上面的testStringupper_casehtml。因为testupper_case的结果是字符串,那么就可以在它的上面使用内建函数html。
很自然可以看到,内建函数的左侧可以是任意的表达式,而不仅仅是变量名:
${testSeqence[1]cap_first}${"horse"cap_first}${(testString+"&Duck")html}BarHorseTom&Jerry&Duck方法调用如果有一个方法,那么可以使用方法调用操作。方法调用操作是使用逗号来分割在括号内的表达式而形成参数列表,这些值就是参数。方法调用操作将这些值传递给方法,然后返回一个结果。这个结果就是整个方法调用表达式的值。
假设程序员定义了一个可供调用的方法repeat。第一个参数是字符串类型,第二个参数是数字类型。方法的返回值是字符串类型,而方法要完成的操作是将第一个参数重复显示,显示的次数是第二个参数设定的值。
${repeat("Foo",3)}将会输出:
这里需要强调方法调用也是普通表达式,和其它都是一样的,所以:
${repeat(repeat("x",2),3)+repeat("Foo",4)upper_case}将会输出:
xxxxxxFOOFOOFOOFOO处理不存在的值Note:这些操作符是从FreeMarker2.3.7版本开始引入的(用来代替内建函数default,exists和if_exists)。
正如我们前面解释的那样,当试图访问一个不存在的变量时,FreeMarker将会报错而导致模板执行中断。通常我们可以使用两个特殊操作符来压制这个错误,控制这种错误情况。被控制的变量可以是顶层变量,哈希表或序列的子变量。此外这些操作符还能处理方法调用的返回值不存在的情况(这点对Java程序员来说:返回值是null或者返回值为void类型),通常来说,我们应该使用这些操作符来控制可能不存在的值,而不仅仅是不存在的变量。
使用形式:unsafe_expr!default_expr或unsafe_expr!or(unsafe_expr)!default_expr或(unsafe_expr)!
这个操作符允许你为可能不存在的变量指定一个默认值。
例如,假设下面展示的代码中没有名为mouse的变量:
${mouse!"Nomouse."}<#assignmouse="Jerry">${mouse!"Nomouse."}将会输出:
Nomouse.Jerry默认值可以是任何类型的表达式,也可以不必是字符串。也可以这么写:hits!0或colors!["red","green","blue"]。默认值表达式的复杂程度没有严格限制,还可以这么来写:cargo.weight!(item.weight*itemCount+10)。
如果在!后面有复合表达式,如1+x,通常使用括号,如${x!(1+y)}或${(x!1)+y)},这样就根据你的意图来确定优先级。由于FreeMarker2.3.x版本的源码中的小失误所以必须这么来做。!(作为默认值操作)右侧的优先级非常低。这就意味着${x!1+y}会被FreeMarker误解为${x!(1+y)},而真实的意义是${(x!1)+y}。这个源码错误在FreeMarker2.4中会得到修正。在编程中注意这个错误,要么就使用FreeMarker2.4!
如果默认值被省略了,那么结果将会是空串,空序列或空哈希表。(这是FreeMarker允许多类型值的体现)请注意,如果想让默认值为0或false,则不能省略它。例如:
(${mouse!})<#assignmouse="Jerry">(${mouse!})将会输出:
()(Jerry)Warning!因为语法的含糊<@somethinga=x!b=y/>将会解释为<@somethinga=x!(b=y)/>,那就是说b=y将会被视为是比较运算,然后结果作为x的默认值,而不是想要的参数b。为了避免这种情况,如下编写代码即可:<@somethinga=(x!)b=y/>
用于非顶层变量时,默认值操作符可以有两种使用方式:
product.color!"red"如果是这样的写法,那么在product中,当color不存在时(返回"red"),将会被处理,但是如果连product都不存在时将不会处理。也就是说这样写时变量product必须存在,否则模板就会报错。
(product.color)!"red"这时,如果当product.color不存在时也会被处理,那就是说,如果product不存在或者product存在而color不存在,都能显示默认值"red"而不会报错。本例和上例写法的重要区别在于用括号时,就允许其中表达式的任意部分可以未定义。而没有括号时,仅允许表达式的最后部分可以不被定义。
当然,默认值操作也可以作用于序列子变量,比如:
使用形式:unsafe_expr或(unsafe_expr)
这个操作符告诉我们一个值是否存在。基于这种情况,结果是true或false。
示例如下,假设并没有名为mouse的变量:
<#ifmouse>Mousefound<#else>Nomousefound#if>Creatingmouse...<#assignmouse="Jerry"><#ifmouse>Mousefound<#else>Nomousefound#if>将会输出:
NomousefoundCreatingmouse...Mousefound访问非顶层变量的使用规则和默认值操作符也是一样的,也就是说,可以写product.color和(product.color)。
<#assignx+=y>是<#assignx=x+y>的简写,<#assignx*=y>是<#assignx=x*y>的简写等等。。。
<#assignx++>和<#assignx+=1>(或<#assignx=x+1>)不同,它只做算术加法运算(如果变量不是数字的话就会失败),而其它的是进行字符串,序列连接和哈希表连接的重载。<#assignx-->是<#assignx-=1>的简写。
括号可以用来给任意表达式分组。示例如下:
${x+":"+book.titleupper_case}和
如果你熟悉C语言,Java语言或JavaScript语言,请注意FreeMarker中的优先级规则和它们是相同的,除了那些只有FTL本身含有的操作符。
因为编程的失误,默认值操作符(exp!exp)不在上面的表格中,按照向后兼容的原则,在FreeMarker2.4版本中将会修正它。而且它将是最高优先级的运算符,但是在FreeMarker2.3.x版本中它右边的优先级由于失误就非常低。所以在默认值操作符的右边中使用复杂表达式时可以使用括号,可以是x!(y+1)或者是(x!y)+1。而不能是x!y+1。
Generatedfor:Freemarker2.3.23Lastgenerated:2015-09-1814:38:51GMT