(二)FreeMarker模板开发

来源:互联网 发布:javascript初级教程 编辑:程序博客网 时间:2024/04/30 12:04

1、模板开发入门

1)、模板的引入

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

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

对于用户、最新产品的URL和名称,不能使用静态的HTML代码,那样是不能即时改变的。对于这个问题,FreeMarker的解决方案是使用模板来代替静态的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将会介入执行,然后动态转换模板,用最新的数据内容替换模板中${…}的部分(例如:用Big Joe或者其他的访问者的用户名来代替${user}),生成普通的HTML文本并发送结果到访问者的Web浏览器中去显示。所以访问者的Web浏览器会接收到类似于第一个HTML示例的内容(也就是说,显示普通的HTML文本而没有FreeMarker的指令),因为浏览器也不会感知到FreeMarker在服务器端被调用了。模板文件本身(存储在Web服务器端的文件)在这个过程中也不会改变什么,所以这个转换过程发生在一次又一次的访问中。这样就保证了显示的信息总是即时的。

如果我们的信息更新的不是很频繁,可以将转换的结果直接保存成静态页面,这样就不用每次访问都转换,效率会提高很大。

该模板并没有包含关于如何找出当前的访问者是谁,或者是如何去查询数据库中查找最新的产品的指令。它似乎已经知道了这些数据是什么。事实也确实是这样的,在FreeMarker背后(确切的说是在MVC模式的背后)的重要思想就是表现逻辑和业务逻辑相分离。在模板中,只是处理显示相关的问题,也就是视觉设计问题和格式问题。所准备要显示的数据(如用户名等)与FreeMarker无关,这通常是使用Java语言或其他目的语言来编写的程序。

FreeMarker(还有模板开发人员)并不关心数据是如何计算出来的,FreeMarker只是知道真实的数据是什么。模板能用的所有数据被包装成data-model数据模型。数据模型的创建是通过已经存在的程序计算得到的。对于模板开发人员,数据模型像是树形结构(比如硬盘上的文件夹和文件),正如本例中的数据模型,就可以如下形式来描述:

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

要注意:数据模型并不是文本文件,上面所描述的只是一种数据模型的表现形式。

比较之前在模板中看到的${user}和${latestProduct.name}。作为一种比喻:数据模型就像计算机文件系统上的内容:根root和latestProduct对应目录(文件夹),user,url和name对应文件。url和name在latestProduct目录中,所以latestProduct.name就像是说latestProduct目录的name一样。

模板和数据模型是FreeMarker所需,并用来生成输出内容的:模板+数据模型=输出

2)、数据模型一览

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


上图中变量扮演目录的角色(根root,animals,mouse,elephant,Python,whatnot)被称为hash哈希表。哈希表通过可查找的名称(例如:”animal”, ”mouse”, ”price”)来访问存储的其他变量(如子变量)。
如果仅存储单值的变量(size,price,text和because)则它们被称为scalars标量
如果要在模板中使用子变量,那应该从根root开始指定它的路径,每级之间用点来分隔。要访问mouse的price的话,应该从根开始,先是animals,然后是mouse,最后是price,所以应该这样写:animals.mouse.price。当放置${…}这种特定代码在表达式的前后时,我们就告诉FreeMarker在那个位置上要来输出对应的文本。

sequences序列也是一种非常重要的变量,它们和哈希表变量相似,但是它们不存储所包含变量的名称,而是按顺序存储子变量。这样,就可以使用数字索引来访问这些子变量。在这种数据模型中,animal和whatnot.fruits就是序列:


可以使用数组的方括号方式来访问一个序列的子变量。索引从零开始(从零开始是程序员写代码的传统习惯),那么就意味着序列第一项的索引是0,第二项的索引是1,并以此类推。要得到第一个动物的名称的话,那么就应该这么写代码:animals[0].name。要得到whatnot.fruits(就是”banana”这个字符串)的第二项,那么就应该这么来写:whatnot.fruits[1]。

标量可以分为如下类别:
字符串:这是文本类型,字符的任意序列,比如”m”,“o”,“u”,“s”,“e”这些,而且name-S和size-S也是字符串范畴。(name-S和size-S是什么意思呢,是说name和size子变量吧???)

数字:这是数字值类型,比如price这些。在FreeMarker中字符串”50”和数字50是两种完全不同的类型。前者只是两个字符的序列(这恰好是我们可以读的一个数字),而后者是一个可以在算数运算中直接被使用的数值。
日期/时间:这是时间日期类型。例如动物被捕捉的日期,或商店开始营业的时间。
布尔值:对应对/错(是/否,开/关等)这样仅仅代表正反的值。比如动物可以有一个受保护(protected,译者注)的子变量,这个变量存储这个动物是否被保护起来。
总结:
数据模型可以被看做是树状结构的。
标量存储单一的值,这种类型的值可以是字符串,数字,日期/时间或者是布尔值。
哈希表是存储变量和与其相关且有唯一标识名称变量的容器。
序列是存储有序变量的容器。存储的变量可以通过数字索引来检索,索引通常从零开始。


