FreeMarker详解

什么是 FreeMarker?

FreeMarker 是一款 模板引擎: 即一种基于模板和要改变的数据, 并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。 它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。

模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言, 不是 像PHP那样成熟的编程语言。 那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算, 之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据, 而在模板之外可以专注于要展示什么数据。

Figure

这种方式通常被称为 MVC (模型 视图 控制器) 模式,对于动态网页来说,是一种特别流行的模式。 它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑, 在没有程序员来修改或重新编译代码时,也可以修改页面的样式。

而FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到 Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。

FreeMarker 是 免费的, 基于Apache许可证2.0版本发布。

如果你发现 任何错误 (包括 语法错误错别字, 排版错误) 或者是在文档中找到 误导或混淆 ,或有其他建议,请联系原作者! Email: ddekany at users.sourceforge.net

文档翻译的任何问题(包括语法错误,错别字)或中文技术交流,可以联系译者:nanlei1987 at gmail.com, 或在FreeMarker的Github上Fork一份,修改之后提交Pull Request。我们共同研究,共同进步。

英文版文档的作者(也是FreeMarker项目的维护者)是匈牙利人,其母语非英语,那么在这种情况下, 翻译过程难免会有错误存在,译者结合自身多年对FreeMarker的实践力争精准,译文力求信达雅。 但因个人才疏学浅,水平有限,恳请广大读者批评指正。最好的方式就是使用Github了

手册的更新根据原作者更新,大家的反馈随时进行。但只在有阶段性成果时才会提交发布修正版本。

本翻译是免费的,您可以自由下载和传播,但不可用于任何商业行为。文档版权归译者本人所有, 原版归FreeMarker项目组所有,您可以引用其中的描述,但必须指明出处。如需用于商业行为, 您必须和原作者取得联系。

模板 + 数据模型 = 输出

假设在一个在线商店的应用系统中需要一个HTML页面,和下面这个页面类似:

<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>Welcome John Doe!</h1>
  <p>Our latest product:
  <a href="products/greenmouse.html">green mouse</a>!
</body>
</html>

这里的用户名(上面的"Big Joe"),应该是登录这个网页的访问者的名字, 并且最新产品的数据应该来自于数据库,这样它才能随时更新。那么不能直接在HTML页面中输入它们, 不能使用静态的HTML代码。此时,可以使用要求输出的 模板。 模板和静态HTML是相同的,只是它会包含一些 FreeMarker 将它们变成动态内容的指令:

<html>
<head>
  <title>Welcome!</title>
</head>
<body>
  <h1>Welcome ${user}!</h1>
  <p>Our latest product:
  <a href="${latestProduct.url}">${latestProduct.name}</a>!
</body>
</html>

模板文件存放在Web服务器上,就像通常存放静态HTML页面那样。当有人来访问这个页面, FreeMarker将会介入执行,然后动态转换模板,用最新的数据内容替换模板中 ${...} 的部分, 之后将结果发送到访问者的Web浏览器中。访问者的Web浏览器就会接收到例如第一个HTML示例那样的内容 (也就是没有FreeMarker指令的HTML代码),访问者也不会察觉到服务器端使用的FreeMarker。 (当然,存储在Web服务器端的模板文件是不会被修改的;替换也仅仅出现在Web服务器的响应中。)

请注意,模板并没有包含程序逻辑来查找当前的访问者是谁,或者去查询数据库获取最新的产品。 显示的数据是在 FreeMarker 之外准备的,通常是一些 "真正的" 编程语言(比如Java) 所编写的代码。模板作者无需知道这些值是如何计算出的。事实上,这些值的计算方式可以完全被修改, 而模板可以保持不变,而且页面的样式也可以完全被修改而无需改动模板。 当模板作者(设计师)和程序员不是同一人时,显示逻辑和业务逻辑相分离的做法是非常有用的, 即便模板作者和程序员是一个人,这么来做也会帮助管理应用程序的复杂性。 保证模板专注于显示问题(视觉设计,布局和格式化)是高效使用模板引擎的关键。

为模板准备的数据整体被称作为 数据模型。 模板作者要关心的是,数据模型是树形结构(就像硬盘上的文件夹和文件),在视觉效果上, 数据模型可以是:

