插值

概览

插值的使用格式是: ${expression},这里的 expression 可以是所有种类的表达式(比如 ${100 + x})。

插值是用来给 表达式 插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:在 文本 (比如 <h1>Hello ${name}!</h1>) 和 字符串表达式 (比如 <#include "/footer/${company}.html">)中。

表达式的结果必须是字符串,数字或者日期/时间/日期-时间值, 因为(默认是这样)仅仅这些值可以被插值自动转换为字符串。其它类型的值 (比如布尔值,序列)必须 "手动地" 转换成字符串(后续会有一些建议), 否则就会发生错误,中止模板执行。

Warning!

一个常犯的错误是在不能使用插值的地方使用了它。 插值 仅仅文本 (比如 <h1>Hello ${name}!</h1>) 和 字符串 (比如 <#include "/footer/${company}.html">)中起作用。 典型的 错误 使用是 <#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 包含 &, 在输出中它就会被替换成 &amp;, 而页面还会保持为格式良好的HTML。如果用户注释包含如 <iframe>(或其它元素)的标记,那么就会被转义成 &lt;iframe&gt; 的样子,使他们没有任何有害点。 但有时在数据模型中真的需要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 来设置;详情请参考 setting 指令部分的文档)。 可以使用 内建函数 string 为一个插值来重写默认数值格式。

小数的分隔符通常(其他类似的符号也是这样,如分组符号) 是根据所在地的标准(语言,国家)来确定的,这也需要程序员来设置。例如这个模板:

${1.5}

如果当前本地化设置为英语时,将会输出:

1.5

而当前地区为德国时,将会输出:

1,5

这是因为德国人使用逗号作为小数点。

Warning!

可以看出,插值的打印都是给用户看的(至少是这样的), 而不是给''计算机''的。有时候这样并不好,比如要打印数据库记录的主键, 用来作为URL中的一部分或HTML表单的隐藏域来作为提交内容, 或者要打印CSS/JavaScript中的数字,因为这些值都是给计算机程序去识别的而不是给用户看的。 很多程序对数字格式的要求非常严格,它们只能理解一部分简单的美式数字格式。那样的话, 可以使用内建函数c (代表''计算机'')来解决这个问题,比如:

<a href="/shop/productdetails?id=${product.id?c}">Details...</a>

日期/时间插入指南

如果表达式的值是时间日期类型,那么日期中的数字将会按照默认格式来转换成文本。 通常程序员应该设置默认格式,而页面设计者无需处理这一点。(如果需要的话, 可以参考 date_formattime_formatdatetime_format setting 指令设置)。当然,也可以使用 内建函数string 来覆盖单独插值的默认格式。

Warning!

为了将日期显示成文本,FreeMarker 必须知道日期中的哪一部分在使用,也就是说, 如果仅仅日期部分(年,月,日)使用或仅仅时间部分(时,分,秒,毫秒)使用或两部分都用。 不幸的是,由于Java平台技术的限制,自动探测一些变量是不现实的。 这时可以找程序员对数据模型中可能出问题的变量进行处理。 如果找出时间日期变量的哪部分在使用是不太可能的话,就必须帮助 FreeMarker 使用内建函数 datetimedatetime 来识别 (比如 ${lastUpdated?datetime} ),否则就会出现错误停止执行。

布尔值插入指南

若要使用插值方式来打印布尔值会引起错误,中止模板的执行。 例如: ${a == 2} 就会引起错误, 它不会打印''true''或其他内容。这是因为没有全局来表示布尔值的好方法 (有时想输出yes/no,但有时是想要enabled/disabled,on/off等等)。

我们可以使用内建函数 ?string 来将布尔值转换为字符串形式。比如输出变量"married"的值(假设它是一个布尔值), 那么可以这么来写: ${married?string("yes", "no")}

可以使用设置参数 boolean_format 来为 FreeMarker 配置默认的布尔值格式。那么,直接编写 ${married} 这样的代码就不会有问题了。但在很多应用程序中, 这样的做法是不推荐使用的,因为布尔值在不同的地方就应该呈现出不同的格式, 同时将格式留作默认值也可以认为是疏忽,因为这可能导致错误产生。

当想生成JavaScript或其它计算机语言代码部分时,那么可以考虑使用 ${someBoolean?c}("c" 代表计算机)来输出布尔值true/false。 (请记住 ?c 也可以用来输出给计算机看的数字。)

精确的转换规则

对于有兴趣研究的人,表达式的值转换为字符串(仍受限于转义) 精确的规则就是下面这些,以这个顺序进行:

  1. 如果这个值是数字,那么它会按照指定的 number_format 设置规则来转换为字符串。所以这些转换通常是对用户进行的,而不是对计算机。

  2. 如果这个值是日期,时间或时间日期类型的一种,那么它们会按照指定的 date_formattime_format 或者 datetime_format 设置规则来转换为字符串。 如果它不能被探测出来是哪种日期类型(日期或时间或日期时间)时,就会发生错误了。

  3. 如果值本来就是字符串类型的,不需要转换。

  4. 如果 FreeMarker 引擎在传统兼容模式下:

    1. 如果值是布尔类型,true值就转换成"true",false值将会转换为空字符串。

    2. 如果表达式未被定义(null 或者变量未定义), 那么就转换为空字符串。

    3. 否则就会发生错误中止模板执行。

  5. 否则就会发生错误中止模板执行。