模板一览

Page Contents

最简单的模板是纯 HTML 文件(或其他文本文件; FreeMarker 不限于 HTML)。当 Client 端访问该页面时,FreeMarker 会将 HTML 照原样发送给 Client 端。但是,如果您希望该页面更具动态性,则可以开始在 FreeMarker 中理解 HTML 的特殊部分:

  • ${...}:FreeMarker 将在输出中将其替换为大括号内表达式的实际值。它们称为 interpolation

  • FTL 标记 (用于 FreeMarker 模板语言标记):FTL 标记与 HTML 标记有点相似,但是它们是 FreeMarker 的说明,不会打印到输出中。这些标签的名称以#开头。 (用户定义的 FTL 标签使用@而不是#,但是它们是高级主题.)

  • Comments: Comments 与 HTMLComments 相似,但由<#---->分隔。与 HTMLComments 不同,FTLComments 不会进入输出(访问者的页面源中将不可见),因为 FreeMarker 会跳过它们。

任何非 FTL 标签,插值或 Comments 的内容均视为静态文本,FreeMarker 不会对其进行解释;它只是按原样打印到输出中。

对于 FTL 标签,您指的是所谓的“指令”。这与 HTML 标记(例如<table></table>)和 HTML 标记所引用的 HTML 元素(例如table元素)之间的关系相同。 (如果您不了解这种区别,请考虑“ FTL 标签”和“指令”同义词.)

Note:

您可以轻松地尝试在http://freemarker-online.kenshoo.com/上编写模板

一些基本指令

在这里,我们将看一些最常用的指令(但是还有更多)。

if 指令

使用if指令,您可以有条件地跳过模板的一部分。例如,假设您要在第一个例子中与其他用户打招呼的方式与老板 Big Joe 不同:

<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>
    Welcome ${user}<#if user == "Big Joe">, our beloved leader</#if>!
  </h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>

在这里,您已经告诉 FreeMarker,只有变量user的值等于字符串"Big Joe"时,“我们心爱的领导者”才应该存在。通常,如果condition为 false(布尔值),则会跳过<#if condition></#if>标记之间的内容。

让我们更仔细地查看condition==是一个运算符,用于测试其左侧和右侧的值是否相等,并且结果为布尔值,相应地为 true 或 false。在==的左侧,我有引用了一个变量的语法,应该已经熟悉了;这将被替换为变量的值。通常,指令或插值内的未加引号的单词被视为对变量的引用。在右侧,我指定了一个 Literals 字符串。模板中的 Literals 字符串必须始终放在引号内。

这将打印“ Pythons 今天免费!”如果价格为 0:

<#if animals.python.price == 0>
  Pythons are free today!
</#if>

与之前直接指定字符串的情况类似,此处直接指定数字(0)。请注意,该数字未加引号。如果用引号("0")引用,则 FreeMarker 会将其误解为字符串 Literals,并且由于与之比较的价格是数字,因此会出现错误。

这将显示“ Python 今天不是免费的!”如果其价格不为 0:

<#if animals.python.price != 0>
  Pythons are not free today!
</#if>

您可能已经猜到了,!=表示“不等于”。

您也可以这样写(使用用于演示哈希的数据模型):

<#if animals.python.price < animals.elephant.price>
  Pythons are cheaper than elephants today.
</#if>

使用<#else>标记,您可以指定条件为假时该怎么办。例如:

<#if animals.python.price < animals.elephant.price>
  Pythons are cheaper than elephants today.
<#else>
  Pythons are not cheaper than elephants today.
</#if>

上面写着“ Python 比今天的大象便宜”。如果 python 的价格小于大象的价格,否则它将显示“ python 并不比今天的大象便宜”。您可以使用elseif进一步优化:

<#if animals.python.price < animals.elephant.price>
  Pythons are cheaper than elephants today.
<#elseif animals.elephant.price < animals.python.price>
  Elephants are cheaper than pythons today.
<#else>
  Elephants and pythons cost the same today.
</#if>

如果您的变量具有布尔值(对/错),则可以直接将其用作ifcondition

<#if animals.python.protected>
  Pythons are protected animals!
</#if>

list 指令