(root)
  |
  +- user = "Big Joe"
  |
  +- latestProduct
      |
      +- url = "products/greenmouse.html"
      |
      +- name = "green mouse"
Note:

上面只是一个形象化显示;数据模型不是文本格式,它来自于Java对象。 对于Java程序员来说,root就像一个有 getUser() 和 getLatestProduct() 方法的Java对象, 也可以有 "user" 和 "latestProducts" 键值的Java Map对象。相似地,latestProduct 就像是有 getUrl() 和getName() 方法的Java对象。

早期版本中,可以从数据模型中选取这些值,使用 user 和 latestProduct.name 表达式即可。如果我们继续类推, 数据模型就像一个文件系统,那么 "(root)" 和 latestProduct 就对应着目录(文件夹),而 userurl 和 name 就是这些目录中的文件。

总的来说,模板和数据模型是FreeMarker来生成输出(比如第一个展示的HTML)所必须的:

模板 + 数据模型 = 输出

数据模型一览

正如已经看到的,数据模型的基本结构是树状的。 这棵树可以很复杂,并且可以有很大的深度,比如:

(root)
  |
  +- animals
  |   |
  |   +- mouse
  |   |   |   
  |   |   +- size = "small"
  |   |   |   
  |   |   +- price = 50
  |   |
  |   +- elephant
  |   |   |   
  |   |   +- size = "large"
  |   |   |   
  |   |   +- price = 5000
  |   |
  |   +- python
  |       |   
  |       +- size = "medium"
  |       |   
  |       +- price = 4999
  |
  +- message = "It is a test"
  |
  +- misc
      |
      +- foo = "Something"

上图中的变量扮演目录的角色(比如 root, animalsmouseelephantpythonmisc) 被称为 hashes (哈希表或哈希,译者注)。哈希表存储其他变量(被称为 子变量), 它们可以通过名称来查找(比如 "animals", "mouse" 或 "price")。

存储单值的变量 (sizepricemessage 和 foo) 称为 scalars (标量,译者注)。

如果要在模板中使用子变量, 那应该从根root开始指定它的路径,每级之间用点来分隔开。要访问 mouse 的 price ,要从root开始,首先进入到 animals ,之后访问 mouse ,最后访问 price 。就可以这样来写 animals.mouse.price

另外一种很重要的变量是 sequences (序列,译者注)。 它们像哈希表那样存储子变量,但是子变量没有名字,它们只是列表中的项。 比如,在下面这个数据模型中, animals 和 misc.fruits 就是序列:

(root)
  |
  +- animals
  |   |
  |   +- (1st)
  |   |   |
  |   |   +- name = "mouse"
  |   |   |
  |   |   +- size = "small"
  |   |   |
  |   |   +- price = 50
  |   |
  |   +- (2nd)
  |   |   |
  |   |   +- name = "elephant"
  |   |   |
  |   |   +- size = "large"
  |   |   |
  |   |   +- price = 5000
  |   |
  |   +- (3rd)
  |       |
  |       +- name = "python"
  |       |
  |       +- size = "medium"
  |       |
  |       +- price = 4999
  |
  +- misc
      |
      +- fruits
          |
          +- (1st) = "orange"
          |
          +- (2nd) = "banana"

要访问序列的子变量,可以使用方括号形式的数字索引下标。 索引下标从0开始(从0开始也是程序员的传统),那么第一项的索引就是0, 第二项的索引就是1等等。要得到第一个动物的名称的话,可以这么来写代码 animals[0].name。要得到 misc.fruits 中的第二项(字符串"banana")可以这么来写 misc.fruits[1]。(实践中,通常按顺序遍历序列,而不用关心索引, 这点会在 后续介绍。)