3)、模板一览

最简单的模板是普通HTML文件(或者是其他任何文本文件—FreeMarker本身不属于HTML)。当客户端访问页面时,FreeMarker要发送HTML代码至客户端浏览器端显示。如果想要页面动起来,就要在HTML中放置能被FreeMarker所解析的特殊部分。
${…}:FreeMarker将会输出真实的值来替换花括号内的表达式,这样的表达式被称为interpolations插值,可以参考第上面示例的内容。(注意是表达式称为插值,也就是说不包括${}
FTL tags标签FreeMarker 模板的语言标签):FTL标签和HTML标签有一点相似,但是它们是FreeMarker的指令而且是不会直接输出出来的东西。这些标签的使用一般以符号#开头。(用户自定义的FTL标签使用@符号来代替#,但这是更高级的主题内容了,后面会详细地讨论)
Comments注释:FreeMarker的注释和HTML的注释相似,但是它用<#--和-->来分隔的(html使用<!-- -->)。任何介于这两个分隔符(包含分隔符本身)之间内容会被FreeMarker忽略,就不会输出出来了。
其他任何不是FTL标签,插值或注释的内容将被视为静态文本,这些东西就不会被FreeMarker所解析,会被按照原样输出出来。
directives指令就是所指的FTL标签。这些指令在HTML的标签(如<table>和</table>)和HTML元素(如table元素)中的关系是相同的。(如果现在你还不能区分它们,那么把“FTL标签”和“指令”看做是同义词即可。也就是<#后跟的是指令,整个<# ></# >叫标签)

3.1)指令

三个最为常见的指令:

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> 

Our beloved leader是if条件中那唯一的user变量值的判断结果,当它和”Big Joe”相同时才显示出来。那么,当condition的判断结果为false(布尔值)时,在<#if condition>和</#if>标签之间的内容将会被略过。condition的使用:==是来判断在它两侧的值相等的操作符,比较的结果是布尔值,true或者false。在==的左侧,是引用的变量,它会被变量的值来替代。右侧是指定的字符串,在模板中的字符串必须放在引号内。
当price是0的时候,下面的代码将会打印:”Pythons are free today!”

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

字符串被直接指定,但是这里则是数字(0)被直接指定。注意到数字是不用放在引号内的。如果将0放在引号内(”0”),FreeMarker就会将其误判为字符串了。
当price不是0的时候,下面的代码将会打印:”Pythons are not free today!”

<#if animals.python.price != 0>
Pythons are 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>

如果变量本身就是布尔值(true或者false),那么可以直接让其作为if的条件condition:

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

3.2)list指令

当需要用列表来遍历集合的内容时,list指令是非常好用的。例如,如果在模板中用前面示例描述序列的数据模型。

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

list指令的一般格式为:
<#list sequence as loopVariable>repeatThis</#list>
repeatThis部分将会在给定的sequence遍历时在每项中重复,从第一项开始,一个接着一个。在所有的重复中,loopVariable将持有当前项的值。这个循环变量仅存在于<#list …>和</#list>标签之间。

再如:遍历示例数据模型fruits。

<p>And BTW we have these fruits:
<ul>
<#list whatnot.fruits as fruit>
<li>${fruit}
</#list>
<ul>

注意:loopVariable是我们自己定义的,只要符合变量的定义。

3.3)include指令

使用include指令,我们可以在当前的模板中插入其他文件的内容。

假设要在一些页面中显示版权声明的信息。那么可以创建一个文件来单独包含版权声明,之后在需要它的地方插入即可。比方说,我们可以将版权信息单独存放在页面文件copyright_footer.html中。

<hr>
<i>
Copyright (c) 2014 <a href="http://www.xx.com">XX 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>

3.4)联合使用指令

在页面也可以多次使用指令,而且指令间可以相互嵌套,

<p>We have these animals:
<table border=1>
<tr><th>Name<th>Price
<#list animals as being>
<tr>
<td>
<#if being.size == "large"><font size="+1"></#if>
${being.name}
<#if being.size == "large"></font></#if>
<td>${being.price} Euros
</#list>
</table>

3.5)处理不存在的变量

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

一个不存在的变量和一个是null的变量,对于FreeMarker来说是一样的,所以这里所指的丢失包含这两种情况
不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况,通过在变量名后面跟着一个!和默认值。就像下面的例子,当user从数据模型中丢失时,模板将会将user的值表示为字符串”Anonymous”。(若user并没有丢失,那么模板就会表现出”Anonymous”不存在一样):

<h1>welcome  ${user!"Anonymous"}!</h1>