当您要列出某些东西时,这是必需的。例如,如果您将此模板与先前用于演示序列的数据模型合并:

<p>We have these animals:
<table border=1>
  <#list animals as animal>
    <tr><td>${animal.name}<td>${animal.price} Euros
  </#list>
</table>

那么输出将是:

<p>We have these animals:
<table border=1>
    <tr><td>mouse<td>50 Euros
    <tr><td>elephant<td>5000 Euros
    <tr><td>python<td>4999 Euros
</table>

list指令的通用形式是:<#list sequence as loopVariable>repeatThis</#list>。对于您使用sequence指定的 Sequences,每个项目都会重复repeatThis部分,从第一个项目开始,一个接一个地重复。在所有重复中,loopVariable将保留当前项目的值。此变量仅存在于<#list ...></#list>标记之间。

sequence可以是任何一种表达式。例如,我们可以像这样列出示例数据模型的成果:

<ul>
<#list misc.fruits as fruit>
  <li>${fruit}
</#list>
</ul>

您应该熟悉misc.fruits表达式; 引用数据模型中的变量

上面的示例的一个问题是,如果我们碰巧有 0 个水果,它将仍然打印一个空的<ul></ul>而不是什么也没有。为了避免这种情况,您可以使用list的形式:

<#list misc.fruits>
  <ul>
    <#items as fruit>
      <li>${fruit}
    </#items>
  </ul>
</#list>

在这里,list伪指令代表整个列表,并且每个水果仅重复items伪指令内的部分。如果我们有 0 个水果,则list内的所有内容都会被跳过,因此我们不会有ul标记。

另一个与列表相关的常见任务:让我们列出用逗号分隔水果的结果,例如逗号:

<p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, </#list>
<p>Fruits: orange, banana