标量类型可以分为如下的类别:

  • 字符串:就是文本,也就是任意的字符序列,比如上面提到的 ''m'', ''o'', ''u'', ''s'', ''e''。比如 name 和 size 也是字符串。

  • 数字:这是数值类型,就像上面的 price。 在FreeMarker中,字符串 "50" 和数字 50 是两种完全不同的东西。前者是两个字符的序列 (这恰好是人们可以读的一个数字),而后者则是可以在数学运算中直接被使用的数值。

  • 日期/时间: 可以是日期-时间格式(存储某一天的日期和时间), 或者是日期(只有日期,没有时间),或者是时间(只有时间,没有日期)。

  • 布尔值:对应着对/错(是/否,开/关等值)类似的值。 比如动物可以有一个 protected (受保护的,译者注) 的子变量, 该变量存储这个动物是否被保护起来的值。

总结:

  • 数据模型可以被看成是树形结构。

  • 标量用于存储单一的值。这种类型的值可以是字符串,数字,日期/时间或者是布尔值。

  • 哈希表是一种存储变量及其相关且有唯一标识名称的容器。

  • 序列是存储有序变量的容器。存储的变量可以通过数字索引来检索,索引通常从0开始。

Note:

还有一些其它更为高级的类型,在这里我们并没有涉及到,比如方法和指令。

模板一览

最简单的模板通常是普通的HTML文件(或者是其他任何文本文件; FreeMarker本身不属于HTML)。当客户端访问某个页面时, FreeMarker要发送HTML代码至客户端浏览器中去显示。如果想要页面动起来 (这里指动态网页技术,译者注),那么就要在HTML中放置能被FreeMarker所解析的特殊代码片段:

  • ${...}: FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为 interpolation(插值,译者注)。

  • FTL 标签 (FreeMarker模板的语言标签): FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。 这些标签的名字以 # 开头。(用户自定义的FTL标签则需要使用 @ 来代替 #,但这属于更高级的话题了。)

  • 注释: 注释和HTML的注释也很相似, 但是它们使用 <#-- and --> 来标识。 不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中), 因为 FreeMarker会跳过它们。

其他任何不是FTL标签,插值或注释的内容将被视为静态文本, 这些东西不会被FreeMarker所解析;会被按照原样输出出来。

FTL标签也被称为 指令。 这些指令在HTML的标签 (比如: <table> 和 </table>) 和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,当和 "Big Joe" 相同时 ", our beloved leader" (我们最尊敬的领导,译者注) 才是if条件中那唯一的 user 变量的值。 通常来讲,如果 condition 是false(布尔值),那么介于 <#if condition> 和 </#if> 标签中的内容会被略过。

我们来详细说说 condition 的使用: == 是用来判断它两侧的值是否相等的操作符, 比较的结果是布尔值,也就是true或者false。在 == 的左侧,是 被引用的变量, 我们很熟悉这样的语法结构;最终它会被变量的值所替代。通常来说, 在指令或插值中没有被引号标注的内容都被视为变量的引用。右侧则是指定的字符串, 在模板中的字符串 只能 放在引号内。

当价格为0时,就会打印出 "Pythons are free today!":

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

和之前示例中,字符串被直接指定相似, 但这里则是数字(0)被直接指定了。 请注意,这里的数字 没有 放在引号内。 如果将("0")放在引号中, 那么FreeMarker就会将其误判为字符串了(也就是字符串0,译者注)。

当价格不为0时,则会打印出"Pythons are not free today!":

<#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> 标签可以指定当条件为false时程序所要执行的内容。比如:

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

这个示例中,如果蟒蛇的价格比大象的价格低的话, 程序将会打印出 "Pythons are cheaper than elephants today."。 否则会打印 "Pythons are not cheaper than elephants today."。 后面也可以使用 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>

如果变量本身就是布尔值(true/false),则可以直接让其作为 if 的 condition (判断条件,译者注):

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

list 指令

当需要列表显示内容时,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>。 repeatThis 部分将会在给定的 sequence 遍历时在每一项中重复, 从第一项开始,一个接着一个。在所有的重复中, 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个水果,会怎么样?只是打印 "Fruits:" 也没有什么不方便。 list 指令,也像 if 指令那样,可以有 else部分,如果列表中有0个元素时就会被执行:

<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:

重用代码片段的一个更有力的方式是使用宏,但是只是更为高级的话题了, 将会在 后续讨论

联合使用指令

在页面上也可以多次使用指令,而且指令间也可以很容易地相互嵌套。 比如,在 list 指令中嵌套 if 指令:

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