也可以在变量名后面通过放置??来询问FreeMarker一个变量是否存在。将它和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)??来看。

2、数值和类型

什么是数值?

变量user的数值是”Big Joe”(字符串),today的数值是Jul 6,2007(日期),todayHoilday的数值是false(布尔值,是/否,这样的值)。lotteryNumbers的数值是包含20,14,42,8,15的序列。在这种意义上,lotteryNumbers是多值的,它包含多个数值(如其中的第二项是14),但是lotteryNumbers本身还是单值。它像一个装有很多东西的盒子,整个盒子被看做是独立的。最后有一个数值cargo,它是一个哈希表(也可以看做是盒子)。所以数值就是存储在变量中的(在user,cargo或cargo.name中)东西。但是不需要存储的数值也可以称之为数值,比如这里的数字100:

<#if cargo.weight < 100>Light cargo</#if>

什么是类型?

数值中非常重要的一个概念就是类型。比方说,变量user的类型是字符串,lotteryNumbers的类型是序列。数值的类型非常重要,因为它决定了这些数值可以在哪里使用的最大限度。比如${user/2}就是错误的,但是${cargo.weight/2}就能计算出结果20,除法仅对数字值有效,而不能作用于字符串。仅当cargo是一个哈希表时cargo.name可以使用。也可以用<#list …>仅仅来遍历序列。<#if …>指令的条件condition只能是布尔值等。

数值同时也可以含有多种类型,尽管这样很少使用。看下面这个数据模型mouse,就又是字符串又是哈希表。

(root)
    |
    +- mouse = "Yerri"
            |
            +- age = 12
            |
            +- color = "brown"

如果用上面的数据模型合并到模板中,就该这么来写:

${mouse} <#-- 用 mouse 作为字符串 -->
${mouse.age} <#-- 用 mouse 作为哈希表 -->
${mouse.color} <#-- 用 mouse 作为哈希表 -->

输出的结果:

Yerri
12
brown

数据模型是哈希表

注意观察每个你已经知道的数据模型:被”(root)”标识的内容就是哈希表类型的数值。当书写如user这样的代码,那就意味着想要把”user”变量存储在哈希表的根上。而如果代码是:root.user,也没有名为”root”的变量,那么这就没有任何作用。

关于类型

支持的类型有:
1)标量:
       字符串
       数字
       布尔值
       日期
2) 容器:
       哈希表
       序列
       集
3)子程序:
       方法和函数
       用户自定义指令
4)其它/很少使用:
       节点

标量:

字符串:简单的文本,如果想在模板中直接给出字符串的值,而不是使用数据模型中的变量,那么将文本写在引号内即可,比如”green mouse”或者’green mouse’。
数字:整数和非整数是不区分的,只有单一的数字类型。比如计算3/2的结果是1.5而不是1。如要在模板中直接给出数字的值,可以这么来写:150,-90.05,或者0.001。
布尔值:布尔值代表了逻辑上的对或错(是或否)。典型的应用是使用布尔值作为if指令的条件,在模板中可以使用保留字true和false来指定布尔值。
日期:日期变量可以存储和日期/时间相关的数据。一共有三种变化。
     精确到天的日期(通常指的是“日期”),比如April 4, 2003
     每天的时间(不包括日期部分),比如10:19:18 PM。时间的存储精确到毫秒。
     日期-时间(也称作“时间戳”),比如April 4, 2003 10:19:18 PM。时间部分的存储精确到毫秒。

FreeMarker区别字符串,数字和布尔值,所以字符串”150”和数字150是完全不同的两种数值。数字持有的是数字的值,布尔值表达的是逻辑上的对或错。字符串可以是任意字符的序列。

容器:

这些值存在的目的是为了包含其他变量,它们仅仅作为容器。被包含的变量通常是子变量。容器的类型有:

哈希表:每个子变量都可以通过一个唯一的名称来查找,这个名称是不受限制的字符串。哈希表并不确定其中子变量的顺序,也就是说没有第一个变量,第二个变量这样的说法,变量仅仅是通过名称来访问的。(就像Java语言中的HashMap一样,是实现了Hash算法的Map,不记录内部元素的顺序,仅仅通过名称来访问。)
序列:每个子变量通过一个整数来标识。第一个子变量的标识符是0,第二个是1,这样来类推,而且子变量是有顺序的。这些数字通常被称为是子变量的索引。序列通常比较密集,也就是所有的索引,它们和子变量都是相关联的,子变量的数值类型也并不需要完全一致。
集: 从模板设计者角度来看,集是有限制的序列。不能获取集的大小,也不能通过索引取出集中的子变量,但是它们仍然可以通过list指令来遍历。

子程序:

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

方法/函数是第一类值,就像函数化的编程语言。也就是说函数/方法也可以是其他函数或方法的参数或者返回值,并可以把它们定义成变量。