sep涵盖的部分(我们也可以这样写:...<#sep>, </#sep></#list>)仅在存在下一项时执行。因此,在最后一个结果之后没有逗号。

同样,如果我们有 0 个水果怎么办?只需打印“水果:”,然后没有任何尴尬。 list就像if一样,可以有else,如果有 0 个列表项,则执行else

<p>Fruits: <#list misc.fruits as fruit>${fruit}<#sep>, <#else>None</#list>

Note:

事实上,这个简单的示例可以这样写,但是它使用的语言设备不在这里讨论:

<p>Fruits: ${fruits?join(", ", "None")}

所有这些指令(listitemssepelse)可以一起使用:

<#list misc.fruits>
  <p>Fruits:
  <ul>
    <#items as fruit>
      <li>${fruit}<#sep> and</#sep>
    </#items>
  </ul>
<#else>
  <p>We have no fruits.
</#list>

Note:

您可以阅读有关这些指令在参考中的更多信息。

include 指令

使用include指令,您可以将另一个文件的内容插入模板。

假设您必须在几页上显示相同的版权声明。您可以创建仅包含版权声明的文件,然后将该文件插入需要版权声明的任何位置。假设您将此版权声明存储在copyright_footer.html中:

<hr>
<i>
Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>,
<br>
All Rights Reserved.
</i>

每当您需要该文件时,只需使用include指令将其插入:

<html>
<head>
  <title>Test page</title>
</head>
<body>
  <h1>Test page</h1>
  <p>Blah blah...
  <#include "/copyright_footer.html">
</body>
</html>

输出将是:

<html>
<head>
  <title>Test page</title>
</head>
<body>
  <h1>Test page</h1>
  <p>Blah blah...
<hr>
<i>
Copyright (c) 2000 <a href="http://www.acmee.com">Acmee Inc</a>,
<br>
All Rights Reserved.
</i>
</body>
</html>

如果您更改copyright_footer.html,则访问者将在所有页面上看到新的版权声明。

Note:

重用代码段的一种更强大的方法是使用宏,但这是高级主题discussed later

一起使用指令

您可以根据需要在页面上多次使用指令,并且可以将指令彼此自由地嵌套在一起。例如,在这里您将if指令嵌套在list指令中:

<#list animals as animal>
      <div<#if animal.protected> class="protected"</#if>>
        ${animal.name} for ${animal.price} Euros
      </div>
</#list>

请注意,由于 FreeMarker 不会解释 FTL 标签,内插和 FTLComments 之外的文本,因此您可以在 HTML 属性内使用 FTL 标签而不会出现问题。

Using built-ins

所谓的内建函数就像不是从数据模型来的而是由 FreeMarker 添加到值中的子变量(或者,如果您知道 Java 术语,则是方法,就像方法一样)。为了明确子变量的来源,您必须使用?(问号)而不是.(点)来访问它们。 一些最常用的内置示例:

  • user?upper_case给出值user的大写形式(例如“ JOHN DOE”而不是“ John Doe”)

  • animal.name?cap_first将首字母转换为大写的animal.name(例如“鼠标”而不是“鼠标”)

  • user?length给出user值中的字符数量(“ John Doe”为 8)

  • animals?size给出animals序列中* items *的数量(在我们的示例数据模型中为 3)

  • 如果您位于<#list animals as animal>和相应的</#list>标签之间:

  • animal?indexanimals内部给出从animal的基于 0 的索引

    • 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(", "):通过串联项目并在每个项目之间插入参数分隔符(例如“橙色,香蕉”),将列表转换为字符串

  • user?starts_with("J")给出布尔值 true 为 false 取决于user是否以字母“ J”开头。

  • animals?filter(it -> it.protected)列出了受保护的动物。要仅列出受保护的动物,可以使用<#list animals?filter(it -> it.protected) as animal>...</#list>

内置应用程序可以被链接,例如fruits?join(", ")?upper_case会先将列表 a 转换为字符串,然后将其转换为大写字母。 (这就像您也可以链接. -s(点)。)

您可以找到参考中的完整内置组件

处理缺失的变量

数据模型通常具有可选的变量(即有时会丢失)。为了发现一些典型的人为错误,FreeMarker 不会容忍对丢失变量的引用,除非您明确告诉变量丢失了该怎么办。在这里,我们将展示两种最典型的方法。

程序员注意:对于 FreeMarker,不存在的变量和值为null的变量是相同的。此处使用的“缺失”一词涵盖了两种情况。

无论在何处引用变量,都可以通过在变量名后加上!和默认值来为丢失变量的情况指定默认值。像下面的示例一样,当数据模型中缺少user时,模板的行为类似于user的值是字符串"visitor"。 (当不缺少user时,此模板的行为与${user}完全相同):

<h1>Welcome ${user!"visitor"}!</h1>

您可以通过在变量名后加上??来询问变量是否不丢失。如果缺少user变量,则将其与已经引入的if指令结合使用可以跳过整个问候:

<#if user??><h1>Welcome ${user}!</h1></#if>

关于使用多个步骤(例如animals.python.price)进行变量访问,只有在animals.python从不丢失并且只有最后一个子变量price可能丢失的情况下,编写animals.python.price!0才是正确的(在这种情况下,我们假设它是0)。如果缺少animalspython,则模板处理将因“未定义的变量”错误而停止。为了防止这种情况,您必须编写(animals.python.price)!0。在这种情况下,即使缺少animalspython,表达式也将是0??使用相同的逻辑; animals.python.price??(animals.python.price)??

用于 HTML,XML 和其他标记的转义

假设模板生成 HTML,并且您插入的值${...}是纯文本(非 HTML),例如来自数据库的公司名称。在 HTML 中具有特殊含义的字符必须以此类值“转义” *,例如name是“ Someone&Co.”。然后${name}应打印“ Someone É Co.”。

FreeMarker 会自动转义使用${...}打印的所有值,如果配置正确(这是程序员的责任; 看这里如何)。建议的做法是使用ftlh文件 extensions 激活 HTML 自动转义,并使用ftlx文件 extensions 激活 XML 自动转义。

您可以尝试是否像${"<"}一样启用自动转义,然后检查原始输出(用于 HTML 或 XML 转义)。如果不是这样,并且不会调整配置,请将其添加为模板的第一行:

(如果生成 XML,请使用上面的"XML"而不是上面的"HTML".)

如果要打印的字符串值故意包含标记,则必须防止自动转义,例如${value?no_esc}

您可以找到有关自动转义和输出格式的更多信息here...

Note:

这里描述的自动转义至少需要 FreeMarker 2.3.24. 如果必须使用早期版本,请改用不推荐使用的escape directive