请注意,FreeMarker并不解析FTL标签以外的文本、插值和注释, 上面示例在HTML属性中使用FTL标签也不会有问题。

使用内建函数

内建函数很像子变量(如果了解Java术语的话,也可以说像方法), 它们并不是数据模型中的东西,是 FreeMarker 在数值上添加的。 为了清晰子变量是哪部分,使用 ?(问号)代替 .(点)来访问它们。常用内建函数的示例:

  • user?html 给出 user 的HTML转义版本, 比如 & 会由 &amp; 来代替。

  • user?upper_case 给出 user 值的大写版本 (比如 "JOHN DOE" 来替代 "John Doe")

  • animal.name?cap_first 给出 animal.name 的首字母大写版本(比如 "Mouse" 来替代 "mouse")

  • user?length 给出 user 值中 字符的数量(对于 "John Doe" 来说就是8)

  • animals?size 给出 animals 序列中 项目 的个数(我们示例数据模型中是3个)

  • 如果在 <#list animals as animal> 和对应的 </#list> 标签中:

    • animal?index 给出了在 animals 中基于0开始的 animal的索引值

    • 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(", ") 通过连接所有项,将列表转换为字符串, 在每个项之间插入参数分隔符(比如 "orange,banana")

  • user?starts_with("J") 根据 user 的首字母是否是 "J" 返回布尔值true或false。

内建函数应用可以链式操作,比如user?upper_case?html 会先转换用户名到大写形式,之后再进行HTML转义。(这就像可以链式使用 .(点)一样)

可以阅读 全部内建函数参考

处理不存在的变量

数据模型中经常会有可选的变量(也就是说有时并不存在)。 除了一些典型的人为原因导致失误外,FreeMarker 绝不能容忍引用不存在的变量, 除非明确地告诉它当变量不存在时如何处理。这里来介绍两种典型的处理方法。

这部分对程序员而言: 一个不存在的变量和一个是 null 值的变量, 对于FreeMarker来说是一样的,所以这里所指的"丢失"包含这两种情况。

不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况, 通过在变量名后面跟着一个 !(叹号,译者注)和默认值。 就像下面的这个例子,当 user 不存在于数据模型时, 模板将会将 user 的值表示为字符串 "visitor"。(当 user 存在时, 模板就会表现出 ${user}的值):

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

也可以在变量名后面通过放置 ?? 来询问一个变量是否存在。将它和 if 指令合并, 那么如果 user 变量不存在的话将会忽略整个问候的代码段:

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

关于多级访问的变量,比如 animals.python.price, 书写代码:animals.python.price!0 当且仅当 animals.python 永远存在, 而仅仅最后一个子变量 price 可能不存在时是正确的 (这种情况下我们假设价格是 0)。 如果 animals 或 python 不存在, 那么模板处理过程将会以"未定义的变量"错误而停止。为了防止这种情况的发生, 可以如下这样来编写代码 (animals.python.price)!0。 这种情况就是说 animals 或 python 不存在时, 表达式的结果是 0。对于 ?? 也是同样用来的处理这种逻辑的; 将 animals.python.price?? 对比(animals.python.price)??来看。

类型

支持的类型有:

标量

标量是最基本,最简单的数值类型,它们可以是:

  • 字符串:表示简单的文本,例如:产品的名称。

    如果想在模板中直接给出字符串值,而不是使用数据模型中的变量, 那么将文本内容写在引号内即可,比如 "green mouse" 或 'green mouse'。(关于语法的更多细节可以在 后续章节中找到。)

  • 数值:比如说,产品的价格。 整数和非整数是不区分的;只有单一的数字类型。比如使用了计算器, 计算3/2的结果是1.5而不是1。

    如果要在模板中直接给出数字的值,那么可以这么来写: 150 或 -90.05 或 0.001。(关于语法的更多细节可以在 后续章节中找到。)

  • 布尔值:布尔值代表了逻辑上的对或错(是或否)。比如:用户是否登录了。 典型的应用是使用布尔值作为 if 指令的条件, 比如 <#if loggedIn >...</#if> 或者 <#if price == 0>...</#if>; 后面这个 price == 0 部分的结果就是布尔值。

    在模板中可以使用保留字 true 和 false 来指定布尔值。

  • 日期:日期变量可以存储和日期/时间相关的数据。 一共有三种变化:

    • 日期:精确到天的日期,没有时间部分。比如April 4, 2003。

    • 时间:精确到毫秒,没有日期部分。比如10:19:18 PM。

    • 日期-时间(有时也被称为"时间戳"),比如April 4,2003 10:19:18 PM。 有日期和时间两部分,时间部分的存储精确到毫秒。

    不幸的是,受到Java平台的限制,FreeMarker 有时是不能决定日期的部哪分被使用 (也就是说,是日期-时间格式,日期格式还是时间格式)。 这个问题的解决方法是一个的高级话题,将会在 后续章节讨论。

    模板中直接定义日期数值是可以的,但这也是高级话题,将会在 后续章节 中进行解释。