假设程序员在数据模型中放置了一个方法变量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)}

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

用户自定义指令:

用户自定义指令(换句话说,就是FreeMarker的标签)这种类型的值也是一种子程序,一种可以复用的模板代码段。:用户自定义指令(比如宏),也是第一类值,就像函数/方法一样。

假设现在有一个变量,box,它的值是用户自定义的指令,用来打印一些特定的HTML信息,这个指令定义了一个标题和其中的信息。

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

box就是自定义指令

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

如果要使用函数/方法或自定义指令去实现一些东西的时候,二者之间的选择是两难的。按经验来说,如果能够实现,请先用自定义指令而不要用函数/方法。指令的特征如下:
    ■ 输出(返回值)的是标记(HTML,XML等)。主要原因是函数的返回结果可以自动进行XML转义(这是因为${…}的特性),而用户自定义指令的输出则不是(这是因为<@...>的特性所致,它的输出假定为是标记,因此就不再转义)。
    ■ 副作用也是很重要的一点,它没有返回值。例如一个指令的目的是往服务器日志中添加一个条目。(事实上你不能得到自定义指令的返回值,但有些反馈的类型是有可能设置非本地变量的)
    ■ 会进行流程的控制(就像list或if指令那样),但是不能在函数/方法上这么做。

节点

节点变量代表了树状结构中的一个节点,而且通常是配合XML格式来处理的,节点和存储在其他节点中的序列很相似,通常也被当作为子节点。节点存储它所在的容器节点的引用,也就是父节点。节点的主要作用是拓扑信息。其它数据必须通过使用多类型的值来存储。就像一个值可以同时是一个节点和一个数字,这样它存储的数字可以作为如支付额来使用。除了拓扑信息,节点也可以存储一些元信息(即metadata):如节点名称,它的类型(字符串),命名空间(作为字符串)。若一个节点象征XHTML文档中的h1元素,那么它的名字可以是”h1”,类型可以是”element”,命名空间可以是”http://www.w3.org/1999/xhtml”。

3、模板

总体结构

实际上你用程序语言编写的程序就是模板,模板也被称为FTL(代表FreeMarker模板语言 Freemarker Templet Language)。这是为编写模板设计的非常简单的编程语言。
模板(FTL编程)是由如下部分混合而成的:

