类型
支持的类型有:
标量
标量是最基本,最简单的数值类型,它们可以是:
-
字符串:表示简单的文本,例如:产品的名称。
如果想在模板中直接给出字符串值,而不是使用数据模型中的变量, 那么将文本内容写在引号内即可,比如
"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"
。但对于数据模型设计者来说,
这些元信息,还有如何来使用它们又有什么意义呢。检索拓扑信息和元信息的方法将会在
后续章节 中来说明(这里可以先不用理解它们)。