要记住,FreeMarker区别字符串,数字,布尔值和日期类型的值。比如, 字符串 "150" 看起来很像数字 150, 字符串只是字符的任意序列,不能将它用于计算目的,也不能和其它数字进行比较等等。

容器

这些值存在的目的是为了包含其他变量;它们只是容器。 它们包含的变量通常视为 subvariables (子变量,译者注)。容器的类型有:

  • 哈希表:每个子变量都可以通过一个唯一的名称来查找。 这个名称是不受限制的字符串。哈希表 并不确定其中子变量的顺序。 也就是说没有第一个子变量,第二个子变量这样的说法等;变量仅仅是通过名称来访问的。 (就像Java语言中的HashMap一样,是实现了Hash算法的Map,不记录内部元素的顺序, 仅仅通过名称来访问。译者注)

  • 序列:每个子变量通过一个整数来标识。第一个子变量的标识符是0, 第二个是1,第三个是2,这样来类推,而且子变量是有顺序的。这些数次通常被称为 indexes(索引,译者注)。序列通常比较密集,也就是所有的索引, 包括最后一个子变量的,它们和子变量都是相关联的,但不是绝对必要的。 子变量的类型也并不需要完全一致。

  • 集合:从模板设计者角度来看,集合是有限制的序列。不能获取集合的大小, 也不能通过索引取出集合中的子变量,但是它们仍然可以通过 list 指令来遍历。

请注意,一个值也可有多种类型 一个值也可有多种类型, 对于一个值可能同时存在哈希表和序列这两种类型,这时, 该变量就支持索引和名称两种访问方式。 不过容器基本是当作哈希表或者序列来使用的,而不是两者同时使用。

尽管存储在哈希表,序列(集合)中的变量可以是任意类型的, 这些变量也可以是哈希表,序列(或集合)。这样就可以构建任意深度的数据结构。

数据模型本身(最好说成是它的根root)也是哈希表。

子程序

方法和函数

当一个值是方法或函数的时候,那么它就可以计算其他值,结果取决于传递给它的参数。

这部分是对程序员来说的:方法/函数是一等类型值, 就像函数化的编程语言。也就是说函数/方法也可以是其他函数/方法的参数或者返回值, 并可以把它们定义成变量等。

假设程序员在数据模型中放置了一个方法变量 avg, 该变量用来计算数字的平均值。如果给定3和5作为参数,访问 avg 时就能得到结果4。

方法的使用将会在 后续章节 中进行解释, 下面这个示例会帮助我们理解方法的使用:

The average of 3 and 5 is: ${avg(3, 5)}
The average of 6 and 10 and 20 is: ${avg(6, 10, 20)}
The average of the price of a python and an elephant is:
${avg(animals.python.price, animals.elephant.price)}

将会输出:

The average of 3 and 5 is: 4
The average of 6 and 10 and 20 is: 12
The average of the price of a python and an elephant is:
4999.5

那么方法和函数有什么区别呢?这是模板作者所关心的, 它们没有关系,但也不是一点关系都没有。 方法是来自于数据模型 (它们反射了Java对象的方法) 而函数是定义在模板内的 (使用 function 指令 -- 也是高级话题),但二者可以用同一种方式来使用。

用户自定义指令