Text文本:文本会照着原样来输出。
Interpolation插值:这部分的输出会被计算的值来替换。插值由${和}所分隔(或者#{和},这种风格已经不建议再使用了)。
FTL tags标签:FTL标签和HTML标签很相似,但是它们却是给FreeMarker的指示,而且不会打印在输出内容中(主要是<# ></#>或<@ ></@>)。
Comments注释:FTL的注释和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 being>[BR] 
        <li>${being.name} for ${being.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... --> being>[BR]
...

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

指令

使用FTL标签来调用directives指令,比如调用list指令。在语法上我们使用了两个标签:<#list animals as being>和</#list>。
标签分为两种:
 开始标签:<#directivename parametes>
 结束标签:</#directivename>
除了标签以#开头外,其他都和HTML,XML的语法很相似。如果标签没有嵌套内容(在开始标签和结束标签之内的内容),那么可以只使用开始标签。例如有嵌套内容<#if something>...</#if>,无嵌套内容,如FreeMarker知道<#include something>中include指令没有可嵌套的内容。
parameters的格式由directivename来决定。

事实上,指令有两种类型:预定义指令和用户自定义指令。对于用户自定义的指令使用@来代替#,比如<@mydirective parameters>...</@mydirective>。更深的区别在于如果指令没有嵌套内容,那么必须这么使用<@mydirective parameters />,这和XML语法很相似(例如<img ... />)

像HTML标签一样,FTL标签必须正确的嵌套使用。注意一下FreeMarker仅仅关心FTL标签的嵌套而不关心HTML标签的嵌套,它只会把HTML看做是相同的文本,不会来解释HTML。

FreeMarker会忽略FTL标签中的多余空白标记,当然,也不能在<,</和指令名中间插入空白标记

表达式

当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。例如,设x为8,y为5,那么(x+y)/2的值就会被处理成数字类型的值6.5
来看一些具体的例子:
     当给插值提供值时:插值的使用方式为${expression},把它放到你想输出文本的位置上然后给值就可以打印了。即${(5+8)/2}会打印”6.5”出来
     当给指令参数提供值时:在前面我们已经看到if指令的使用了。这个指令的语法是:<#if expression>...</#if>。这里的表达式计算结果必须是布尔类型的。比如<#if 2 < 3>中的2 < 3(2小于3)是结果为true的布尔表达式。

快速浏览:

■直接指定值
    ◆字符串:"Foo" 或者 'Foo' 或者 "It's \"quoted\"" 或者 r"C:\raw\string"
    ◆ 数字:123.45 <#list[BR] animals as[BR] being[BR] >[BR] ${being.name} for ${being.price} Euros[BR] </#list >
    ◆ 布尔值:true, false
    ◆ 序列:["foo", "bar", 123.45], 1..100
    ◆ 哈希表:{"name":"green mouse", "price":150}
■检索变量
    ◆ 顶层变量:user
    ◆  从哈希表中检索数据:user.name, user[“name”]
    ◆  从序列中检索:products[5]
    ◆  特殊变量:.main
■ 字符串操作
    ◆ 插值(或连接):"Hello ${user}!"(或"Free" + "Marker")
    ◆ 获取一个字符:name[0]
■  序列操作
    ◆连接:users + ["guest"]
    ◆ 序列切分:products[10..19] 或 products[5..]
■  哈希表操作
    ◆ 连接:passwords + {"joe":"secret42"}
    ◆ 算数运算: (x * 1.5 + 10) / 2 - y % 100
    ◆ 比较运算:x == y, x != y, x < y, x > y, x >= y, x <= y, x &lt; y, 等等
    ◆ 逻辑操作:!registered && (firstVisit || fromEurope)
    ◆ 内建函数:name?upper_case
    ◆ 方法调用:repeat("What", 3)
■  处理不存在的值
    ◆ 默认值:name!"unknown" 或者(user.name)!"unknown" 或者name! 或者 (user.name)!
    ◆ 检测不存在的值:name?? 或者(user.name)??

1)、字符串:在文本中确定字符串值的方法是看引号和单引号,比如"some text"或'some text',这两种形式是相等的。如果文本本身包含用于字符引用的引号(双引号”或单引号’)或反斜杠时,应该在它们的前面再加一个反斜杠,这就是转义。转义允许你直接在文本中输入任何字符,也包括反斜杠。

${"It's \"quoted\" and this is a backslash: \\"}
${'It\'s "quoted" and this is a backslash: \\'}

转义序列            含义
\"                         引号(u0022)
\’                         单引号(又称为撇号)(u0027)
\\                         反斜杠(u005C)
\n                       换行符(u000A)
\r                        回车(u000D)
\t                        水平制表符(又称为标签)(u0009)
\b                       退格(u0008)
\f                        换页(u000C)
\l                       小于号:<
\g                      大于号:>
\a                      和号:&
\xCode            字符的16进制Unicode码(UCS码)
在\x之后的Code是1-4位的16进制码。下面这个示例中都是在字符串中放置版权符号"\xA9 1999-2001", "\x0A9 1999-2001", "\x00A9 1999-2001":如果紧跟16进制码后一位的字符也能解释成16进制码时,就必须把4位补全,否则FreeMarker的解析就会出现问题。

一种特殊的字符串就是原生字符串。在原生字符串中,反斜杠和${没有特殊的含义,它们被视为普通的字符。为了表明字符串是原生字符串,在开始的引号或单引号之前放置字母r,例如:

${r"${foo}"}
${r"C:\foo\bar"}

2)、数字:输入不带引号的数字就可以直接指定一个数字,必须使用点作为小数的分隔符而不能是其他的分组分隔符。可以使用-或+来表明符号(+是多余的)。科学记数法暂不支持使用(1E3就是错误的),而且也不能在小数点之前不写0(.5也是错误的)。

3)、布尔值:直接写true或false就表征一个布尔值了,不需使用引号。

4)、序列:指定一个文字的序列,使用逗号来分隔其中的每个子变量,然后把整个列表放到方括号中。例如:

<#list ["winter", "spring", "summer", "autumn"] as x>
${x}
</#list>

列表中的项目是表达式,那么也可以这样做:[2 + 2, [1, 2, 3, 4], "whatnot"],其中第一个子变量是数字4,第二个子变量是一个序列,第三个子变量是字符串”whatnot”。
也可以用start..end定义存储数字范围的序列,这里的start和end是处理数字值表达式,比如2..5和[2, 3, 4, 5]是相同的,但是使用前者会更有效率(内存占用少而且速度快)。可以看出前者也没有使用方括号,这样也可以用来定义递减的数字范围,比如5..2。(此外,还可以省略end,只需5..即可,但这样序列默认包含5,6,7,8等递增量直到无穷大)

5)、哈希表:在模板中指定一个哈希表,就可以遍历用逗号分隔开的“键/值”对,把列表放到花括号内。键和值成对出现并以冒号分隔。看这个例子:{"name":"green mouse", "price":150}。注意到名字和值都是表达式,但是用来检索的名字就必须是字符串类型的。

