本章将介绍 FreeMarker 插值(${}),插值指将指定的变量中的值插入到模板中,然后输出文本。
插值的使用格式是:
${expression}
这里的 expression 可以是所有种类的表达式,比如:${100 + x}
插值是用来计算表达式的具体值,然后将值转换为文本 (字符串)。插值仅仅可以在两种位置使用:
文本区,比如 <h1>Hello ${name}!</h1>
字符串表达式,比如 <#include "/footer/${company}.html">
【注意】
(1)表达式的结果必须是字符串;数字、日期/时间/日期时间值可以被插值自动转换为字符串。其它类型的值,比如:布尔值,序列,必须 "手动地" 转换成字符串,否则就会发生错误,中止模板执行。
(2)一个常犯的错误是在不能使用插值的地方使用了它。 例如:在 if 指令中使用插值,如:<#if ${big}>...</#if>,这是语法上的错误。只要简单写为 <#if big>...</#if>即可。 而且 <#if "${big}">...</#if> 也是错误的,因为这样参数就是字符串类型了,但是 if 指令的参数要求是布尔值,所以就会发生运行时错误。
如果插值在文本区 (也就是说,不在字符串表达式中),如果 escape 指令起作用了,那么将被插入的字符串会被自动转义。如果要生成 HTML,那么强烈建议你利用它来阻止跨站脚本攻击和非格式良好的HTML页面。这里有一个示例:
<#escape x as x?html> ... <p>Title: ${book.title}</p> <p>Description: <#noescape>${book.description}</#noescape></p> <h2>Comments:</h2> <#list comments as comment> <div class="comment"> ${comment} </div> </#list> ... </#escape>
这个示例展示了当生成HTML时,最好将完整的模板放入到 escape 指令中。那么,如果 book.title 包含 &, 在输出中它就会被替换成 &,而页面还会保持为格式良好的HTML。如果用户注释包含如 <iframe> (或其它元素)的标记,那么就会被转义成 <iframe> 的样子,使他们没有任何危险。但有时在数据模型中真的需要HTML,我们假设上面的 book.description 在数据库中的存储是HTML格式的,那么此时不得不使用 noescape 来取消 escape 的转义,不包含 escape, 模板就会像这样了:
... <p>Title: ${book.title?html}</p> <p>Description: ${book.description}</p> <h2>Comments:</h2> <#list comments as comment> <div class="comment"> ${comment?html} </div> </#list> ...
这和之前示例的效果是一样的,但是这里可能会忘记 ?html 等内建函数,那么这就会有安全上的问题了。 在之前的示例中,你可能忘记 noescape 等内建函数,也会造成不良的输出,但是起码是没有安全隐患的。
如果表达式是数字类型,那么数值将根据默认格式转换成字符串。这也许会包含最大的小数,数字分组和相似处理的问题。通常程序员应该设置默认的数字格式;而模板设计者不需要处理它(但是可以使用 number_format 来设置)。 可以使用内建函数 string 为一个插值来重写默认数值格式。
小数的分隔符通常(其他类似的符号也是这样,如分组符号)是根据所在地的标准(语言,国家)来确定的,这也需要程序员来设置。例如这个模板:
${1.5}
如果当前本地化设置为英语时,将会输出:
1.5
而当前地区为德国时,将会输出:
1,5
这是因为德国人使用逗号作为小数点。
【注意】
可以看出,插值的打印都是给用户看的,而不是给“计算机”的。有时候这样并不好,比如要打印数据库记录的主键,用来作为URL中的一部分或HTML表单的隐藏域来作为提交内容,或者要打印 CSS/JavaScript 中的数字,因为这些值都是给计算机程序去识别的而不是给用户看的。很多程序对数字格式的要求非常严格,它们只能理解一部分简单的美式数字格式。那样的话,可以使用内建函数 c (代表''计算机'')来解决这个问题,比如:
<a href="/shop/productdetails?id=${product.id?c}">Details...</a>
如果表达式的值是时间日期类型,那么日期中的数字将会按照默认格式来转换成文本。 通常程序员应该设置默认格式,而页面设计者无需处理这一点。如果需要的话,可以参考 date_format, time_format 和 datetime_format 的 setting 指令设置。当然,也可以使用内建函数 string 来覆盖单独插值的默认格式。
<p>当前日期:${currentDate?string("yyyy-MM-dd HH:mm:ss.SSS")}</p>
输出结果:
<p>当前日期:2020-06-27 22:26:52.225</p>
【注意】
为了将日期显示成文本,FreeMarker 必须知道日期中的哪一部分在使用,也就是说,如果仅仅日期部分 (年,月,日) 使用或仅仅时间部分 (时,分,秒,毫秒) 使用或两部分都用。不幸的是,由于Java平台技术的限制,自动探测一些变量是不现实的。 这时可以找程序员对数据模型中可能出问题的变量进行处理。如果找出时间日期变量的哪部分在使用是不太可能的话,就必须使用 FreeMarker 内建函数 date,time 和 datetime 来识别 ( 比如 ${lastUpdated?datetime} ),否则就会出现错误停止执行。例如:
<p>${currentDate?datetime}</p>
<p>${currentDate?date}</p>
<p>${currentDate?time}</p>
输出结果:
<p>2020-6-27 22:33:29</p>
<p>2020-6-27</p>
<p>22:33:29</p>