这种类型的值可以作为用户自定义指令(换句话说,就是FreeMarker的标签) 用户自定义指令是一种子程序,一种可以复用的模板代码段。但这也是一个高级话题, 将会在 后续章节 中进行解释。

这部分是对程序员来说的: 用户自定义指令(比如宏)也是一等值类型,就像函数/方法一样。

这里仅仅对用户自定义指令有一个认识即可(如果现在还不能理解可以先忽略它)。 假设现在有一个变量 box,它的值是用户自定义的指令, 用来打印一些特定的HTML信息,包含标题和一条信息。那么, box 变量就可以在模板中使用(示例如下):

<@box title="Attention!">
  Too much copy-pasting may leads to
  maintenance headaches.
</@box>

函数/方法和用户自定义指令的比较

这部分内容也是对高级用户来说的(如果还不能理解可以先忽略它)。 如果要使用函数/方法或自定义指令去实现一些东西的时候, 二者之间的选择是两难的。按经验来说,如果能够实现需求, 请先用自定义指令而不要用函数/方法。如果:

  • ... 输出(返回值)的是标记(HTML,XML等)。 主要原因是函数的返回结果可以自动进行XML转义(这是因为 ${...} 的特性), 而用户自定义指令的输出则不是 (这是因为 <@...> 的特性所致; 它的输出假定是标记,因此已经转义过了)。

  • ... 副作用也是很重要的一点,它没有返回值。 例如一个指令的目的是往服务器日志中添加一条。 (事实上不能得到自定义指令的返回值, 但有些反馈的类型是有可能设置非本地变量的。)

  • ... 会进行流程的控制(就像 list 或 if 指令那样)。但是不能在函数/方法上这么做。

在模板中,FreeMarker不知道的Java对象的方法通常是可以作为方法来使用的, 而不用考虑Java对象方法本身的特性,因为在这里没有其他的选择。

其它

结点

结点变量代表了树状结构中的一个结点,而且通常是配合 XML 处理的,这是专业而且更高级的话题。

这里我们仅对 高级用户 进行一个概要说明: 结点和存储在其他结点中的序列很相似,通常也被当作为子结点。结点存储它所在的容器结点的引用, 也就是父结点。结点的主要作用是拓扑信息;其它数据必须通过使用多类型的值来存储。 就像一个值可以同时是一个结点和一个数字,这样它存储的数字可以作为如支付额来使用。 除了拓扑信息,结点也可以存储一些元信息(即metadata,译者注):如结点名称,它的类型(字符串), 命名空间(字符串)。若一个结点象征XHTML文档中的 h1 元素, 那么它的名字可以是 "h1",类型可以是 "element", 命名空间可以是 "http://www.w3.org/1999/xhtml"。但对于数据模型设计者来说, 这些元信息,还有如何来使用它们又有什么意义呢。检索拓扑信息和元信息的方法将会在 后续章节 中来说明(这里可以先不用理解它们)。

总体结构

实际上用程序语言编写的程序就是模板。 FTL (代表FreeMarker模板语言)。 这是为编写模板设计的非常简单的编程语言。