6)、顶层变量:为了访问顶层的变量,可以简单地使用变量名。例如,${user},用表达式user就可以在根上获取以“user”为名存储的变量值。然后就可以打印出存储在里面的内容。如果没有顶层变量,那么FreeMarker在处理表达式时就会发生错误,进而终止模板的执行(除非程序员事先配置了FreeMarker)。
在这个表达式中变量名可以包含字母(也可以是非拉丁文),数字(也可以是非拉丁数字),下划线(_),美元符号($),at符号(@)和哈希表(#),此外要注意变量名命名时是不能以数字开头的

7)、从哈希表中检索数据:如果有一个表达式的结果是哈希表,那么我们可以使用点和子变量的名字得到它的值,假设我们有如下的数据模型:


现在,就可以通过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"]。

当使用点式语法时,顶层变量名的命名也有相同的限制(命名时只能使用字母,数字,下划线,$,@等),而使用方括号语法形式时命名有没有这样的限制,它可以是任意的表达式。(为了FreeMarker支持XML,如果变量名是*(星号)或者**,那么就应该使用方括号语法格式。)
对于顶层变量来说,如果尝试访问一个不存在的变量也会引起错误导致模板解析执行的中断(除非程序员事先配置过FreeMarker)。

8)、从序列中检索数据:这和从哈希表中检索是相同的,但是你只能使用方括号语法形式来进行,而且方括号内的表达式最终必须是一个数字而不是字符串。在第一个例子的数据模型示例中,为了获取第一个动物的名字(记住第一项数字索引是0而不是1)可以这么来写:animals[0].name。

9)、特殊变量:特殊变量是由FreeMarker引擎本身定义的,为了使用它们,可以按照如下语法形式来进行:.variable_name。

关于字符串操作

1)、插值(或连接):如果要在字符串中插入表达式的值,可以在字符串的文字中使用${…},${...}的作用和在文本区的是相同的。假设用户是”Big Joe”,看下面的代码:

${"Hello ${user}!"}
${"${user}${user}${user}${user}"}

将会打印如下内容:

Hello Big Joe!
Big JoeBig JoeBig JoeBig Joe

另外,也可以使用+号来达到类似的效果,这是比较老的方法,也叫做字符串连接。示例如下:

${"Hello " + user + "!"}
${user + user + user + user}

警告:
使用者在使用插值时经常犯的一个错误是:在不能使用插值的地方使用了它。插值只能在文本区段(<h1>Hello ${name}!</h1>)和字符串文字(<#include "/footer/${company}.html">)中使用。一个典型的错误使用是<#if ${isBig}>Wow!</#if>,这是语法上的错误。只能这么来写:<#if isBig>Wow!</#if>,<#if "${isBig}">Wow!</#if>来写也是错误的。因为if指令的参数需要的是布尔值,而这里是字符串,那么就会引起运行时的错误

2)、获取一个字符:在给定索引值时可以获取字符串中的一个字符,比如user[0]。这个操作执行的结果是一个长度为1的字符串,FTL并没有独立的字符类型。和序列中的子变量一样,这个索引也必须是数字,范围是从0到字符串的长度,否则模板的执行将会发生错误并终止。

由于序列的子变量语法和字符的getter语法冲突,那么只能在变量不是序列时使用字符的getter语法(因为FTL支持多类型值,所以它是可能的),这种情况下使用序列方式就比较多。(为了变通,可以使用内建函数string,比如user?string[0]。)。可以按照切分序列的方式来获取一定范围内的字符,比如${user[1..4]}和${user[4..]}。然而现在这种使用方法已经被废弃了,作为它的替代,可以使用内建函数substring,

序列操作

1)、连接:序列的连接可以使用+号来进行,例如:

<#list ["Joe", "Fred"] + ["Julia", "Kate"] as user>
- ${user}
</#list>

将打印出:

- Joe
- Fred
- Julia
- Kate

要注意不要在很多重复连接时使用序列连接操作,比如在循环中往序列上追加项目,而这样的使用是可以的:<#list users + admins as person>。尽管序列连接的很快,而且速度是和被连接序列的大小相独立的,但是最终的结果序列的读取却比原先的两个序列慢那么一点。通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。

2)、序列切分:使用[firstindex..lastindex]可以获取序列中的一部分,这里的firstindex和lastindex表达式的结果是数字。如果seq存储序列"a", "b", "c", "d", "e", "f",那么表达式seq[1..4]将会是含有"b", "c", "d", "e"的序列(索引为1的项是"b",索引为4的项是"e")。
lastindex可以被省略,那么这样将会读取到序列的末尾。如果seq存储序列"a", "b", "c", "d", "e", "f",那么seq[3..]将是含有"d", "e", "f"的序列。

哈希表操作

1)、连接:像连接字符串那样,也可以使用+号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在+号右侧的哈希表中的项目优先。例如:

<#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}>
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}

将打印:

- Joe is 30
- Fred is 25
- Julia is 18

注意很多项目连接时不要使用哈希表连接,比如在循环时往哈希表中添加新项。这和序列连接的情况是一致的。