模板(FTL编程)是由如下部分混合而成的:

  • 文本:文本会照着原样来输出。

  • 插值:这部分的输出会被计算的值来替换。插值由 ${ and } 所分隔(或者 #{ and },这种风格已经不建议再使用了;点击查看更多)。

  • FTL 标签:FTL标签和HTML标签很相似,但是它们却是给FreeMarker的指示, 而且不会打印在输出内容中。

  • 注释:注释和HTML的注释也很相似,但它们是由 <#-- 和 -->来分隔的。注释会被FreeMarker直接忽略, 更不会在输出内容中显示。

我们来看一个具体的模板。其中的内容已经用颜色来标记了: 文本, 插值, FTL 标签, 注释。为了看到可见的 换行符, 这里使用了 [BR]

<html>[BR]
<head>[BR]
  <title>Welcome!</title>[BR]
</head>[BR]
<body>[BR]
  <#-- Greet the user with his/her name -->[BR]
  <h1>Welcome ${user}!</h1>[BR]
  <p>We have these animals:[BR]
  <ul>[BR]
  <#list animals as animal>[BR]
    <li>${animal.name} for ${animal.price} Euros[BR]
  </#list>[BR]
  </ul>[BR]
</body>[BR]
</html>

FTL是区分大小写的。 list 是指令的名称而 List 就不是。类似地 ${name} 和 ${Name} 或 ${NAME} 也是不同的。

请注意非常重要的一点: 插值 仅仅可以在 文本 中使用。 (也可以是字符串表达式;请参考 后续内容)

FTL 标签 不可以在其他 FTL 标签 和 插值中使用。比如, 这样做是 错误 的: <#if <#include 'foo'>='bar'>...</#if>

注释 可以放在 FTL 标签 和 插值中。比如:

<h1>Welcome ${user <#-- The name of user -->}!</h1>[BR]
<p>We have these animals:[BR]
<ul>[BR]
<#list <#-- some comment... --> animals as <#-- again... --> animal>[BR]
...
Note:

如果您已经自行尝试测试上面所有示例的话:那么也许会注意到,一些空格、 制表符和换行符从模板的输出中都不见了,尽管我们之前已经说了 文本 是按照原样输出的。 现在不用为此而计较,这是由于FreeMarker的"空格剥离"特性在起作用, 它当然会自动去除一些多余的空格,制表符和换行符了。 这个特性在 后续内容 中会解释到。

指令

使用 FTL标签来调用 指令。 在示例中已经调用了 list 指令。在语法上我们使用了两个标签: <#list animals as animal> 和 </#list>

FTL 标签分为两种:

  • 开始标签: <#directivename parameters>

  • 结束标签: </#directivename>

除了标签以 # 开头外,其他都和HTML,XML的语法很相似。 如果标签没有嵌套内容(在开始标签和结束标签之间的内容),那么可以只使用开始标签。 例如 <#if something>...</#if>, 而FreeMarker知道 <#include something> 中的 include 指令没有可嵌套的内容。

parameters 的格式由 directivename来决定。

事实上,指令有两种类型: 预定义指令 和 用户自定义指令。 对于用户自定义的指令使用 @ 来代替 #,比如,<@mydirectiveparameters>...</@mydirective>。 更深的区别在于如果指令没有嵌套内容,那么必须这么使用 <@mydirective parameters />,这和XML语法很相似 (例如 <img ... />)。 但用户自定义指令是更高级的话题,将会在 后续章节 中继续讨论。

像HTML标签一样,FTL标签也必须正确地嵌套使用。下面这段示例代码就是错的, 因为 if 指令在 list 指令嵌套内容的内外都有:

<ul>
<#list animals as animal>
  <li>${animal.name} for ${animal.price} Euros
  <#if user == "Big Joe">
     (except for you)
</#list> <#-- WRONG! The "if" has to be closed first. -->
</#if>
</ul>

请注意,FreeMarker 仅仅关心FTL标签的嵌套而不关心HTML标签的嵌套。 它只会把HTML看做是文本,不会来解释HTML。

如果你尝试使用一个不存在的指令(比如,输错了指令的名称), FreeMarker 就会拒绝执行模板,同时抛出错误信息。

FreeMarker会忽略FTL标签中多余的 空白标记,所以也可以这么来写代码:

<#list[BR]
  animals       as[BR]
     animal[BR]
>[BR]
${animal.name} for ${animal.price} Euros[BR]
</#list    >

当然,也不能在 <</ 和指令名中间入空白标记。

所有指令的详细介绍可以在 模板语言参考 /指令参考 中找到(但是建议您先看表达式章节)。

Note:

通过配置,FreeMarker 可以在FTL标签和FTL注释中, 使用 [ 和 ] 来代替 < 和 >,就像 [#if user == "Big Joe"]...[/#if]。 要获取更多信息,请参考:其它/替换(方括号)语法

Note:

通过配置,FreeMarker 可以不需要 # 来理解预定义指令(比如 <if user == "Big Joe">...</if>)。 而我们不建议这样来使用。要获取更多信息,请参考:模板语言参考 /废弃的 FTL 结构/老式 FTL 语法

引用:http://freemarker.foofun.cn/dgui_template_exp.html

原文地址:https://www.cnblogs.com/yangyongjie/p/7515880.html