算数运算:算数运算包含基本的四则运算和求模运算,运算符有:加法:+、 减法:-、 乘法:*、 除法:/、 求模(求余):%

要保证两个操作数都是结果为数字的表达式,但这种情况也有一个例外,就是+号,它是用来连接字符串的,如果+号的一端是字符串,另外一端是数字,那么数字就会自动转换为字符串类型(使用适当的格式)。有时我们只想获取计算结果的整数部分,这可以使用内建函数int来解决。

比较运算:有时我们需要知道两个值是否相等,或者哪个数的值更大一点。

测试两个值相等使用=(或者采用Java和C语言中的==,二者是完全等同的。)测试两个值不等使用!=

=或!=两边的表达式的结果都必须是标量,而且两个标量都必须是相同类型(也就是说字符串只能和字符串来比较,数字只能和数字来比较等)。否则将会出错,模板执行中断。对数字和日期类型的比较,也可以使用<,<=,>=和>。不能把它们当作字符串来比较。使用>=和>的时候有一点小问题。FreeMarker解释>的时候可以把它当作FTL标签的结束符。为了避免这种问题,不得不将表达式放到括号内:<#if (x > y)>,或者可以在比较关系处使用&gt;和&lt;:<#if x &gt; y>。(通常在FLT标签中不支持实体引用(比如&...;这些),否则就会抛出算数比较异常)。另外,可以使用lt代替<,lte代替<=,gt代替>,gte代替>=, 由于历史遗留的原因,FTL也支持\lt, \lte, \gt 和 \gte,使用他们和使用不带反斜杠的效果一样。

逻辑操作:常用的逻辑操作符: 逻辑或:|| 、 逻辑与:&& 、 逻辑非:!
逻辑操作符仅仅在布尔值之间有效,若用在其他类型将会产生错误导致模板执行中止。

内建函数:内建函数提供始终可用的内置功能。内建函数以?形式提供变量的不同形式或者其他信息。使用内建函数的语法和访问哈希表子变量的语法很像,除了使用?号来代替点,其他的都一样。例如得到字符串的大写形式:user?upper_case。

一些重要的内建函数:

■ 字符串使用的内建函数:
    ◆  html: 字符串中所有的特殊HTML字符都需要用实体引用来代替(比如<代替&lt;)。
    ◆  cap_first:字符串的第一个字母变为大写形式
    ◆  lower_case:字符串的小写形式
    ◆  upper_case:字符串的大写形式
    ◆  trim:去掉字符串首尾的空格
■ 序列使用的内建函数:
    ◆  size:序列中元素的个数
■ 数字使用的内建函数:
    ◆  int:数字的整数部分(比如-1.9?int就是-1)

例子:

${test?html}
${test?upper_case?html}

方法调用

可以使用方法调用操作来使用一个已经定义过的方法。方法调用的语法形式是使用逗号来分割在括号内的表达式而形成的参数列表,这些值就是参数。方法调用操作将这些值传递给方法,然后返回一个结果,这个结果就是整个方法调用表达式的值。
假设程序员定义了一个可供调用的方法repeat。第一个参数字符串类型,第二个参数是数字类型。方法的返回值是字符串类型,而方法要完成是将第一个参数重复显示,显示的次数是第二个参数的值。例如:${repeat("What", 3)}

处理不存在的值:这个操作是FreeMarker 2.3.7版本以后才有的(用来代替内建函数default,exists和if_exists)。

1)、默认值:使用形式概览:unsafe_expr!default_expr或unsafe_expr!或(unsafe_expr)!default_expr或(unsafe_expr)!
这个操作符允许你为可能不存在的变量指定一个默认值。

默认值可以是任何类型的表达式,也可以不必是字符串。你也可以这么写:hits!0或colors!["red", "green", "blue"]。默认值表达式的复杂程度没有严格限制,你还可以这么来写:cargo.weight!(item.weight * itemCount + 10)

如果在!后面有复合表达式,如1 + x,通常使用括号,像${x!(1 + y)}或${(x!1) + y)},这样就根据你的意图来确定优先级,!(作为默认值操作)的优先级非常低。这就意味着${x!1 + y}会被FreeMarker误解为${x!(1 + y)},而真实的意义是${(x!1) + y}。

如果默认值被省略了,那么结果将会是空串,空序列或空哈希表。(这是FreeMarker允许多类型值的体现)如果想让默认值为0或false,则注意不能省略它

因为语法的含糊<@something a=x! b=y />将会解释为<@something a=x!(b=y) />,那就是说b=y将会视为是比较运算,然后结果作为x的默认值。而不是想要的参数b是x的默认值,为了避免这种情况,如下编写代码:<@something a=(x!) b=y />。
在不是顶层变量时,默认值操作符可以有两种使用方式:product.color!"red",如果是这样的写法,那么在product中,当color不存在时(返回”red”)将会被处理,但是如果连produce都不存在时将不会处理。也就是说这样写时变量product必须存在,否则模板就会报错。

(product.color)!"red",这时,如果当不存在时也会被处理,那就是说如果product不存在或者product存在而color不存在,都能显示默认值”red”而不会报错。

检测不存在的值:使用形式概览:unsafe_expr??或(unsafe_expr)??    这个操作符告诉我们一个值是否存在。基于这种情况,结果是true或false。

访问非顶层变量的使用规则和默认值操作符也是一样的,即product.color??和(product.color)??

<#if mouse??>
Mouse found
<#else>
No mouse found
</#if>
Creating mouse...
<#assign mouse = "Jerry">
<#if mouse??>
Mouse found
<#else>
No mouse found
</#if>

括号:括号可以用来给表达式分组,别忘了方法调用时使用的括号和给表达式分组的括号含义是完全不同的

表达式中的空格:FTL忽略表达式中的多余空格

操作符的优先级

运算符组运算符最高优先级运算符[subvarName][subStringRange].?(methodParams) expr! expr??一元前缀运算符+expr -expr !expr乘除法,求模* / %加减法+ -关系运算符 < > <= >= (相当于: gt, lt, 等)相等,不等== (也可以是: =) !=逻辑与&&逻辑或||数字范围..

插值:插值的使用语法是:${expression},expression可以是所有种类的表达式(比如${100 + x})。
插值是用来给插入具体值然后转换为文本(字符串)。插值仅仅可以在两种位置使用:文本区(如<h1>Hello ${name}!</h1>)和字符串表达式(如<#include "/footer/${company}.html">)中。
警告:
一个常犯的错误是在不能使用插值的地方使用了它。典型的错误就是<#if ${isBig}>Wow!</#if>,这是语法上的错误。只要写为<#if isBig>Wow!</#if>就对了,而且<#if "${isBig}">Wow!</#if>也是错误的,因为这样参数就是字符串类型了,但是if指令的参数要求是布尔值,所以运行时就会发生错误。

插值表达式的结果必须是字符串,数字或日期类型的,因为只有数字和日期类型可以自动转换为字符串类型,其他类型的值(如布尔,序列)只能手动转换为字符串类型,否则就会发生错误导致模板执行中止。
字符串插入指南:不要忘了转义! 如果插值在文本区(也就是说,不再字符串表达式中),如果escapse指令起作用了,即将被插入的字符串会被自动转义。如果你要生成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的转义,模板就会像这样了:

...
<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为一个插值来重写默认数值格式。
可以使用内建函数string为单独的插值来修改设置。

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

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

日期/时间插入指南
如果表达式的值是时间日期类型,那么日期中的数字将会按照默认格式来转换成文本。通常程序员应该设置默认格式,而页面设计者无需处理这一点。(如果需要的话,可以参考date_format,time_format和datetime_format的setting指令的设置来完成响应操作)这里也可以使用内建函数string为单独的插值重写默认格式。
警告:
为了将日期显示成文本,FreeMarker必须知道日期中的哪一部分在使用,也就是说,如果仅仅日期部分(年,月,日)使用或仅仅时间部分(时,分,秒,毫秒)使用或两部分都用。不幸的是,由于Java平台技术的限制,自动探测一些变量是不现实的。这时可以找程序员对数据模型中可能出问题的变量进行处理。如果找出时间日期变量的哪部分在使用是不太可能的话,就必须帮助FreeMarker使用内建函数date,time和datetime来识别,否则就会出现错误停止执行。

布尔值插入指南

若要使用插值方式来打印布尔值会引起错误,中止模板的执行。例如:${a == 2}就会引起错误,它不会打印”true”或其他内容。
然而,我们可以使用内建函数string来将布尔值转换为字符串形式。比如打印变量”married”(假设它是布尔值),那么可以这么来写: ${married?string("yes", "no")}

精确的转换规则

1. 如果这个值是数字,那么它会按照指定的number_format设置规则来转换为字符串。所以这些转换通常是对用户进行的,而不是对计算机。
2. 如果这个值是日期,时间或时间日期类型的一种,那么它们会按照指定的time_format,date_format和datetime_format设置规则来转换为字符串,这要看日期信息中是只包含时间,日期还是全包括了。如果它不能被探测出来是哪种日期类型(日期或时间或日期时间)时,就会发生错误了。
3. 如果值本来就是字符串类型的,不需要转换。
4. 如果FreeMarker引擎在传统兼容模式下:
1. 如果值是布尔类型,那么就转换成”true”,false值将会转换为空串。
2. 如果表达式未被定义(null或者变量未定义),那么就转换为空串。
3. 否则就会发生错误中止模板执行。
5. 否则就会发生错误中止模板执行。


0 0
原创粉丝点击