blob: 91ece1699490ddabd202fc411673e368591b4408 [file] [log] [blame]
<?xml version="1.0" encoding="UTF-8"?>
<!--
Licensed to the Apache Software Foundation (ASF) under one
or more contributor license agreements. See the NOTICE file
distributed with this work for additional information
regarding copyright ownership. The ASF licenses this file
to you under the Apache License, Version 2.0 (the
"License"); you may not use this file except in compliance
with the License. You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing,
software distributed under the License is distributed on an
"AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
KIND, either express or implied. See the License for the
specific language governing permissions and limitations
under the License.
-->
<book conformance="docgen" version="5.0" xml:lang="en"
xmlns="http://docbook.org/ns/docbook"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:ns5="http://www.w3.org/1999/xhtml"
xmlns:ns4="http://www.w3.org/2000/svg"
xmlns:ns3="http://www.w3.org/1998/Math/MathML"
xmlns:ns="http://docbook.org/ns/docbook">
<info>
<title>FreeMarker 手册</title>
<titleabbrev>Manual</titleabbrev>
<productname>Freemarker 2.3.23</productname>
</info>
<preface role="index.html" xml:id="preface">
<title>什么是 FreeMarker?</title>
<para>FreeMarker 是一款 <emphasis>模板引擎</emphasis>: 即一种基于模板和要改变的数据,
并用来生成输出文本(HTML网页,电子邮件,配置文件,源代码等)的通用工具。
它不是面向最终用户的,而是一个Java类库,是一款程序员可以嵌入他们所开发产品的组件。</para>
<para>模板编写为FreeMarker Template Language (FTL)。它是简单的,专用的语言,
<emphasis>不是</emphasis> 像PHP那样成熟的编程语言。
那就意味着要准备数据在真实编程语言中来显示,比如数据库查询和业务运算,
之后模板显示已经准备好的数据。在模板中,你可以专注于如何展现数据,
而在模板之外可以专注于要展示什么数据。</para>
<mediaobject>
<imageobject>
<imagedata fileref="figures/overview.png"/>
</imageobject>
</mediaobject>
<para>这种方式通常被称为 <link
linkend="gloss.MVC">MVC (模型 视图 控制器) 模式</link>,对于动态网页来说,是一种特别流行的模式。
它帮助从开发人员(Java 程序员)中分离出网页设计师(HTML设计师)。设计师无需面对模板中的复杂逻辑,
在没有程序员来修改或重新编译代码时,也可以修改页面的样式。</para>
<para>而FreeMarker最初的设计,是被用来在MVC模式的Web开发框架中生成HTML页面的,它没有被绑定到
Servlet或HTML或任意Web相关的东西上。它也可以用于非Web应用环境中。</para>
<para>FreeMarker 是 <link
xlink:href="http://www.fsf.org/philosophy/free-sw.html">免费的</link>
基于Apache许可证2.0版本发布。</para>
<para>如果你发现 <emphasis>任何错误</emphasis> (包括
<emphasis>语法错误</emphasis>, <emphasis>错别字</emphasis>,
排版错误) 或者是在文档中找到 <emphasis>误导或混淆</emphasis> ,或有其他建议,请联系原作者!
Email: ddekany at users.sourceforge.net</para>
<para>文档翻译的任何问题(包括语法错误,错别字)或中文技术交流,可以联系译者:nanlei1987 at gmail.com,
或在FreeMarker的Github上Fork一份,修改之后提交Pull Request。我们共同研究,共同进步。</para>
<para>英文版文档的作者(也是FreeMarker项目的维护者)是匈牙利人,其母语非英语,那么在这种情况下,
翻译过程难免会有错误存在,译者结合自身多年对FreeMarker的实践力争精准,译文力求信达雅。
但因个人才疏学浅,水平有限,恳请广大读者批评指正。最好的方式就是使用Github了</para>
<para>手册的更新根据原作者更新,大家的反馈随时进行。但只在有阶段性成果时才会提交发布修正版本。</para>
<para>本翻译是免费的,您可以自由下载和传播,但不可用于任何商业行为。文档版权归译者本人所有,
原版归FreeMarker项目组所有,您可以引用其中的描述,但必须指明出处。如需用于商业行为,
您必须和原作者取得联系。</para>
</preface>
<part xml:id="dgui">
<title>模板开发指南</title>
<chapter xml:id="dgui_quickstart">
<title>入门</title>
<para>本章内容只是对 FreeMarker 进行简略的介绍,在后续章节中将会详细展开讲述。
不过没有关系,在阅读了本章节的内容后,就能够编写简单,但却很有用的FreeMarker模板程序了。</para>
<section xml:id="dgui_quickstart_basics">
<title>模板 + 数据模型 = 输出</title>
<para>假设在一个在线商店的应用系统中需要一个HTML页面,和下面这个页面类似:</para>
<programlisting role="output">&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome <emphasis>John Doe</emphasis>!&lt;/h1&gt;
&lt;p&gt;Our latest product:
&lt;a href="<emphasis>products/greenmouse.html</emphasis>"&gt;<emphasis>green mouse</emphasis>&lt;/a&gt;!
&lt;/body&gt;
&lt;/html&gt;</programlisting>
<para>这里的用户名(上面的"Big Joe"),应该是登录这个网页的访问者的名字,
并且最新产品的数据应该来自于数据库,这样它才能随时更新。那么不能直接在HTML页面中输入它们,
不能使用静态的HTML代码。此时,可以使用要求输出的 <emphasis role="term">模板</emphasis>
模板和静态HTML是相同的,只是它会包含一些 FreeMarker 将它们变成动态内容的指令:</para>
<programlisting role="template" xml:id="example.first">&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Welcome <emphasis>${user}</emphasis>!&lt;/h1&gt;
&lt;p&gt;Our latest product:
&lt;a href="<emphasis>${latestProduct.url}</emphasis>"&gt;<emphasis>${latestProduct.name}</emphasis>&lt;/a&gt;!
&lt;/body&gt;
&lt;/html&gt;</programlisting>
<para>模板文件存放在Web服务器上,就像通常存放静态HTML页面那样。当有人来访问这个页面,
FreeMarker将会介入执行,然后动态转换模板,用最新的数据内容替换模板中
<literal>${<replaceable>...</replaceable>}</literal> 的部分,
之后将结果发送到访问者的Web浏览器中。访问者的Web浏览器就会接收到例如第一个HTML示例那样的内容
(也就是没有FreeMarker指令的HTML代码),访问者也不会察觉到服务器端使用的FreeMarker。
(当然,存储在Web服务器端的模板文件是不会被修改的;替换也仅仅出现在Web服务器的响应中。)</para>
<para>请注意,模板并没有包含程序逻辑来查找当前的访问者是谁,或者去查询数据库获取最新的产品。
显示的数据是在 FreeMarker 之外准备的,通常是一些 <quote>真正的</quote> 编程语言(比如Java)
所编写的代码。模板作者无需知道这些值是如何计算出的。事实上,这些值的计算方式可以完全被修改,
而模板可以保持不变,而且页面的样式也可以完全被修改而无需改动模板。
当模板作者(设计师)和程序员不是同一人时,显示逻辑和业务逻辑相分离的做法是非常有用的,
即便模板作者和程序员是一个人,这么来做也会帮助管理应用程序的复杂性。
保证模板专注于显示问题(视觉设计,布局和格式化)是高效使用模板引擎的关键。</para>
<para><indexterm>
<primary>data-model</primary>
</indexterm>为模板准备的数据整体被称作为 <emphasis role="term">数据模型</emphasis>
模板作者要关心的是,数据模型是树形结构(就像硬盘上的文件夹和文件),在视觉效果上,
数据模型可以是:</para>
<programlisting role="dataModel">(root)
|
+- <emphasis>user</emphasis> = "Big Joe"
|
+- <emphasis>latestProduct</emphasis>
|
+- <emphasis>url</emphasis> = "products/greenmouse.html"
|
+- <emphasis>name</emphasis> = "green mouse"</programlisting>
<note>
<para>上面只是一个形象化显示;数据模型不是文本格式,它来自于Java对象。
对于Java程序员来说,root就像一个有 <literal>getUser()</literal>
<literal>getLatestProduct()</literal> 方法的Java对象,
也可以有 <literal>"user"</literal><literal>"latestProducts"</literal>
键值的Java <literal>Map</literal>对象。相似地,<literal>latestProduct</literal>
就像是有 <literal>getUrl()</literal><literal>getName()</literal> 方法的Java对象。</para>
</note>
<para>早期版本中,可以从数据模型中选取这些值,使用 <literal>user</literal>
<literal>latestProduct.name</literal> 表达式即可。如果我们继续类推,
数据模型就像一个文件系统,那么 <quote>(root)</quote><literal>latestProduct</literal>
就对应着目录(文件夹),而 <literal>user</literal>, <literal>url</literal>
<literal>name</literal> 就是这些目录中的文件。</para>
<para>总的来说,模板和数据模型是FreeMarker来生成输出(比如第一个展示的HTML)所必须的:</para>
<para><phrase role="markedTemplate">模板</phrase> + <phrase
role="markedDataModel">数据模型</phrase> = <phrase
role="markedOutput">输出</phrase></para>
</section>
<section xml:id="dgui_quickstart_datamodel">
<title>数据模型一览</title>
<para>正如已经看到的,数据模型的基本结构是树状的。
这棵树可以很复杂,并且可以有很大的深度,比如:</para>
<programlisting role="dataModel"
xml:id="example.qStart.dataModelWithHashes">(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"</programlisting>
<para>上图中的变量扮演目录的角色(比如 root,
<literal>animals</literal>, <literal>mouse</literal>,
<literal>elephant</literal>, <literal>python</literal>,
<literal>misc</literal>) 被称为 <emphasis
role="term">hashes</emphasis> (哈希表或哈希,译者注)。哈希表存储其他变量(被称为
<anchor xml:id="topic.dataModel.subVar"/><emphasis>子变量</emphasis>),
它们可以通过名称来查找(比如 <quote>animals</quote>,
<quote>mouse</quote><quote>price</quote>)。</para>
<para>存储单值的变量
(<literal>size</literal>, <literal>price</literal>,
<literal>message</literal><literal>foo</literal>) 称为
<emphasis role="term">scalars</emphasis> (标量,译者注)。</para>
<para><anchor xml:id="topic.qStart.accessVariables"/>如果要在模板中使用子变量,
那应该从根root开始指定它的路径,每级之间用点来分隔开。要访问 <literal>mouse</literal>
<literal>price</literal> ,要从root开始,首先进入到 <literal>animals</literal> ,之后访问
<literal>mouse</literal> ,最后访问 <literal>price</literal> 。就可以这样来写
<literal>animals.mouse.price</literal></para>
<para>另外一种很重要的变量是 <emphasis role="term">sequences</emphasis> (序列,译者注)。
它们像哈希表那样存储子变量,但是子变量没有名字,它们只是列表中的项。
比如,在下面这个数据模型中, <literal>animals</literal>
<literal>misc.fruits</literal> 就是序列:</para>
<programlisting role="dataModel"
xml:id="example.qStart.dataModelWithSequences">(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"</programlisting>
<para>要访问序列的子变量,可以使用方括号形式的数字索引下标。
索引下标从0开始(从0开始也是程序员的传统),那么第一项的索引就是0,
第二项的索引就是1等等。要得到第一个动物的名称的话,可以这么来写代码
<literal>animals[0].name</literal>。要得到 <literal>misc.fruits</literal>
中的第二项(字符串<literal>"banana"</literal>)可以这么来写
<literal>misc.fruits[1]</literal>。(实践中,通常按顺序遍历序列,而不用关心索引,
这点会在 <link linkend="topic.tutorial.list">后续介绍</link>。)</para>
<para>标量类型可以分为如下的类别:</para>
<itemizedlist>
<listitem>
<para>字符串:就是文本,也就是任意的字符序列,比如上面提到的
''m'', ''o'', ''u'', ''s'', ''e''。比如 <literal>name</literal>
<literal>size</literal> 也是字符串。</para>
</listitem>
<listitem>
<para>数字:这是数值类型,就像上面的 <literal>price</literal>
在FreeMarker中,字符串 <literal>"50"</literal> 和数字
<literal>50</literal> 是两种完全不同的东西。前者是两个字符的序列
(这恰好是人们可以读的一个数字),而后者则是可以在数学运算中直接被使用的数值。</para>
</listitem>
<listitem>
<para>日期/时间: 可以是日期-时间格式(存储某一天的日期和时间),
或者是日期(只有日期,没有时间),或者是时间(只有时间,没有日期)。</para>
</listitem>
<listitem>
<para>布尔值:对应着对/错(是/否,开/关等值)类似的值。
比如动物可以有一个 <literal>protected</literal> (受保护的,译者注) 的子变量,
该变量存储这个动物是否被保护起来的值。</para>
</listitem>
</itemizedlist>
<para>总结:</para>
<itemizedlist>
<listitem>
<para>数据模型可以被看成是树形结构。</para>
</listitem>
<listitem>
<para>标量用于存储单一的值。这种类型的值可以是字符串,数字,日期/时间或者是布尔值。</para>
</listitem>
<listitem>
<para>哈希表是一种存储变量及其相关且有唯一标识名称的容器。</para>
</listitem>
<listitem>
<para>序列是存储有序变量的容器。存储的变量可以通过数字索引来检索,索引通常从0开始。</para>
</listitem>
</itemizedlist>
<note>
<para>还有一些其它更为高级的类型,在这里我们并没有涉及到,比如方法和指令。</para>
</note>
</section>
<section xml:id="dgui_quickstart_template">
<title>模板一览</title>
<para>最简单的模板通常是普通的HTML文件(或者是其他任何文本文件;
FreeMarker本身不属于HTML)。当客户端访问某个页面时,
FreeMarker要发送HTML代码至客户端浏览器中去显示。如果想要页面动起来
(这里指动态网页技术,译者注),那么就要在HTML中放置能被FreeMarker所解析的特殊代码片段:</para>
<itemizedlist>
<listitem>
<para><literal>${<replaceable>...</replaceable>}</literal>
FreeMarker将会输出真实的值来替换大括号内的表达式,这样的表达式被称为 <emphasis
role="term">interpolation</emphasis>(插值,译者注)。</para>
</listitem>
<listitem>
<para><emphasis role="term">FTL 标签</emphasis> (FreeMarker模板的语言标签):
FTL标签和HTML标签有一些相似之处,但是它们是FreeMarker的指令,是不会在输出中打印的。
这些标签的名字以 <literal>#</literal> 开头。(用户自定义的FTL标签则需要使用
<literal>@</literal> 来代替 <literal>#</literal>,但这属于更高级的话题了。)</para>
</listitem>
<listitem>
<para><emphasis role="term">注释:</emphasis> 注释和HTML的注释也很相似,
但是它们使用 <literal>&lt;#--</literal> and <literal>--&gt;</literal> 来标识。
不像HTML注释那样,FTL注释不会出现在输出中(不出现在访问者的页面中),
因为 FreeMarker会跳过它们。</para>
</listitem>
</itemizedlist>
<para>其他任何不是FTL标签,插值或注释的内容将被视为静态文本,
这些东西不会被FreeMarker所解析;会被按照原样输出出来。</para>
<para>FTL标签也被称为 <emphasis role="term">指令</emphasis>
这些指令在HTML的标签 (比如: <literal>&lt;table&gt;</literal>
<literal>&lt;/table&gt;</literal>) 和HTML元素 (比如:
<literal>table</literal> 元素) 中的关系是相同的。(如果现在还没有感觉到它们的不同,
那么把“FTL标签”和“指令”看做是同义词即可。)</para>
<note>
<para>可以在 <link
xlink:href="http://freemarker-online.kenshoo.com/">http://freemarker-online.kenshoo.com/</link>
上很方便的尝试编写模板</para>
</note>
<section>
<title>基本指令</title>
<para>这里我们仅仅来看一些非常常用的指令,当然
(<link linkend="ref_directives">指令还有很多</link>)。</para>
<section>
<title>if 指令</title>
<para>使用 <literal>if</literal> 指令可以有条件地跳过模板的一些片段。
比如,假设在 <link linkend="example.first">最初的示例</link> 中,
想向你的老板Big Joe特别地问好,可其他人不同:</para>
<programlisting role="template">&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Welcome!&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;
Welcome ${user}<emphasis>&lt;#if user == "Big Joe"&gt;</emphasis>, our beloved leader<emphasis>&lt;/#if&gt;</emphasis>!
&lt;/h1&gt;
&lt;p&gt;Our latest product:
&lt;a href="${latestProduct.url}"&gt;${latestProduct.name}&lt;/a&gt;!
&lt;/body&gt;
&lt;/html&gt;</programlisting>
<para>此时,告诉 FreeMarker,当和 <literal>"Big Joe"</literal> 相同时
<quote>, our beloved leader</quote> (我们最尊敬的领导,译者注)
才是if条件中那唯一的 <literal>user</literal> 变量的值。
通常来讲,如果 <literal><replaceable>condition</replaceable></literal>
是false(布尔值),那么介于 <literal>&lt;#if
<replaceable>condition</replaceable>&gt;</literal>
<literal>&lt;/#if&gt;</literal> 标签中的内容会被略过。</para>
<para>我们来详细说说
<literal><replaceable>condition</replaceable></literal> 的使用:
<literal>==</literal> 是用来判断它两侧的值是否相等的操作符,
比较的结果是布尔值,也就是true或者false。在 <literal>==</literal>
的左侧,是 <link linkend="topic.qStart.accessVariables">被引用的变量</link>
我们很熟悉这样的语法结构;最终它会被变量的值所替代。通常来说,
在指令或插值中没有被引号标注的内容都被视为变量的引用。右侧则是指定的字符串,
在模板中的字符串 <emphasis>只能</emphasis> 放在引号内。</para>
<para>当价格为0时,就会打印出 <quote>Pythons are free today!</quote></para>
<programlisting role="template">&lt;#if animals.python.price == <emphasis>0</emphasis>&gt;
Pythons are free today!
&lt;/#if&gt;</programlisting>
<para>和之前示例中,字符串被直接指定相似,
但这里则是数字(<literal>0</literal>)被直接指定了。
请注意,这里的数字 <emphasis>没有</emphasis> 放在引号内。
如果将(<literal>"0"</literal>)放在引号中,
那么FreeMarker就会将其误判为字符串了(也就是字符串0,译者注)。</para>
<para>当价格不为0时,则会打印出"Pythons are not free today!":</para>
<programlisting role="template">&lt;#if animals.python.price <emphasis>!=</emphasis> 0&gt;
Pythons are not free today!
&lt;/#if&gt;</programlisting>
<para>你也许就会猜测了, <literal>!=</literal> 就是<quote>不等于</quote></para>
<para>同时,也可以这样编来写代码(使用 <link
linkend="example.qStart.dataModelWithHashes">数据模型来描述哈希表</link>):</para>
<programlisting role="template">&lt;#if <emphasis>animals.python.price &lt; animals.elephant.price</emphasis>&gt;
Pythons are cheaper than elephants today.
&lt;/#if&gt;</programlisting>
<para>使用 <literal>&lt;#else&gt;</literal>
标签可以指定当条件为false时程序所要执行的内容。比如:</para>
<programlisting role="template">&lt;#if animals.python.price &lt; animals.elephant.price&gt;
Pythons are cheaper than elephants today.
<emphasis>&lt;#else&gt;</emphasis>
Pythons are not cheaper than elephants today.
&lt;/#if&gt;</programlisting>
<para>这个示例中,如果蟒蛇的价格比大象的价格低的话,
程序将会打印出 <quote>Pythons are cheaper than elephants today.</quote>
否则会打印 <quote>Pythons are not cheaper than elephants today.</quote>
后面也可以使用 <literal>elseif</literal> 来完善它:</para>
<programlisting role="template">&lt;#if animals.python.price &lt; animals.elephant.price&gt;
Pythons are cheaper than elephants today.
<emphasis>&lt;#elseif animals.elephant.price &lt; animals.python.price&gt;</emphasis>
Elephants are cheaper than pythons today.
&lt;#else&gt;
Elephants and pythons cost the same today.
&lt;/#if&gt;</programlisting>
<para>如果变量本身就是布尔值(true/false),则可以直接让其作为
<literal>if</literal><literal><replaceable>condition</replaceable></literal>
(判断条件,译者注):</para>
<programlisting role="template">&lt;#if animals.python.protected&gt;
Pythons are protected animals!
&lt;/#if&gt;</programlisting>
</section>
<section>
<title>list 指令</title>
<anchor xml:id="topic.tutorial.list"/>
<para>当需要列表显示内容时,list指令是必须的。比如:
如果合并该模板到 <link
linkend="example.qStart.dataModelWithSequences">前面描述序列的数据模型</link>
中:</para>
<programlisting role="template">&lt;p&gt;We have these animals:
&lt;table border=1&gt;
<emphasis>&lt;#list animals as animal&gt;</emphasis>
&lt;tr&gt;&lt;td&gt;${<emphasis>animal</emphasis>.name}&lt;td&gt;${<emphasis>animal</emphasis>.price} Euros
<emphasis>&lt;/#list&gt;</emphasis>
&lt;/table&gt;</programlisting>
<para>那么输出结果将会是这样的:</para>
<programlisting role="output">&lt;p&gt;We have these animals:
&lt;table border=1&gt;
<emphasis>&lt;tr&gt;&lt;td&gt;mouse&lt;td&gt;50 Euros
&lt;tr&gt;&lt;td&gt;elephant&lt;td&gt;5000 Euros
&lt;tr&gt;&lt;td&gt;python&lt;td&gt;4999 Euros</emphasis>
&lt;/table&gt;</programlisting>
<para> <literal>list</literal> 指令的一般格式为:
<literal> &lt;#list <replaceable>sequence</replaceable> as
<replaceable>loopVariable</replaceable>&gt;<replaceable>repeatThis</replaceable>&lt;/#list&gt;</literal>
<literal><replaceable>repeatThis</replaceable></literal> 部分将会在给定的
<literal><replaceable>sequence</replaceable></literal> 遍历时在每一项中重复,
从第一项开始,一个接着一个。在所有的重复中,
<literal><replaceable>loopVariable</replaceable></literal> 将持有当前遍历项的值。
这个变量仅存在于 <literal>&lt;#list
<replaceable>...</replaceable>&gt;</literal>
<literal>&lt;/#list&gt;</literal> 标签内。</para>
<para><literal><replaceable>sequence</replaceable></literal> 可以是任意表达式,
比如我们可以列表显示示例数据模型中的水果,就像这样:</para>
<programlisting role="template">&lt;ul&gt;
<emphasis>&lt;#list misc.fruits as fruit&gt;</emphasis>
&lt;li&gt;${fruit}
<emphasis>&lt;/#list&gt;</emphasis>
&lt;/ul&gt;</programlisting>
<para>你应该很熟悉表达式 <literal>misc.fruits</literal> 了; 它 <link
linkend="topic.qStart.accessVariables">引用了数据模型中的变量</link></para>
<para>上面示例中的一个问题是如果我们有0个水果,它仍然会输出一个空的
<literal>&lt;ul&gt;&lt;/ul&gt;</literal>,而不是什么都没有。
要避免这样的情况,可以这么来使用 <literal>list</literal></para>
<programlisting role="template">&lt;#list misc.fruits&gt;
&lt;ul&gt;
<emphasis> &lt;#items as fruit&gt;</emphasis>
&lt;li&gt;${fruit}
<emphasis> &lt;/#items&gt;</emphasis>
&lt;/ul&gt;
&lt;/#list&gt;</programlisting>
<para>此时, <literal>list</literal> 指令将列表视为一个整体,
<literal>items</literal> 指令中的部分才会为每个水果重复。
如果我们有0个水果,那么在 <literal>list</literal> 中的所有东西都被略过了,
因此就不会有 <literal>ul</literal> 标签了。</para>
<para>另一个列表相关的常见任务是:使用一些分隔符来列出水果,比如逗号:</para>
<programlisting role="template">&lt;p&gt;Fruits: &lt;#list misc.fruits as fruit&gt;${fruit}<emphasis>&lt;#sep&gt;, </emphasis>&lt;/#list&gt;</programlisting>
<programlisting role="output">&lt;p&gt;Fruits: orange, banana</programlisting>
<para><literal>sep</literal> 覆盖的部分(我们也可以这么来写:
<literal><replaceable>...</replaceable>&lt;#sep&gt;,
&lt;/#sep&gt;&lt;/#list&gt;</literal>) 只有当还有下一项时才会被执行。
因此最后一个水果后面不会有逗号。</para>
<para>再次回到这个话题,如果我们有0个水果,会怎么样?只是打印
<quote>Fruits:</quote> 也没有什么不方便。
<literal>list</literal> 指令,也像 <literal>if</literal> 指令那样,可以有
<literal>else</literal> 部分,如果列表中有0个元素时就会被执行:</para>
<programlisting role="template">&lt;p&gt;Fruits: &lt;#list misc.fruits as fruit&gt;${fruit}&lt;#sep&gt;, <emphasis>&lt;#else&gt;None</emphasis>&lt;/#list&gt;</programlisting>
<note>
<para>事实上,这个过于简单的示例可以这么来写,
但是它使用了本主题中没有介绍的语言特性:</para>
<programlisting role="template">&lt;p&gt;Fruits: ${fruits?join(", ", "None")}</programlisting>
</note>
<para>所有的这些指令(<literal>list</literal>,
<literal>items</literal>, <literal>sep</literal>,
<literal>else</literal>)可以联合起来使用:</para>
<programlisting role="template">&lt;#list misc.fruits&gt;
&lt;p&gt;Fruits:
&lt;ul&gt;
&lt;#items as fruit&gt;
&lt;li&gt;${fruit}&lt;#sep&gt; and&lt;/#sep&gt;
&lt;/#items&gt;
&lt;/ul&gt;
&lt;#else&gt;
&lt;p&gt;We have no fruits.
&lt;/#list&gt;</programlisting>
<note>
<para><link linkend="ref_directive_list">指令参考</link> 中,
可以获取到更多关于这些指令的内容。</para>
</note>
</section>
<section>
<title>include 指令</title>
<para>使用 <literal>include</literal> 指令,
我们可以在模板中插入其他文件的内容。</para>
<para>假设要在一些页面中显示版权声明的信息。那么可以创建一个文件来单独包含这些版权声明,
之后在需要它的地方插入即可。比方说,我们可以将版权信息单独存放在页面文件
<literal>copyright_footer.html</literal> 中:</para>
<programlisting role="template">&lt;hr&gt;
&lt;i&gt;
Copyright (c) 2000 &lt;a href="http://www.acmee.com"&gt;Acmee Inc&lt;/a&gt;,
&lt;br&gt;
All Rights Reserved.
&lt;/i&gt;</programlisting>
<para>当需要用到这个文件时,可以使用 <literal>include</literal> 指令来插入:</para>
<programlisting role="template">&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Test page&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Test page&lt;/h1&gt;
&lt;p&gt;Blah blah...
<emphasis> &lt;#include "/copyright_footer.html"&gt;</emphasis>
&lt;/body&gt;
&lt;/html&gt;</programlisting>
<para>此时,输出的内容为:</para>
<programlisting role="output">&lt;html&gt;
&lt;head&gt;
&lt;title&gt;Test page&lt;/title&gt;
&lt;/head&gt;
&lt;body&gt;
&lt;h1&gt;Test page&lt;/h1&gt;
&lt;p&gt;Blah blah...
<emphasis>&lt;hr&gt;
&lt;i&gt;
Copyright (c) 2000 &lt;a href="http://www.acmee.com"&gt;Acmee Inc&lt;/a&gt;,
&lt;br&gt;
All Rights Reserved.
&lt;/i&gt;</emphasis>
&lt;/body&gt;
&lt;/html&gt;</programlisting>
<para>当修改了 <literal>copyright_footer.html</literal> 文件,
那么访问者在所有页面都会看到版权声明的新内容。</para>
<note>
<para>重用代码片段的一个更有力的方式是使用宏,但是只是更为高级的话题了,
将会在 <link linkend="dgui_misc_userdefdir">后续讨论</link></para>
</note>
</section>
</section>
<section>
<title>联合使用指令</title>
<para>在页面上也可以多次使用指令,而且指令间也可以很容易地相互嵌套。
比如,在 <literal>list</literal> 指令中嵌套 <literal>if</literal> 指令:</para>
<programlisting role="template"><emphasis>&lt;#list animals as animal&gt;</emphasis>
&lt;div<emphasis>&lt;#if animal.protected&gt;</emphasis><emphasis> </emphasis>class="protected"<emphasis>&lt;/#if&gt;</emphasis>&gt;
${animal.name} for ${animal.price} Euros
&lt;/div&gt;
<emphasis>&lt;/#list&gt;</emphasis></programlisting>
<para>请注意,FreeMarker并不解析FTL标签以外的文本、插值和注释,
上面示例在HTML属性中使用FTL标签也不会有问题。</para>
</section>
<section>
<title>使用内建函数</title>
<para>内建函数很像子变量(如果了解Java术语的话,也可以说像方法),
它们并不是数据模型中的东西,是 FreeMarker 在数值上添加的。
为了清晰子变量是哪部分,使用 <literal>?</literal>(问号)代替
<literal>.</literal>(点)来访问它们。<anchor
xml:id="topic.commonlyUsedBuiltIns"/>常用内建函数的示例:</para>
<itemizedlist>
<listitem>
<para><literal>user?html</literal> 给出 <literal>user</literal> 的HTML转义版本,
比如 <literal>&amp;</literal> 会由 <literal>&amp;amp;</literal> 来代替。</para>
</listitem>
<listitem>
<para><literal>user?upper_case</literal> 给出 <literal>user</literal> 值的大写版本
(比如 <quote>JOHN DOE</quote> 来替代 <quote>John
Doe</quote>)</para>
</listitem>
<listitem>
<para><literal>animal.name?cap_first</literal> 给出 <literal>animal.name</literal>
的首字母大写版本(比如 <quote>Mouse</quote> 来替代 <quote>mouse</quote>)</para>
</listitem>
<listitem>
<para><literal>user?length</literal> 给出 <literal>user</literal> 值中
<emphasis>字符</emphasis>的数量(对于 <quote>John Doe</quote> 来说就是8)</para>
</listitem>
<listitem>
<para><literal>animals?size</literal> 给出 <literal>animals</literal> 序列中
<emphasis>项目</emphasis> 的个数(我们示例数据模型中是3个)</para>
</listitem>
<listitem>
<para>如果在 <literal>&lt;#list animals as animal&gt;</literal> 和对应的
<literal>&lt;/#list&gt;</literal> 标签中:</para>
<itemizedlist>
<listitem>
<para><literal>animal?index</literal> 给出了在 <literal>animals</literal>
中基于0开始的 <literal>animal</literal>的索引值</para>
</listitem>
<listitem>
<para><literal>animal?counter</literal> 也像 <literal>index</literal>
但是给出的是基于1的索引值</para>
</listitem>
<listitem>
<para><literal>animal?item_parity</literal> 基于当前计数的奇偶性,给出字符串
<quote>odd</quote><quote>even</quote>。在给不同行着色时非常有用,比如在
<literal>&lt;td class="${animal?item_parity}Row"&gt;</literal>中。</para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
<para>一些内建函数需要参数来指定行为,比如:</para>
<itemizedlist>
<listitem>
<para><literal>animal.protected?string("Y", "N")</literal> 基于
<literal>animal.protected</literal> 的布尔值来返回字符串
<quote>Y</quote><quote>N</quote></para>
</listitem>
<listitem>
<para><literal>animal?item_cycle('lightRow','darkRow')</literal> 是之前介绍的
<literal>item_parity</literal> 更为常用的变体形式。</para>
</listitem>
<listitem>
<para><literal>fruits?join(", ")</literal> 通过连接所有项,将列表转换为字符串,
在每个项之间插入参数分隔符(比如 <quote>orange,banana</quote>)</para>
</listitem>
<listitem>
<para><literal>user?starts_with("J")</literal> 根据 <literal>user</literal>
的首字母是否是 <quote>J</quote> 返回布尔值true或false。</para>
</listitem>
</itemizedlist>
<para>内建函数应用可以链式操作,比如<literal>user?upper_case?html</literal>
会先转换用户名到大写形式,之后再进行HTML转义。(这就像可以链式使用
<literal>.</literal>(点)一样)</para>
<para>可以阅读 <link linkend="ref_builtins">全部内建函数参考</link></para>
</section>
<section>
<title>处理不存在的变量</title>
<para>数据模型中经常会有可选的变量(也就是说有时并不存在)。
除了一些典型的人为原因导致失误外,FreeMarker 绝不能容忍引用不存在的变量,
除非明确地告诉它当变量不存在时如何处理。这里来介绍两种典型的处理方法。</para>
<para><phrase role="forProgrammers">这部分对程序员而言:
一个不存在的变量和一个是 <literal>null</literal> 值的变量,
对于FreeMarker来说是一样的,所以这里所指的"丢失"包含这两种情况。</phrase></para>
<para>不论在哪里引用变量,都可以指定一个默认值来避免变量丢失这种情况,
通过在变量名后面跟着一个 <literal>!</literal>(叹号,译者注)和默认值。
就像下面的这个例子,当 <literal>user</literal> 不存在于数据模型时,
模板将会将 <literal>user</literal> 的值表示为字符串
<literal>"visitor"</literal>。(当 <literal>user</literal> 存在时,
模板就会表现出 <literal>${user}</literal> 的值):</para>
<programlisting role="template">&lt;h1&gt;Welcome ${user<emphasis>!"visitor"</emphasis>}!&lt;/h1&gt;</programlisting>
<para>也可以在变量名后面通过放置 <literal>??</literal>
来询问一个变量是否存在。将它和 <literal>if</literal> 指令合并,
那么如果 <literal>user</literal> 变量不存在的话将会忽略整个问候的代码段:</para>
<programlisting role="template">&lt;#if <emphasis>user??</emphasis>&gt;&lt;h1&gt;Welcome ${user}!&lt;/h1&gt;&lt;/#if&gt;</programlisting>
<para>关于多级访问的变量,比如 <literal>animals.python.price</literal>
书写代码:<literal>animals.python.price!0</literal>
当且仅当 <literal>animals.python</literal> 永远存在,
而仅仅最后一个子变量 <literal>price</literal> 可能不存在时是正确的
(这种情况下我们假设价格是 <literal>0</literal>)。
如果 <literal>animals</literal><literal>python</literal> 不存在,
那么模板处理过程将会以"未定义的变量"错误而停止。为了防止这种情况的发生,
可以如下这样来编写代码 <literal>(animals.python.price)!0</literal>
这种情况就是说 <literal>animals</literal><literal>python</literal> 不存在时,
表达式的结果是 <literal>0</literal>。对于 <literal>??</literal>
也是同样用来的处理这种逻辑的; 将 <literal>animals.python.price??</literal> 对比
<literal>(animals.python.price)??</literal>来看。</para>
</section>
</section>
</chapter>
<chapter xml:id="dgui_datamodel">
<title>数值,类型</title>
<section xml:id="dgui_datamodel_basics">
<title>基本内容</title>
<note>
<para>这里假设你已经阅读完 <xref linkend="dgui_quickstart"/> 章节的内容了。</para>
</note>
<para>理解数值和类型的概念是理解数据模型的关键和基础。
但数值和类型的概念并不局限于数据模型,下面你就会看到了。</para>
<section xml:id="topic.value">
<title>什么是数值?</title>
<indexterm>
<primary>value</primary>
</indexterm>
<para><phrase role="forProgrammers">这部分对于程序员来说可以直接跳过的。</phrase></para>
<para>正如你知道的,来自于每天所使用的数字,比如16,0.5等这些用语就是
<emphasis>数值</emphasis> 的示例,也就是数字。在计算机语言中,
这些用语有着更广泛的含义,比如数值并不一定是数字类型值,比如面这个数据模型:</para>
<programlisting role="dataModel" xml:id="example.stdDataModel">(root)
|
+- user = "Big Joe"
|
+- today = Jul 6, 2007
|
+- todayHoliday = false
|
+- lotteryNumbers
| |
| +- (1st) = 20
| |
| +- (2st) = 14
| |
| +- (3rd) = 42
| |
| +- (4th) = 8
| |
| +- (5th) = 15
|
+- cargo
|
+- name = "coal"
|
+- weight = 40
</programlisting>
<para>变量 <literal>user</literal><emphasis>value</emphasis>
是"Big Joe"(字符串), <literal>today</literal><emphasis>value</emphasis>
是 Jul 6, 2007 (日期),<literal>todayHoliday</literal>
<emphasis>value</emphasis> 是false(布尔值,比如yes/no等值)。
<literal>lotteryNumbers</literal><emphasis>value</emphasis>
是包含20,14, 42, 8, 15的序列。当然在这种意义上,
<literal>lotteryNumbers</literal> 是多值类型。它 <emphasis>包含</emphasis>
多个值(比如,其中的第二项的 <emphasis>value</emphasis> 是14),但是
<literal>lotteryNumbers</literal> 本身还是单值。它就像一个装有其它很多东西的盒子
(或称之为容器,译者注),但盒子作为整体还是视作单独的。最后还有一个数值
<literal>cargo</literal>,它的 <emphasis>value</emphasis> 是一个哈希表
(也是类似盒子一样的东西)。所以说,数值就是存储在变量中的(比如,在
<literal>user</literal><literal>cargo</literal>
<literal>cargo.name</literal> 中)的那个东西。但是,
不需要存储于变量之中的数值也可以称之为数值,比如下面的数字100:</para>
<programlisting role="template">&lt;#if cargo.weight &lt; <emphasis>100</emphasis>&gt;Light cargo&lt;/#if&gt;</programlisting>
<para>当模板被执行时,计算出的临时结果也称为数值,比如这里的20和120(它会打印120):</para>
<programlisting role="template">${cargo.weight / 2 + 100}</programlisting>
<para>这里针对最后一种表示进行解释:有两个数,40(货物的重量)和2,
相除的结果是20,这是一个新计算出来的数值。然后,把它和100相加,
那么120就计算出来了,接着就打印出来。
(<literal>${<replaceable>...</replaceable>}</literal>),之后模板继续向下执行,
直到所有结果都计算出来。</para>
<para>现在你应该能体会到数值这个词的含义了。</para>
</section>
<section>
<title>什么是类型?</title>
<para>数值中非常重要的一个概念就是类型。比方说,变量
<literal>user</literal> 的类型是字符串,变量 <literal>lotteryNumbers</literal>
的类型是序列。数值的类型这个概念非常的重要,因为它决定了这些数值可以在哪里使用的最大限度。
比如说,使用 <literal>${user / 2}</literal> 就是错误的,但是使用
<literal>${cargo.weight / 2}</literal> 就能计算出结果,为20,
因为算术中的除法仅对数字类型的值有效,而不能用于字符串。
仅当 <literal>cargo</literal> 是一个哈希表变量时,表达式 <literal>cargo.name</literal>
才可以使用点。也可以用 <literal>&lt;#list <replaceable>...</replaceable>&gt;</literal>
指令来遍历序列。而 <literal>&lt;#if ...&gt;</literal> 指令的条件只能是布尔值等。</para>
<note>
<para>这里说一点点的术语:称 "布尔" 或 "布尔值" 或 "布尔类型" 都是相同的含义。</para>
</note>
<para xml:id="topic.multitype"><indexterm>
<primary>Multi-typed value</primary>
</indexterm>数值同时也可以含有多种类型,尽管很少这么使用。看一下下面这个数据模型
<literal>mouse</literal> 它本身就又是字符串,又是哈希表:</para>
<programlisting role="dataModel">(root)
|
+- mouse = "Yerri"
|
+- age = 12
|
+- color = "brown"</programlisting>
<para>如果用上面的数据模型合并到下面的模板中:</para>
<programlisting role="template">${mouse} &lt;#-- uses mouse as a string --&gt;
${mouse.age} &lt;#-- uses mouse as a hash --&gt;
${mouse.color} &lt;#-- uses mouse as a hash --&gt;</programlisting>
<para>将会输出:</para>
<programlisting role="output">Yerri
12
brown</programlisting>
</section>
<section>
<title>数据模型是哈希表</title>
<para>注意观察每个数据模型的例子你也许能发现:被"(root)"所标识的内容就是哈希表类型的值。
当编写如 <literal>user</literal> 这样的代码时,那就意味着要把"user"变量存储在哈希表的根上。
就像编写 <literal>root.user</literal>一样,这里但并没有名"root"为的变量,
那么这就起不到任何作用了。</para>
<para>某些人也许会被这种数据模型的例子所困惑,也就是说,根哈希表包含更多的哈希表或序列
(<literal>lotteryNumbers</literal> and <literal>cargo</literal>)。其它就没有更特殊的内容了。
哈希表包含其他变量,那些变量包含其它值,这些数值可以是字符串,数字等变量,
当然也可以是哈希表或序列变量。最初我们解释过的,就像字符串和数字,
序列或哈希表也是一种值的表示形式。</para>
</section>
</section>
<section xml:id="dgui_datamodel_types">
<title>类型</title>
<para>支持的类型有:</para>
<itemizedlist spacing="compact">
<listitem>
<para><link linkend="dgui_datamodel_scalar"
os="">标量:</link></para>
<itemizedlist spacing="compact">
<listitem>
<para>字符串</para>
</listitem>
<listitem>
<para>数字</para>
</listitem>
<listitem>
<para>布尔值</para>
</listitem>
<listitem>
<para>日期/时间 (日期,时间或日期时间)</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><link
linkend="dgui_datamodel_container">容器:</link></para>
<itemizedlist spacing="compact">
<listitem>
<para>哈希表</para>
</listitem>
<listitem>
<para>序列</para>
</listitem>
<listitem>
<para>集合</para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>子程序:</para>
<itemizedlist spacing="compact">
<listitem>
<para><link linkend="dgui_datamodel_method">方法和函数</link></para>
</listitem>
<listitem>
<para><link linkend="dgui_datamodel_userdefdir">用户自定义指令</link></para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para>其它/很少使用:</para>
<itemizedlist spacing="compact">
<listitem>
<para><link linkend="dgui_datamodel_node">结点</link></para>
</listitem>
</itemizedlist>
</listitem>
</itemizedlist>
<section xml:id="dgui_datamodel_scalar">
<title>标量</title>
<anchor xml:id="topic.designer.scalarVariable"/>
<para>标量是最基本,最简单的数值类型,它们可以是:</para>
<itemizedlist>
<listitem>
<para><indexterm>
<primary>string</primary>
<secondary>FTL 值类型</secondary>
</indexterm>字符串:表示简单的文本,例如:产品的名称。</para>
<para>如果想在模板中直接给出字符串值,而不是使用数据模型中的变量,
那么将文本内容写在引号内即可,比如 <literal>"green mouse"</literal>
<literal>'green mouse'</literal>。(关于语法的更多细节可以在
<link linkend="dgui_template_exp_direct_string" xml:lang="">后续章节</link>中找到。)</para>
</listitem>
<listitem>
<para><indexterm>
<primary>number</primary>
<secondary>FTL 值类型</secondary>
</indexterm>数值:比如说,产品的价格。
<phrase role="forProgrammers">整数和非整数是不区分的;只有单一的数字类型。比如使用了计算器,
计算3/2的结果是1.5而不是1。</phrase></para>
<para>如果要在模板中直接给出数字的值,那么可以这么来写:
<literal>150</literal><literal>-90.05</literal>
<literal>0.001</literal>。(关于语法的更多细节可以在 <link
linkend="dgui_template_exp_direct_number" xml:lang="">后续章节</link>中找到。)</para>
</listitem>
<listitem>
<para><indexterm>
<primary>boolean</primary>
<secondary>FTL 值类型</secondary>
</indexterm>布尔值:布尔值代表了逻辑上的对或错(是或否)。比如:用户是否登录了。
典型的应用是使用布尔值作为 <literal>if</literal> 指令的条件,
比如 <literal>&lt;#if loggedIn &gt;<replaceable>...</replaceable>&lt;/#if&gt;</literal> 或者
<literal>&lt;#if price == 0&gt;<replaceable>...</replaceable>&lt;/#if&gt;</literal>
后面这个 <literal>price == 0</literal> 部分的结果就是布尔值。</para>
<para>在模板中可以使用保留字 <literal>true</literal>
<literal>false</literal> 来指定布尔值。</para>
</listitem>
<listitem>
<para><indexterm>
<primary>date</primary>
<secondary>FTL 值类型</secondary>
</indexterm><indexterm>
<primary>time</primary>
<secondary>FTL 值类型</secondary>
</indexterm><indexterm>
<primary>date-time</primary>
<secondary>FTL 值类型</secondary>
</indexterm>日期:日期变量可以存储和日期/时间相关的数据。
一共有三种变化:</para>
<itemizedlist>
<listitem>
<para>日期:精确到天的日期,没有时间部分。比如April 4, 2003。</para>
</listitem>
<listitem>
<para>时间:精确到毫秒,没有日期部分。比如10:19:18 PM。</para>
</listitem>
<listitem>
<para>日期-时间(有时也被称为"时间戳"),比如April 4,2003 10:19:18 PM。
有日期和时间两部分,时间部分的存储精确到毫秒。</para>
</listitem>
</itemizedlist>
<para>不幸的是,受到Java平台的限制,FreeMarker 有时是不能决定日期的部哪分被使用
(也就是说,是日期-时间格式,日期格式还是时间格式)。
这个问题的解决方法是一个的高级话题,将会在
<link linkend="ref_builtin_date_datetype">后续章节</link>讨论。</para>
<para>模板中直接定义日期数值是可以的,但这也是高级话题,将会在
<link linkend="ref_builtin_string_date">后续章节</link> 中进行解释。</para>
</listitem>
</itemizedlist>
<para>要记住,FreeMarker区别字符串,数字,布尔值和日期类型的值。比如,
字符串 <literal>"150"</literal> 看起来很像数字 <literal>150</literal>
字符串只是字符的任意序列,不能将它用于计算目的,也不能和其它数字进行比较等等。</para>
</section>
<section xml:id="dgui_datamodel_container">
<title>容器</title>
<remark>站在一个比之前更''专业''的视角上来重新解释哈希表和序列,
还有一些深入的思考。</remark>
<para>这些值存在的目的是为了包含其他变量;它们只是容器。
它们包含的变量通常视为 <emphasis>subvariables</emphasis>
(子变量,译者注)。容器的类型有:</para>
<itemizedlist>
<listitem>
<para><indexterm>
<primary>hash</primary>
<secondary>FTL 值类型</secondary>
</indexterm>哈希表:每个子变量都可以通过一个唯一的名称来查找。
这个名称是不受限制的字符串。哈希表 <emphasis>并不确定其中子变量的顺序</emphasis>
也就是说没有第一个子变量,第二个子变量这样的说法等;变量仅仅是通过名称来访问的。
(就像Java语言中的HashMap一样,是实现了Hash算法的Map,不记录内部元素的顺序,
仅仅通过名称来访问。译者注)</para>
</listitem>
<listitem>
<para><indexterm>
<primary>sequence</primary>
<secondary>FTL 值类型</secondary>
</indexterm>序列:每个子变量通过一个整数来标识。第一个子变量的标识符是0,
第二个是1,第三个是2,这样来类推,而且子变量是有顺序的。这些数次通常被称为
<emphasis>indexes</emphasis>(索引,译者注)。序列通常比较密集,也就是所有的索引,
包括最后一个子变量的,它们和子变量都是相关联的,但不是绝对必要的。
子变量的类型也并不需要完全一致。</para>
</listitem>
<listitem>
<para><indexterm>
<primary>collection</primary>
<secondary>FTL 值类型</secondary>
</indexterm>集合:从模板设计者角度来看,集合是有限制的序列。不能获取集合的大小,
也不能通过索引取出集合中的子变量,但是它们仍然可以通过
<link linkend="ref.directive.list"><literal>list</literal> 指令</link>来遍历。</para>
</listitem>
</itemizedlist>
<para>请注意,一个值也可有多种类型
<link linkend="topic.multitype">一个值也可有多种类型</link>
对于一个值可能同时存在哈希表和序列这两种类型,这时,
该变量就支持索引和名称两种访问方式。
不过容器基本是当作哈希表或者序列来使用的,而不是两者同时使用。</para>
<para>尽管存储在哈希表,序列(集合)中的变量可以是任意类型的,
这些变量也可以是哈希表,序列(或集合)。这样就可以构建任意深度的数据结构。</para>
<para>数据模型本身(最好说成是它的根root)也是哈希表。</para>
</section>
<section>
<title>子程序</title>
<section xml:id="dgui_datamodel_method">
<title>方法和函数</title>
<anchor xml:id="topic.designer.methodVariable"/>
<indexterm>
<primary>method</primary>
<secondary>FTL 值类型</secondary>
</indexterm>
<para>当一个值是方法或函数的时候,那么它就可以计算其他值,结果取决于传递给它的参数。</para>
<para><phrase role="forProgrammers">这部分是对程序员来说的:方法/函数是一等类型值,
就像函数化的编程语言。也就是说函数/方法也可以是其他函数/方法的参数或者返回值,
并可以把它们定义成变量等。</phrase></para>
<para>假设程序员在数据模型中放置了一个方法变量 <literal>avg</literal>
该变量用来计算数字的平均值。如果给定3和5作为参数,访问
<literal>avg</literal> 时就能得到结果4。</para>
<para>方法的使用将会在 <link
linkend="dgui_template_exp_methodcall">后续章节</link> 中进行解释,
下面这个示例会帮助我们理解方法的使用:</para>
<programlisting role="template">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)}</programlisting>
<para>将会输出:</para>
<programlisting role="output">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</programlisting>
<para>那么方法和函数有什么区别呢?这是模板作者所关心的,
它们没有关系,但也不是一点关系都没有。
方法是来自于数据模型 (<phrase
role="forProgrammers">它们反射了Java对象的方法</phrase>)
而函数是定义在模板内的 (使用 <link
linkend="ref.directive.function"><literal>function</literal>
指令</link> -- 也是高级话题),但二者可以用同一种方式来使用。</para>
</section>
<section xml:id="dgui_datamodel_userdefdir">
<title>用户自定义指令</title>
<indexterm>
<primary>macro</primary>
<secondary>FTL 值类型</secondary>
</indexterm>
<indexterm>
<primary>directive</primary>
<secondary>FTL 值类型</secondary>
</indexterm>
<indexterm>
<primary>user-defined directive</primary>
<secondary>FTL 值类型</secondary>
</indexterm>
<para>这种类型的值可以作为用户自定义指令(换句话说,就是FreeMarker的标签)
用户自定义指令是一种子程序,一种可以复用的模板代码段。但这也是一个高级话题,
将会在 <link linkend="dgui_misc_userdefdir">后续章节</link> 中进行解释。</para>
<para><phrase role="forProgrammers">这部分是对程序员来说的:
用户自定义指令(比如宏)也是一等值类型,就像函数/方法一样。</phrase></para>
<para>这里仅仅对用户自定义指令有一个认识即可(如果现在还不能理解可以先忽略它)。
假设现在有一个变量 <literal>box</literal>,它的值是用户自定义的指令,
用来打印一些特定的HTML信息,包含标题和一条信息。那么,
<literal>box</literal> 变量就可以在模板中使用(示例如下):</para>
<programlisting role="template">&lt;@<emphasis>box</emphasis> title="Attention!"&gt;
Too much copy-pasting may leads to
maintenance headaches.
&lt;/@<emphasis>box</emphasis>&gt;</programlisting>
</section>
<section>
<title>函数/方法和用户自定义指令的比较</title>
<para>这部分内容也是对高级用户来说的(如果还不能理解可以先忽略它)。
如果要使用函数/方法或自定义指令去实现一些东西的时候,
二者之间的选择是两难的。按经验来说,如果能够实现需求,
请先用自定义指令而不要用函数/方法。如果:</para>
<itemizedlist>
<listitem>
<para>... 输出(返回值)的是标记(HTML,XML等)。
主要原因是函数的返回结果可以自动进行XML转义(这是因为
<literal>${<replaceable>...</replaceable>}</literal> 的特性),
而用户自定义指令的输出则不是
(这是因为 <literal>&lt;@<replaceable>...</replaceable>&gt;</literal> 的特性所致;
它的输出假定是标记,因此已经转义过了)。</para>
</listitem>
<listitem>
<para>... 副作用也是很重要的一点,它没有返回值。
例如一个指令的目的是往服务器日志中添加一条。
(事实上不能得到自定义指令的返回值,
但有些反馈的类型是有可能设置非本地变量的。)</para>
</listitem>
<listitem>
<para>... 会进行流程的控制(就像 <literal>list</literal>
<literal>if</literal> 指令那样)。但是不能在函数/方法上这么做。</para>
</listitem>
</itemizedlist>
<para>在模板中,FreeMarker不知道的Java对象的方法通常是可以作为方法来使用的,
而不用考虑Java对象方法本身的特性,因为在这里没有其他的选择。</para>
</section>
</section>
<section>
<title>其它</title>
<section xml:id="dgui_datamodel_node">
<title>结点</title>
<indexterm>
<primary>node</primary>
<secondary>FTL 值类型</secondary>
</indexterm>
<para>结点变量代表了树状结构中的一个结点,而且通常是配合
<link linkend="xgui">XML 处理</link>的,这是专业而且更高级的话题。</para>
<para>这里我们仅对 <emphasis>高级用户</emphasis> 进行一个概要说明:
结点和存储在其他结点中的序列很相似,通常也被当作为子结点。结点存储它所在的容器结点的引用,
也就是父结点。结点的主要作用是拓扑信息;其它数据必须通过使用多类型的值来存储。
就像一个值可以同时是一个结点和一个数字,这样它存储的数字可以作为如支付额来使用。
除了拓扑信息,结点也可以存储一些元信息(即metadata,译者注):如结点名称,它的类型(字符串),
命名空间(字符串)。若一个结点象征XHTML文档中的 <literal>h1</literal> 元素,
那么它的名字可以是 <literal>"h1"</literal>,类型可以是 <literal>"element"</literal>
命名空间可以是 <literal>"http://www.w3.org/1999/xhtml"</literal>。但对于数据模型设计者来说,
这些元信息,还有如何来使用它们又有什么意义呢。检索拓扑信息和元信息的方法将会在
<link linkend="ref_builtins_node">后续章节</link> 中来说明(这里可以先不用理解它们)。</para>
</section>
</section>
</section>
</chapter>
<chapter xml:id="dgui_template">
<title>模板</title>
<indexterm>
<primary>template</primary>
</indexterm>
<note>
<para>这里假设你已经阅读完 <xref linkend="dgui_quickstart"/>
<xref linkend="dgui_datamodel"/> 章节了。</para>
</note>
<section xml:id="dgui_template_overallstructure">
<title>总体结构</title>
<para>实际上用程序语言编写的程序就是模板。
<indexterm>
<primary>FTL</primary>
</indexterm><emphasis role="term">FTL</emphasis> (代表FreeMarker模板语言)。
这是为编写模板设计的非常简单的编程语言。</para>
<para>模板(FTL编程)是由如下部分混合而成的:</para>
<itemizedlist>
<listitem>
<para><emphasis role="term">文本</emphasis><indexterm>
<primary>text</primary>
</indexterm>:文本会照着原样来输出。</para>
</listitem>
<listitem>
<para><emphasis role="term">插值</emphasis><indexterm>
<primary>interpolation</primary>
</indexterm>:这部分的输出会被计算的值来替换。插值由
<literal>${</literal> and <literal>}</literal> 所分隔(或者
<literal>#{</literal> and <literal>}</literal>,这种风格已经不建议再使用了;<link
linkend="ref_depr_numerical_interpolation">点击查看更多</link>)。</para>
</listitem>
<listitem>
<para><emphasis role="term">FTL 标签</emphasis><indexterm>
<primary>FTL tag</primary>
</indexterm>:FTL标签和HTML标签很相似,但是它们却是给FreeMarker的指示,
而且不会打印在输出内容中。</para>
</listitem>
<listitem>
<para><emphasis role="term">注释</emphasis><indexterm>
<primary>comment</primary>
</indexterm><indexterm>
<primary>&lt;#--...--&gt;</primary>
</indexterm><indexterm>
<primary>#</primary>
</indexterm>:注释和HTML的注释也很相似,但它们是由
<literal>&lt;#--</literal>
<literal>--&gt;</literal>来分隔的。注释会被FreeMarker直接忽略,
更不会在输出内容中显示。</para>
</listitem>
</itemizedlist>
<para>我们来看一个具体的模板。其中的内容已经用颜色来标记了:
<phrase role="markedText">文本</phrase>
<phrase role="markedInterpolation">插值</phrase><phrase
role="markedFTLTag">FTL 标签</phrase><phrase
role="markedComment">注释</phrase>。为了看到可见的
<link linkend="gloss.lineBreak">换行符</link>
这里使用了 <phrase role="markedInvisibleText">[BR]</phrase></para>
<programlisting role="template"><phrase role="markedText">&lt;html&gt;<phrase
role="markedInvisibleText">[BR]</phrase>
&lt;head&gt;<phrase role="markedInvisibleText">[BR]</phrase>
  &lt;title&gt;Welcome!&lt;/title&gt;<phrase role="markedInvisibleText">[BR]</phrase>
&lt;/head&gt;<phrase role="markedInvisibleText">[BR]</phrase>
&lt;body&gt;<phrase role="markedInvisibleText">[BR]</phrase>
  <phrase role="markedComment">&lt;#-- Greet the user with his/her name --&gt;</phrase><phrase
role="markedInvisibleText">[BR]</phrase>
  &lt;h1&gt;Welcome <phrase role="markedInterpolation">${user}</phrase>!&lt;/h1&gt;<phrase
role="markedInvisibleText">[BR]</phrase>
  &lt;p&gt;We have these animals:<phrase role="markedInvisibleText">[BR]</phrase>
  &lt;ul&gt;<phrase role="markedInvisibleText">[BR]</phrase>
  <phrase role="markedFTLTag">&lt;#list animals as animal&gt;</phrase><phrase
role="markedInvisibleText">[BR]</phrase>
    &lt;li&gt;<phrase role="markedInterpolation">${animal.name}</phrase> for <phrase
role="markedInterpolation">${animal.price}</phrase> Euros<phrase
role="markedInvisibleText">[BR]</phrase>
  <phrase role="markedFTLTag">&lt;/#list&gt;</phrase><phrase
role="markedInvisibleText">[BR]</phrase>
  &lt;/ul&gt;<phrase role="markedInvisibleText">[BR]</phrase>
&lt;/body&gt;<phrase role="markedInvisibleText">[BR]</phrase>
&lt;/html&gt;</phrase></programlisting>
<para>FTL是区分大小写的。 <literal>list</literal> 是指令的名称而
<literal>List</literal> 就不是。类似地 <literal>${name}</literal>
<literal>${Name}</literal><literal>${NAME}</literal> 也是不同的。</para>
<para>请注意非常重要的一点: <phrase role="markedInterpolation">插值</phrase>
仅仅可以在 <phrase role="markedText">文本</phrase> 中使用。
(也可以是字符串表达式;请参考
<link linkend="dgui_template_exp_stringop_interpolation">后续内容</link>)</para>
<para><phrase role="markedFTLTag">FTL 标签</phrase> 不可以在其他
<phrase role="markedFTLTag">FTL 标签</phrase>
<phrase role="markedInterpolation">插值</phrase>中使用。比如,
这样做是 <emphasis>错误</emphasis> 的:
<literal>&lt;#if &lt;#include 'foo'&gt;='bar'&gt;...&lt;/#if&gt;</literal></para>
<para><phrase role="markedComment">注释</phrase> 可以放在
<phrase role="markedFTLTag">FTL 标签</phrase>
<phrase role="markedInterpolation">插值</phrase>中。比如:</para>
<programlisting role="template"><phrase role="markedText">&lt;h1&gt;Welcome <phrase
role="markedInterpolation">${user <phrase role="markedComment">&lt;#-- The name of user --&gt;</phrase>}</phrase>!&lt;/h1&gt;<phrase
role="markedInvisibleText">[BR]</phrase>
&lt;p&gt;We have these animals:<phrase role="markedInvisibleText">[BR]</phrase>
&lt;ul&gt;<phrase role="markedInvisibleText">[BR]</phrase>
<phrase role="markedFTLTag">&lt;#list <phrase role="markedComment">&lt;#-- some comment... --&gt;</phrase> animals as <phrase
role="markedComment">&lt;#-- again... --&gt;</phrase> animal&gt;</phrase><phrase
role="markedInvisibleText">[BR]</phrase></phrase>
<replaceable>...</replaceable></programlisting>
<note>
<para>如果您已经自行尝试测试上面所有示例的话:那么也许会注意到,一些空格、
制表符和换行符从模板的输出中都不见了,尽管我们之前已经说了
<phrase role="markedText">文本</phrase> 是按照原样输出的。
现在不用为此而计较,这是由于FreeMarker的"空格剥离"特性在起作用,
它当然会自动去除一些多余的空格,制表符和换行符了。
这个特性在 <link linkend="dgui_misc_whitespace">后续内容</link> 中会解释到。</para>
</note>
</section>
<section xml:id="dgui_template_directives">
<title>指令</title>
<indexterm>
<primary>&lt;#...&gt;</primary>
</indexterm>
<indexterm>
<primary>#</primary>
</indexterm>
<anchor xml:id="term.designer.directive"/>
<remark>请注意,表达式章节是基于本章之上的,而插值章节是基于表达式章节的。
因此,指令指令章节就是基本内容后的第一部分内容。</remark>
<para><indexterm>
<primary>directive</primary>
</indexterm>使用 FTL标签来调用 <emphasis role="term">指令</emphasis>
在示例中已经调用了 <literal>list</literal> 指令。在语法上我们使用了两个标签:
<literal>&lt;#list animals as animal&gt;</literal>
<literal>&lt;/#list&gt;</literal></para>
<para><indexterm>
<primary>FTL tag</primary>
</indexterm>FTL 标签分为两种:</para>
<itemizedlist>
<listitem>
<para>开始标签:
<literal>&lt;#<replaceable>directivename</replaceable>
<replaceable>parameters</replaceable>&gt;</literal></para>
</listitem>
<listitem>
<para>结束标签:
<literal>&lt;/#<replaceable>directivename</replaceable>&gt;</literal></para>
</listitem>
</itemizedlist>
<para>除了标签以 <literal>#</literal> 开头外,其他都和HTML,XML的语法很相似。
如果标签没有嵌套内容(在开始标签和结束标签之间的内容),那么可以只使用开始标签。
例如 <literal>&lt;#if
<replaceable>something</replaceable>&gt;<replaceable>...</replaceable>&lt;/#if&gt;</literal>
而FreeMarker知道 <literal>&lt;#include <replaceable>something</replaceable>&gt;</literal> 中的
<literal>include</literal> 指令没有可嵌套的内容。</para>
<para><literal><replaceable>parameters</replaceable></literal> 的格式由
<literal><replaceable>directivename</replaceable></literal>来决定。</para>
<para>事实上,指令有两种类型: <link
linkend="gloss.predefinedDirective">预定义指令</link>
<link linkend="gloss.userDefinedDirective">用户自定义指令</link>
对于用户自定义的指令使用 <literal>@</literal> 来代替
<literal>#</literal>,比如,<literal>&lt;@mydirective
<replaceable>parameters</replaceable>&gt;<replaceable>...</replaceable>&lt;/@mydirective&gt;</literal>
更深的区别在于如果指令没有嵌套内容,那么必须这么使用 <literal>&lt;@mydirective
<replaceable>parameters</replaceable> /&gt;</literal>,这和XML语法很相似
(例如 <literal>&lt;img <replaceable>...</replaceable> /&gt;</literal>)。
但用户自定义指令是更高级的话题,将会在 <link
linkend="dgui_misc_userdefdir">后续章节</link> 中继续讨论。</para>
<para>像HTML标签一样,FTL标签也必须正确地嵌套使用。下面这段示例代码就是错的,
因为 <literal>if</literal> 指令在 <literal>list</literal> 指令嵌套内容的内外都有:</para>
<programlisting role="template">&lt;ul&gt;
<emphasis>&lt;#list animals as animal&gt;</emphasis>
&lt;li&gt;${animal.name} for ${animal.price} Euros
<emphasis>&lt;#if user == "Big Joe"&gt;</emphasis>
(except for you)
<emphasis>&lt;/#list&gt;</emphasis> &lt;#-- WRONG! The "if" has to be closed first. --&gt;
<emphasis>&lt;/#if&gt;</emphasis>
&lt;/ul&gt;</programlisting>
<para>请注意,FreeMarker 仅仅关心FTL标签的嵌套而不关心HTML标签的嵌套。
它只会把HTML看做是文本,不会来解释HTML。</para>
<para>如果你尝试使用一个不存在的指令(比如,输错了指令的名称),
FreeMarker 就会拒绝执行模板,同时抛出错误信息。</para>
<para>FreeMarker会忽略FTL标签中多余的 <link
linkend="gloss.whiteSpace">空白标记</link>,所以也可以这么来写代码:</para>
<programlisting role="template"><phrase role="markedText"><phrase
role="markedFTLTag">&lt;#list<phrase role="markedInvisibleText">[BR]</phrase>
  animals       as<phrase role="markedInvisibleText">[BR]</phrase>
     animal<phrase role="markedInvisibleText">[BR]</phrase>
&gt;</phrase><phrase role="markedInvisibleText">[BR]</phrase>
<phrase role="markedInterpolation">${animal.name}</phrase> for <phrase
role="markedInterpolation">${animal.price}</phrase> Euros<phrase
role="markedInvisibleText">[BR]</phrase>
<phrase role="markedFTLTag">&lt;/#list    &gt;</phrase></phrase></programlisting>
<para>当然,也不能在 <literal>&lt;</literal><literal>&lt;/</literal>
和指令名中间入空白标记。</para>
<para>所有指令的详细介绍可以在 <xref linkend="ref_directives"/>
中找到(但是建议您先看表达式章节)。</para>
<note>
<para>通过配置,FreeMarker 可以在FTL标签和FTL注释中,
使用 <literal>[</literal><literal>]</literal> 来代替
<literal>&lt;</literal><literal>&gt;</literal>,就像
<literal>[#if user == "Big Joe"]<replaceable>...</replaceable>[/#if]</literal>
要获取更多信息,请参考:<xref linkend="dgui_misc_alternativesyntax"/></para>
</note>
<note>
<para>通过配置,FreeMarker 可以不需要 <literal>#</literal>
来理解预定义指令(比如 <literal>&lt;if user
== "Big Joe"&gt;<replaceable>...</replaceable>&lt;/if&gt;</literal>)。
而我们不建议这样来使用。要获取更多信息,请参考:<xref linkend="ref_depr_oldsyntax"/></para>
</note>
</section>
<section xml:id="dgui_template_exp">
<title>表达式</title>
<para><indexterm>
<primary>expression</primary>
</indexterm>当需要给插值或者指令参数提供值时,可以使用变量或其他复杂的表达式。
例如,我们设x为8,y为5,那么 <literal>(x + y)/2</literal>
的值就会被处理成数字类型的值6.5。</para>
<para>在我们展开细节之前,先来看一些具体的例子:</para>
<itemizedlist>
<listitem>
<para>当给插值提供值时:插值的使用方式为
<literal>${<replaceable>expression</replaceable>}</literal>
把它放到你想输出文本的位置上,然后给值就可以打印出来了。
<literal>${(5 + 8)/2}</literal> 会打印出 ''6.5'' 来
(如果输出的语言不是美国英语,也可能打印出''6,5''来)。</para>
</listitem>
<listitem>
<para>当给指令参数提供值时:在入门章节我们已经看到
<literal>if</literal> 指令的使用了。这个指令的语法是:<literal>&lt;#if
<replaceable>expression</replaceable>&gt;<replaceable>...</replaceable>&lt;/#if&gt;</literal>
这里的表达式计算结果必须是布尔类型的。比如
<literal>&lt;#if 2 &lt; 3&gt;</literal> 中的 <literal>2
&lt;3</literal> (2小于3)是结果为 <literal>true</literal> 的布尔表达式。</para>
</listitem>
</itemizedlist>
<section xml:id="exp_cheatsheet">
<title>快速浏览(备忘单)</title>
<para>这里给已经了解 FreeMarker 的人或有经验的程序员的提个醒:</para>
<itemizedlist spacing="compact">
<listitem>
<para><link linkend="dgui_template_exp_direct">直接指定值</link></para>
<itemizedlist spacing="compact">
<listitem>
<para><link
linkend="dgui_template_exp_direct_string">字符串</link>
<literal>"Foo"</literal> 或者 <literal>'Foo'</literal> 或者
<literal>"It's \"quoted\""</literal> 或者 <literal>'It\'s
"quoted"'</literal> 或者
<literal>r"C:\raw\string"</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_direct_number">数字</link>
<literal>123.45</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_direct_boolean">布尔值</link>
<literal>true</literal><literal>false</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_direct_seuqence">序列</link>
<literal>["foo", "bar", 123.45]</literal>; 值域:
<literal>0..9</literal>, <literal>0..&lt;10</literal> (或
<literal>0..!10</literal>), <literal>0..</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_direct_hash">哈希表</link>
<literal>{"name":"green mouse",
"price":150}</literal></para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_var">检索变量</link></para>
<itemizedlist spacing="compact">
<listitem>
<para><link
linkend="dgui_template_exp_var_toplevel">顶层变量</link>
<literal>user</literal></para>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_var_hash">从哈希表中检索数据</link><literal>user.name</literal>
<literal>user["name"]</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_var_sequence">从序列中检索数据</link>
<literal>products[5]</literal></para>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_var_special">特殊变量</link><literal>.main</literal></para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_stringop">字符串操作</link></para>
<itemizedlist spacing="compact">
<listitem>
<para><link
linkend="dgui_template_exp_stringop_interpolation">插值(或连接)</link>
<literal>"Hello ${user}!"</literal> (或 <literal>"Hello
" + user + "!"</literal>)</para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_get_character">获取一个字符</link>
<literal>name[0]</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_stringop_slice">字符串切分:</link> 包含结尾:
<literal>name[0..4]</literal>,不包含结尾:
<literal>name[0..&lt;5]</literal>,基于长度(宽容处理):
<literal>name[0..*5]</literal>,去除开头:
<literal>name[5..]</literal></para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_sequenceop">序列操作</link></para>
<itemizedlist spacing="compact">
<listitem>
<para><link
linkend="dgui_template_exp_sequenceop_cat">连接</link>
<literal>users + ["guest"]</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_seqenceop_slice">序列切分</link>:包含结尾:
<literal>products[20..29]</literal>, 不包含结尾:
<literal>products[20..&lt;30]</literal>,基于长度(宽容处理):
<literal>products[20..*10]</literal>,去除开头:
<literal>products[20..]</literal></para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_hashop">哈希表操作</link></para>
<itemizedlist spacing="compact">
<listitem>
<para><link
linkend="dgui_template_exp_hashop_cat">连接</link>
<literal>passwords + { "joe": "secret42" }</literal></para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_arit">算术运算</link>
<literal>(x * 1.5 + 10) / 2 - y %
100</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_comparison">比较运算</link>
<literal>x == y</literal><literal>x != y</literal>
<literal>x &lt; y</literal><literal>x &gt; y</literal>
<literal>x &gt;= y</literal><literal>x &lt;= y</literal>
<literal>x lt y</literal><literal>x lte y</literal>
<literal>x gt y</literal><literal>x gte y</literal>
等等。。。。。。</para>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_logicalop">逻辑操作</link>
<literal>!registered &amp;&amp; (firstVisit
|| fromEurope)</literal></para>
</listitem>
<listitem>
<para><link
linkend="dgui_template_exp_builtin">内建函数</link>
<literal>name?upper_case</literal>,
<literal>path?ensure_starts_with('/')</literal></para>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_methodcall">方法调用</link>
<literal>repeat("What", 3)</literal></para>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_missing">处理不存在的值</link></para>
<itemizedlist spacing="compact">
<listitem>
<para><link
linkend="dgui_template_exp_missing_default">默认值</link>
<literal>name!"unknown"</literal> 或者
<literal>(user.name)!"unknown"</literal> 或者
<literal>name!</literal> 或者
<literal>(user.name)!</literal></para>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_missing_test">检测不存在的值</link><literal>name??</literal> 或者
<literal>(user.name)??</literal></para>
</listitem>
</itemizedlist>
</listitem>
<listitem>
<para><link linkend="dgui_template_exp_assignment">赋值操作</link>
<literal>=</literal>, <literal>+=</literal>,
<literal>-=</literal>, <literal>*=</literal>,
<literal>/=</literal>, <literal>%=</literal>,
<literal>++</literal>, <literal>--</literal></para>
</listitem>
</itemizedlist>
<para>请参考: <link
linkend="dgui_template_exp_precedence">运算符优先级</link></para>
</section>
<section xml:id="dgui_template_exp_direct">
<title>直接确定值</title>
<indexterm>
<primary>literal</primary>
</indexterm>
<indexterm>
<primary>constant</primary>
</indexterm>
<para>通常我们喜欢是使用直接确定的值而不是计算的结果。</para>
<section xml:id="dgui_template_exp_direct_string">
<title>字符串</title>
<indexterm>
<primary>string</primary>
<secondary>literal</secondary>
</indexterm>
<para>在文本中确定字符串值的方法是看双引号,比如:
<literal>"some text"</literal>,或单引号,比如:
<literal>'some text'</literal>。这两种形式是等同的。
如果文本自身包含用于字符引用的引号
( <literal>"</literal><literal>'</literal>)或反斜杠时,
应该在它们的前面再加一个反斜杠;这就是转义。
转义允许直接在文本中输入任何字符,
也包括<link linkend="gloss.lineBreak">换行</link>。例如:</para>
<programlisting role="template">${"It's \"quoted\" and
this is a backslash: \\"}
${'It\'s "quoted" and
this is a backslash: \\'}</programlisting>
<para>将会输出:</para>
<programlisting role="output">It's "quoted" and
this is a backslash: \
It's "quoted" and
this is a backslash: \</programlisting>
<note>
<para>这里当然可以直接在模板中输入文本而不需要
<literal>${<replaceable>...</replaceable>}</literal>
但是我们在这里用它只是为了示例来说明表达式的使用。</para>
</note>
<anchor xml:id="topic.escapeSequence"/>
<indexterm>
<primary>escape sequences</primary>
</indexterm>
<para>下面的表格是FreeMarker支持的所有转义字符。
在字符串使用反斜杠的其他所有情况都是错误的,运行这样的模板都会失败。</para>
<informaltable border="1">
<thead>
<tr>
<th>转义序列</th>
<th>含义</th>
</tr>
</thead>
<tbody>
<tr>
<td><literal>\"</literal></td>
<td>引号 (u0022)</td>
</tr>
<tr>
<td><literal>\'</literal></td>
<td>单引号(又称为撇号) (u0027)</td>
</tr>
<tr>
<td><literal>\{</literal></td>
<td>起始花括号:<literal>{</literal></td>
</tr>
<tr>
<td><literal>\\</literal></td>
<td>反斜杠 (u005C)</td>
</tr>
<tr>
<td><literal>\n</literal></td>
<td>换行符 (u000A)</td>
</tr>
<tr>
<td><literal>\r</literal></td>
<td>回车 (u000D)</td>
</tr>
<tr>
<td><literal>\t</literal></td>
<td>水平制表符(又称为tab) (u0009)</td>
</tr>
<tr>
<td><literal>\b</literal></td>
<td>退格 (u0008)</td>
</tr>
<tr>
<td><literal>\f</literal></td>
<td>换页 (u000C)</td>
</tr>
<tr>
<td><literal>\l</literal></td>
<td>小于号:<literal>&lt;</literal></td>
</tr>
<tr>
<td><literal>\g</literal></td>
<td>大于号:<literal>&gt;</literal></td>
</tr>
<tr>
<td><literal>\a</literal></td>
<td>&amp;符:<literal>&amp;</literal></td>
</tr>
<tr>
<td><literal>\x<replaceable>Code</replaceable></literal></td>
<td>字符的16进制 <link linkend="gloss.unicode">Unicode</link> 码 (<link
linkend="gloss.UCS">UCS</link> 码)</td>
</tr>
</tbody>
</informaltable>
<para><literal>\x</literal> 之后的
<literal><replaceable>Code</replaceable></literal>
是1-4位的16进制码。下面这个示例中都是在字符串中放置版权符号:
<literal>"\xA9 1999-2001"</literal>
<literal>"\x0A9 1999-2001"</literal>
<literal>"\x00A9 1999-2001"</literal>
如果紧跟16进制码后一位的字符也能解释成16进制码时,
就必须把4位补全,否则FreeMarker就会误解你的意图。</para>
<para>请注意,字符序列 <literal>${</literal> (和 <literal>#{</literal>)
有特殊的含义,它们被用做插入表达式的数值(典型的应用是变量的值:
<literal>"Hello ${user}!"</literal>)。这将在 <link
linkend="dgui_template_exp_stringop_interpolation">后续章节</link>中解释。
如果想要打印 <literal>${</literal><literal>#{</literal>
就要使用下面所说的原生字符串,或者进行转义。就像
<literal>"foo $\{bar}"</literal>中的 <literal>{</literal></para>
<indexterm>
<primary>raw string literal</primary>
</indexterm>
<para>原生字符串是一种特殊的字符串。在原生字符串中,
反斜杠和 <literal>${</literal> 没有特殊含义,
它们被视为普通的字符。为了表明字符串是原生字符串,
在开始的引号或单引号之前放置字母<literal>r</literal>,例如:</para>
<programlisting role="template">${r"${foo}"}
${r"C:\foo\bar"}</programlisting>
<para>将会输出:</para>
<programlisting role="output">${foo}
C:\foo\bar</programlisting>
</section>
<section xml:id="dgui_template_exp_direct_number">
<title>数字</title>
<indexterm>
<primary>number</primary>
<secondary>literal</secondary>
</indexterm>
<para>输入不带引号的数字就可以直接指定一个数字,
必须使用点作为小数的分隔符而不能是其他的分组分隔符。
可以使用 <literal>-</literal><literal>+</literal>
来表明符号 (<literal>+</literal> 是多余的)。
科学记数法暂不支持使用 (<literal>1E3</literal> 就是错误的),
而且也不能在小数点之前不写0(<literal>.5</literal> 也是错误的)。</para>
<para>下面的数字都是合法的:<literal>0.08</literal>
<literal>-5.013</literal><literal>8</literal>
<literal>008</literal><literal>11</literal>
<literal>+11</literal></para>
<para>请注意,像 <literal>08</literal>
<literal>+8</literal><literal>8.00</literal>
<literal>8</literal> 这样的数值是完全等同的,它们都是数字8。
所以, <literal>${08}</literal><literal>${+8}</literal>
<literal>${8.00}</literal><literal>${8}</literal>
的输出都是一样的。</para>
</section>
<section xml:id="dgui_template_exp_direct_boolean">
<title>布尔值</title>
<indexterm>
<primary>boolean</primary>
<secondary>literal</secondary>
</indexterm>
<indexterm>
<primary>literal</primary>
<secondary>boolean</secondary>
</indexterm>
<para>直接写 <literal>true</literal> 或者
<literal>false</literal> 就表示一个布尔值了,不需使用引号。</para>
</section>
<section xml:id="dgui_template_exp_direct_seuqence">
<title>序列</title>
<indexterm>
<primary>sequence</primary>
<secondary>literal</secondary>
</indexterm>
<indexterm>
<primary>numerical sequence</primary>
</indexterm>
<indexterm>
<primary>numerical range expression</primary>
</indexterm>
<indexterm>
<primary>range expression</primary>
</indexterm>
<para>指定一个文字的序列,使用逗号来分隔其中的每个 <link
linkend="topic.dataModel.subVar">子变量</link>
然后把整个列表放到方括号中。例如:</para>
<programlisting role="template">&lt;#list <emphasis>["foo", "bar", "baz"]</emphasis> as x&gt;
${x}
&lt;/#list&gt;</programlisting>
<para>将会输出:</para>
<programlisting role="output">foo
bar
baz
</programlisting>
<para>列表中的项目是表达式,那么也可以这样做:
<literal>[2 + 2, [1, 2, 3, 4], "foo"]</literal>
其中第一个子变量是数字4,第二个子变量是一个序列,
第三个子变量是字符串"foo"。</para>
</section>
<section xml:id="dgui_template_exp_direct_ranges">
<title>值域</title>
<para>值域也是序列,但它们由指定包含的数字范围所创建,
而不需指定序列中每一项。比如:
<literal>0..&lt;m</literal>,这里假定 <literal>m</literal>
变量的值是5,那么这个序列就包含
<literal>[0, 1, 2, 3, 4]</literal>。值域的主要作用有:使用
<literal>&lt;#list<replaceable>...</replaceable>&gt;</literal>
来迭代一定范围内的数字,<link
linkend="dgui_template_exp_seqenceop_slice">序列切分</link>
<link linkend="dgui_template_exp_stringop_slice">字符串切分</link></para>
<para>值域表达式的通用形式是(
<literal><replaceable>start</replaceable></literal>
<literal><replaceable>end</replaceable></literal>
可以是任意的结果为数字表达式):</para>
<itemizedlist>
<listitem>
<para><literal><replaceable>start</replaceable>..<replaceable>end</replaceable></literal>
包含结尾的值域。比如 <literal>1..4</literal> 就是
<literal>[1, 2, 3, 4]</literal>, 而 <literal>4..1</literal>
就是 <literal>[4, 3, 2, 1]</literal>。当心一点,
包含结尾的值域不会是一个空序列,所以 <literal>0..length-1</literal>
就是 <emphasis>错误的</emphasis>,因为当长度是 <literal>0</literal> 时,
序列就成了 <literal>[0, -1]</literal></para>
</listitem>
<listitem>
<para><literal><replaceable>start</replaceable>..&lt;<replaceable>end</replaceable></literal>
<literal><replaceable>start</replaceable>..!<replaceable>end</replaceable></literal>
不包含结尾的值域。比如 <literal>1..&lt;4</literal> 就是
<literal>[1, 2, 3]</literal><literal>4..&lt;1</literal>
就是 <literal>[4, 3, 2]</literal>, 而 <literal>1..&lt;1</literal>
表示 <literal>[]</literal>。请注意最后一个示例;
结果可以是空序列,和 <literal>..&lt;</literal>
<literal>..!</literal> 没有区别; 最后这种形式在应用程序中使用了
<literal>&lt;</literal> 字符而引发问题(如HTML编辑器等)。</para>
</listitem>
<listitem>
<para><literal><replaceable>start</replaceable>..*<replaceable>length</replaceable></literal>
限定长度的值域,比如 <literal>10..*4</literal> 就是
<literal>[10, 11, 12, 13]</literal><literal>10..*-4</literal>
就是 <literal>[10, 9, 8, 7]</literal>,而 <literal>10..*0</literal>
表示 <literal>[]</literal>。当这些值域被用来切分时,
如果切分后的序列或者字符串结尾在指定值域长度之前,则切分不会有问题;请参考
<link linkend="dgui_template_exp_seqenceop_slice">序列切分</link> 来获取更多信息。</para>
<note>
<para>限定长度的值域是在 FreeMarker 2.3.21版本中引入的。</para>
</note>
</listitem>
<listitem>
<para><literal><replaceable>start</replaceable>..</literal>
无右边界值域。这和限制长度的值域很像,只是长度是无限的。
比如 <literal>1..</literal> 就是
<literal>[1, 2, 3, 4, 5, 6, ... ]</literal>,直到无穷大。
但是处理(比如列表显示)这种值域时要万分小心,处理所有项时,
会花费很长时间,直到内存溢出应用程序崩溃。
和限定长度的值域一样,当它们被切分时,
遇到切分后的序列或字符串结尾时,切分就结束了。</para>
<warning>
<para>无右边界值域在 FreeMarker 2.3.21 版本以前只能用于切分,
若用于其它用途,它就像空序列一样了。要使用新的特性,
使用 FreeMarker 2.3.21 版本是不够的,程序员要设置
<literal>incompatible_improvements</literal> 至少到2.3.21版本。</para>
</warning>
</listitem>
</itemizedlist>
<para>值域的进一步注意事项:</para>
<itemizedlist>
<listitem>
<para>值域表达式本身并没有方括号,比如这样编写代码
<literal>&lt;#assign myRange = 0..&lt;x&gt;</literal>
而不是 <literal>&lt;#assign myRange = [0..&lt;x]&gt;</literal>
后者会创建一个包含值域的序列。方括号是切分语法的一部分,就像
<literal><replaceable>seq</replaceable>[<replaceable>myRange</replaceable>]</literal></para>
</listitem>
<listitem>
<para>可以在 <literal>..</literal> 的两侧编写算术表达式而不需要圆括号,
就像 <literal>n + 1 ..&lt; m / 2 - 1</literal></para>
</listitem>
<listitem>
<para><literal>..</literal><literal>..&lt;</literal>
<literal>..!</literal><literal>..*</literal> 是运算符,
所以它们中间不能有空格。就像 <literal>n .. &lt;m</literal>
这样是错误的,但是 <literal>n ..&lt; m</literal> 这样就可以。</para>
</listitem>
<listitem>
<para>无右边界值域的定义大小是2147483647 (如果
<literal>incompatible_improvements</literal> 低于2.3.21版本,那么就是0),
这是由于技术上的限制(32位)。但当列表显示它们的时候,实际的长度是无穷大。</para>
</listitem>
<listitem>
<para>值域并不存储它们包含的数字,那么对于 <literal>0..1</literal>
<literal>0..100000000</literal> 来说,创建速度都是一样的,
并且占用的内存也是一样的。</para>
</listitem>
</itemizedlist>
</section>
<section xml:id="dgui_template_exp_direct_hash">
<title>哈希表</title>
<indexterm>
<primary>hash</primary>
<secondary>literal</secondary>
</indexterm>
<indexterm>
<primary>literal</primary>
<secondary>hash</secondary>
</indexterm>
<para>在模板中指定一个哈希表,就可以遍历用逗号分隔开的"键/值"对,
把列表放到花括号内即可。键和值成对出现并以冒号分隔。比如:
<literal>{ "name": "green mouse", "price": 150 }</literal>
请注意名和值都是表达式,但是用来检索的名称就必须是字符串类型,
而值可以是任意类型。</para>
</section>
</section>
<section xml:id="dgui_template_exp_var">
<title>检索变量</title>
<section xml:id="dgui_template_exp_var_toplevel">
<title>顶层变量</title>
<indexterm>
<primary>subvariable</primary>
<secondary>accessing</secondary>
</indexterm>
<para>访问顶层的变量,可以简单地使用变量名。例如,
用表达式 <literal>user</literal> 就可以在根上获取以
<quote>user</quote> 为名存储的变量值。然后打印出存储在里面的内容:</para>
<programlisting role="template">${user}</programlisting>
<para>如果没有顶层变量,那么 FreeMarker 在处理表达式时就会发生错误,
进而终止模板的执行(除非程序员事先配置了 FreeMarker)。</para>
<para>在这种表达式中,变量名只可以包含字母(也可以是非拉丁文),
数字(也可以是非拉丁数字),下划线 (<literal>_</literal>),
美元符号 (<literal>$</literal>),at符号 (<literal>@</literal>)。
此外,第一个字符不可以是ASCII码数字(<literal>0</literal>-<literal>9</literal>)。
从 FreeMarker 2.3.22 版本开始,变量名在任何位置也可以包含负号
(<literal>-</literal>),点(<literal>.</literal>)和冒号(<literal>:</literal>),
但这些必须使用前置的反斜杠(<literal>\</literal>)来转义,
否则它们将被解释成操作符。比如,读取名为<quote>data-id</quote>的变量,
表达式为 <literal>data\-id</literal>,因为 <literal>data-id</literal>
将被解释成 <quote>data minus id</quote>
(请注意,这些转义仅在标识符中起作用,而不是字符串中。)</para>
</section>
<section xml:id="dgui_template_exp_var_hash">
<title>从哈希表中检索数据</title>
<indexterm>
<primary>subvariable</primary>
<secondary>accessing</secondary>
</indexterm>
<indexterm>
<primary>hash</primary>
<secondary>accessing subvariable</secondary>
</indexterm>
<para>如果有一个表达式的结果是哈希表,
那么我们可以使用点和子变量的名字得到它的值,
假设我们有如下的数据模型:</para>
<programlisting role="dataModel">(root)
|
+- book
| |
| +- title = "Breeding green mouses"
| |
| +- author
| |
| +- name = "Julia Smith"
| |
| +- info = "Biologist, 1923-1985, Canada"
|
+- test = "title"</programlisting>
<para>现在,就可以通过<literal>book.title</literal>
来读取 <literal>title</literal>,book表达式将返回一个哈希表
(就像上一章中解释的那样)。按这种逻辑进一步来说,我们可以使用表达式
<literal>book.author.name</literal> 来读取到auther的name。</para>
<para>如果我们想指定同一个表达式的子变量,那么还有另外一种语法格式:
<literal>book["title"]</literal>。在方括号中可以给出任意长度字符串的表达式。
在上面这个数据模型示例中还可以这么来获取title: <literal>book[test]</literal>
下面这些示例它们含义都是相等的: <literal>book.author.name</literal>
<literal>book["author"].name</literal>
<literal>book.author.["name"]</literal>
<literal>book["author"]["name"]</literal></para>
<para>当使用点式语法时,顶层变量名的命名也有相同的限制
(命名时只能使用字母,数字,<literal>_</literal><literal>$</literal>
<literal>@</literal>,但是不能使用 <literal>0</literal>-<literal>9</literal>开头,
同时,从2.3.22版本开始,也可以使用 <literal>\-</literal><literal>\.</literal>
<literal>\:</literal>)。当使用方括号语法时,则没有这样的限制,
因为名称可以是任意表达式的结果。(请注意,对于FreeMarker的XML支持来说,
如果子变量名称是 <literal>*</literal> (星号)
或者 <literal>**</literal>,那么就不要使用方括号语法。)</para>
<para>对于顶层变量来说,如果尝试访问一个不存在的变量也会引起错误导致解析执行模板中断
(除非程序员事先配置过FreeMarker)。</para>
</section>
<section xml:id="dgui_template_exp_var_sequence">
<title>从序列中检索数据</title>
<indexterm>
<primary>subvariable</primary>
<secondary>accessing</secondary>
</indexterm>
<indexterm>
<primary>sequence</primary>
<secondary>accessing subvariable</secondary>
</indexterm>
<para>这和从哈希表中检索是相同的,但是只能使用方括号语法形式来进行,
而且方括号内的表达式最终必须是一个数字而不是字符串。比如,要从
<link linkend="example.stdDataModel">示例数据模型</link> 中获取第一个动物的名字
(记住第一项数字索引是0而不是1),可以这么来写: <literal>animals[0].name</literal></para>
</section>
<section xml:id="dgui_template_exp_var_special">
<title>特殊变量</title>
<indexterm>
<primary>special variables</primary>
</indexterm>
<para>特殊变量是由FreeMarker引擎本身定义的。
使用它们,可以按照如下语法形式来进行:
<literal>.<replaceable>variable_name</replaceable></literal>。.</para>
<para>通常情况下是不需使用特殊变量,而对专业用户来说可能用到。
所有特殊变量的说明可以参见 <link linkend="ref_specvar">参考手册</link></para>
</section>
</section>
<section xml:id="dgui_template_exp_stringop">
<title>字符串操作</title>
<indexterm>
<primary>string</primary>
<secondary>operations</secondary>
</indexterm>
<section xml:id="dgui_template_exp_stringop_interpolation">
<title>插值 (或连接)</title>
<indexterm>
<primary>interpolation</primary>
</indexterm>
<indexterm>
<primary>concatenate strings</primary>
</indexterm>
<indexterm>
<primary>joining strings</primary>
</indexterm>
<indexterm>
<primary>string</primary>
<secondary>concatenate</secondary>
</indexterm>
<indexterm>
<primary>string</primary>
<secondary>interpolation</secondary>
</indexterm>
<indexterm>
<primary>adding strings</primary>
</indexterm>
<para>如果要在字符串中插入表达式的值,可以在字符串的文字中使用
<literal>${<replaceable>...</replaceable>}</literal>
(已经废弃的 <literal>#{<replaceable>...</replaceable>}</literal>)。
<literal>${<replaceable>...</replaceable>}</literal> 在字符串中的作用和在
<link linkend="dgui_template_valueinsertion"> <phrase role="markedText">文本</phrase>
区是相同的</link> (它遵守相同的 <emphasis>本地化敏感</emphasis> 的数字和日期/时间格式),
而不是 <link linkend="ref.directive.escape">自动转义</link></para>
<para>示例 (假设user是 ''Big Joe''):</para>
<programlisting role="template">&lt;#assign s = "Hello ${user}!"&gt;
${s} &lt;#-- Just to see what the value of s is --&gt;
</programlisting>
<para>将会输出:</para>
<programlisting role="output">Hello Big Joe!</programlisting>
<warning>
<para>用户所犯的一个常见错误是将插值放在了不需要/不应该使用的地方。
插值 <emphasis></emphasis><link
linkend="dgui_template_overallstructure"><phrase
role="markedText">文本</phrase></link> 中有效。(比如,
<literal>&lt;h1&gt;Hello ${name}!&lt;/h1&gt;</literal>) 还有在字符串值中
(比如, <literal>&lt;#include "/footer/${company}.html"&gt;</literal>)。
典型的 <emphasis>错误</emphasis> 使用是
<literal>&lt;#if ${big}&gt;...&lt;/#if&gt;</literal>
这会导致语法错误。简单写为
<literal>&lt;#if big&gt;...&lt;/#if&gt;</literal>即可。
而且, <literal>&lt;#if "${big}"&gt;...&lt;/#if&gt;</literal>
也是 <emphasis>错误的</emphasis>
因为它将参数值转换为字符串,但是 <literal>if</literal> 指令只接受布尔值,
那么这将导致运行时错误。</para>
</warning>
<para>另外,也可以使用 <literal>+</literal> 号来达到类似的效果:</para>
<programlisting role="template">&lt;#assign s = "Hello " + user + "!"&gt;</programlisting>
<para>这样的效果和使用 <literal>${<replaceable>...</replaceable>}</literal> 是一样的。</para>
<warning>
<para>因为 <literal>+</literal> 和使用
<literal>${<replaceable>...</replaceable>}</literal>
的规则相同,附加的字符串受到 <literal>locale</literal>
<literal>number_format</literal><literal>date_format</literal>
<literal>time_format</literal><literal>datetime_format</literal>
<literal>boolean_format</literal> 等等设置的影响,
这是对人来说的,而不是通常机器的解析。默认情况下,这会导致数字出问题,
因为很多地区使用分组(千分位分隔符),那么
<literal>"someUrl?id=" + id</literal> 就可能会是
<literal>"someUrl?id=1 234"</literal>
要预防这种事情的发生,请使用 <literal>?c</literal>
(对计算机来说)内建函数,那么在 <literal>"someUrl?id=" + id?c</literal>
<literal>"someUrl?id=${id?c}"</literal>中,
就会得到如 <literal>"someUrl?id=1234"</literal>
这样的输出, 而不管本地化和格式的设置是什么。</para>
</warning>
</section>
<section xml:id="dgui_template_exp_get_character">
<title>获取字符</title>
<indexterm>
<primary>charAt</primary>
</indexterm>
<indexterm>
<primary>get character</primary>
</indexterm>
<para>在给定索引值时可以获取字符串中的一个字符,这和 <link
linkend="dgui_template_exp_var_sequence">序列的子变量</link>是相似的,
比如 <literal>user[0]</literal>。这个操作执行的结果是一个长度为1的字符串,
FTL并没有独立的字符类型。和序列中的子变量一样,这个索引也必须是数字,
范围是从0到字符串的长度,否则模板的执行将会发生错误并终止。</para>
<para>由于序列的子变量语法和字符的getter语法冲突,
那么只能在变量不是序列时使用字符的getter语法(因为FTL支持多类型值,所以它是可能的),
这种情况下使用序列方式就比较多。(为了变通,可以使用 <link
linkend="ref_builtin_string_for_string">
内建函数 <literal>string</literal> </link>,比如
<literal>user?string[0]</literal>。不必担心你不理解这是什么意思,
内建函数将会在后续章节中讨论。)</para>
<para>示例(假设 user 是 <quote>Big Joe</quote>):</para>
<programlisting role="template">${user[0]}
${user[4]}</programlisting>
<para>将会输出(请注意第一个字符的索引是0):</para>
<programlisting role="output">B
J</programlisting>
</section>
<section xml:id="dgui_template_exp_stringop_slice">
<title>字符串切分 (子串)</title>
<indexterm>
<primary>string slicing</primary>
</indexterm>
<indexterm>
<primary>substring</primary>
</indexterm>
<indexterm>
<primary>string</primary>
<secondary>slice</secondary>
</indexterm>
<indexterm>
<primary>string</primary>
<secondary>substring</secondary>
</indexterm>
<para>可以按照 <link
linkend="dgui_template_exp_seqenceop_slice">切分序列</link>
(请参看)的相同方式来切分字符串,这就是使用字符来代替序列。不同的是:</para>
<itemizedlist>
<listitem>
<para>降序域不允许进行字符串切分。
(因为不像序列那样,很少情况下会想反转字符串。
如果真要这样做了,那就是疏忽。)</para>
</listitem>
<listitem>
<para>如果变量的值既是字符串又是序列(多类型值),
那么切分将会对序列进行,而不是字符串。当处理XML时,
这样的值就是普通的了。此时,可以使用
<literal><replaceable>someXMLnode</replaceable>?string[<replaceable>range</replaceable>]</literal></para>
</listitem>
<listitem>
<para>一个遗留的bug:值域 <emphasis>包含</emphasis> 结尾时,
结尾小于开始索引并且是是非负的(就像在 <literal>"abc"[1..0]</literal> 中),
会返回空字符串而不是错误。(在降序域中这应该是个错误。)
现在这个bug已经向后兼容,但是不应该使用它,否在就会埋下一个错误。</para>
</listitem>
</itemizedlist>
<para>示例:</para>
<programlisting role="template">&lt;#assign s = "ABCDEF"&gt;
${s[2..3]}
${s[2..&lt;4]}
${s[2..*3]}
${s[2..*100]}
${s[2..]}</programlisting>
<para>将会输出:</para>
<programlisting role="output">CD
CD
CDE
CDEF
CDEF</programlisting>
<note>
<para>下面包括了一些使用内建函数来方便字符串切分的典型用例: <link
linkend="ref_builtin_remove_beginning"><literal>remove_beginning</literal></link>
<link
linkend="ref_builtin_remove_ending"><literal>remove_ending</literal></link>
<link
linkend="ref_builtin_keep_before"><literal>keep_before</literal></link>
<link
linkend="ref_builtin_keep_after"><literal>keep_after</literal></link>
<link
linkend="ref_builtin_keep_before_last"><literal>keep_before_last</literal></link>
<link
linkend="ref_builtin_keep_after_last"><literal>keep_after_last</literal></link></para>
</note>
</section>
</section>
<section xml:id="dgui_template_exp_sequenceop">
<title>序列操作</title>
<indexterm>
<primary>sequence</primary>
<secondary>operations</secondary>
</indexterm>
<section xml:id="dgui_template_exp_sequenceop_cat">
<title>连接</title>
<indexterm>
<primary>concatenate sequences</primary>
</indexterm>
<indexterm>
<primary>joining sequences</primary>
</indexterm>
<indexterm>
<primary>sequence</primary>
<secondary>concatenate</secondary>
</indexterm>
<indexterm>
<primary>adding sequences</primary>
</indexterm>
<para>序列的连接可以按照字符串那样使用 <literal>+</literal>
号来进行,例如:</para>
<programlisting role="template">&lt;#list ["Joe", "Fred"] + ["Julia", "Kate"] as user&gt;
- ${user}
&lt;/#list&gt;</programlisting>
<para>将会输出:</para>
<programlisting role="output">- Joe
- Fred
- Julia
- Kate
</programlisting>
<para>请注意,不要在很多重复连接时使用序列连接操作,
比如在循环中往序列上追加项目,而这样的使用是可以的:
<literal>&lt;#list users + admins as person&gt;</literal>
尽管序列连接的速度很快,而且速度是和被连接序列的大小相独立的,
但是最终的结果序列的读取却比原先的两个序列慢那么一点。
通过这种方式进行的许多重复连接最终产生的序列读取的速度会慢。</para>
</section>
<section xml:id="dgui_template_exp_seqenceop_slice">
<title>序列切分</title>
<indexterm>
<primary>sequence slice</primary>
</indexterm>
<indexterm>
<primary>sequence</primary>
<secondary>slice</secondary>
</indexterm>
<indexterm>
<primary>subsequence</primary>
</indexterm>
<para>使用
<literal><replaceable>seq</replaceable>[<replaceable>range</replaceable>]</literal>
这里 <literal><replaceable>range</replaceable></literal> 是一个值域
<link linkend="dgui_template_exp_direct_ranges">此处有说明</link>
就可以得到序列的一个切分。结果序列会包含原序列
(<literal><replaceable>seq</replaceable></literal>)中的项,
而且索引在值域中。例如:</para>
<programlisting role="template">&lt;#assert seq = ["A", "B", "C", "D", "E"]&gt;
&lt;#list seq[1..3] as i&gt;${i}&lt;/#list&gt;</programlisting>
<para>将会输出:</para>
<programlisting role="output">BCD </programlisting>
<para>此外,切分后序列中的项会和值域的顺序相同。
那么上面的示例中,如果值域是 <literal>3..1</literal> 将会输出
<literal>DCB</literal></para>
<para>值域中的数字必须是序列可使用的合法索引,
否则模板的处理将会终止并报错。像上面的示例那样,
<literal>seq[-1..0]</literal> 就会出错,
<literal>seq[-1]</literal> 就是合法的。
<literal>seq[1..5]</literal> 也不对,
因为 <literal>seq[5]</literal> 是非法的。
(请注意,尽管100已经越界,但是
<literal>seq[100..&lt;100]</literal>
<literal>seq[100..*0]</literal> 是合法的,因为那些值域都是空。)</para>
<para>限制长度的值域
(<literal><replaceable>start</replaceable>..*<replaceable>length</replaceable></literal>)
和无右边界值域 (<literal><replaceable>start</replaceable>..</literal>)
适用于切分后序列的长度。它们会切分可用项中尽可能多的部分:</para>
<programlisting role="template">&lt;#assign seq = ["A", "B", "C"]&gt;
Slicing with length limited ranges:
- &lt;#list seq[0..*2] as i&gt;${i}&lt;/#list&gt;
- &lt;#list seq[1..*2] as i&gt;${i}&lt;/#list&gt;
- &lt;#list seq[2..*2] as i&gt;${i}&lt;/#list&gt; &lt;#-- Not an error --&gt;
- &lt;#list seq[3..*2] as i&gt;${i}&lt;/#list&gt; &lt;#-- Not an error --&gt;
Slicing with right-unlimited ranges:
- &lt;#list seq[0..] as i&gt;${i}&lt;/#list&gt;
- &lt;#list seq[1..] as i&gt;${i}&lt;/#list&gt;
- &lt;#list seq[2..] as i&gt;${i}&lt;/#list&gt;
- &lt;#list seq[3..] as i&gt;${i}&lt;/#list&gt;</programlisting>
<para>将会输出:</para>
<programlisting role="output">Slicing with length limited ranges:
- AB
- BC
- C
-
Slicing with right-unlimited ranges:
- ABC
- BC
- C
-</programlisting>
<para>请注意,上面的有限长度切分和无右边界切分都允许开始索引超过最后项
<emphasis>一个</emphasis> (但不能再多了)。</para>
<note>
<para>要对序列进行给定大小的切分,就应该使用内建函数 <link
linkend="ref_builtin_chunk"><literal>chunk</literal></link></para>
</note>
</section>
</section>
<section xml:id="dgui_template_exp_hashop">
<title>哈希表操作</title>
<indexterm>
<primary>hash</primary>
<secondary>operations</secondary>
</indexterm>
<section xml:id="dgui_template_exp_hashop_cat">
<title>连接</title>
<indexterm>
<primary>concatenate hashes</primary>
</indexterm>
<indexterm>
<primary>joining hashes</primary>
</indexterm>
<indexterm>
<primary>hash</primary>
<secondary>concatenate</secondary>
</indexterm>
<indexterm>
<primary>adding hashes</primary>
</indexterm>
<para>像连接字符串那样,也可以使用 <literal>+</literal>
号的方式来连接哈希表。如果两个哈希表含有键相同的项,那么在
<literal>+</literal> 号右侧的哈希表中的项优先。例如:</para>
<programlisting role="template">&lt;#assign ages = {"Joe":23, "Fred":25} + {"Joe":30, "Julia":18}&gt;
- Joe is ${ages.Joe}
- Fred is ${ages.Fred}
- Julia is ${ages.Julia}
</programlisting>
<para>将会输出:</para>
<programlisting role="output">- Joe is 30
- Fred is 25
- Julia is 18</programlisting>
<para>请注意,很多项连接时不要使用哈希表连接,
比如在循环时往哈希表中添加新项。这和<link
linkend="dgui_template_exp_sequenceop_cat">序列连接</link>
的情况是一致的。</para>
</section>
</section>
<section xml:id="dgui_template_exp_arit">
<title>算数运算</title>
<indexterm>
<primary>arithmetic</primary>
</indexterm>
<indexterm>
<primary>math</primary>
</indexterm>
<indexterm>
<primary>addition</primary>
</indexterm>
<indexterm>
<primary>subtraction</primary>
</indexterm>
<indexterm>
<primary>division</primary>
</indexterm>
<indexterm>
<primary>modulus</primary>
</indexterm>
<para>算数运算包含基本的四则运算和求模运算,运算符有:</para>
<itemizedlist spacing="compact">
<listitem>
<para>加法: <literal>+</literal></para>
</listitem>
<listitem>
<para>减法: <literal>-</literal></para>
</listitem>
<listitem>
<para>乘法: <literal>*</literal></para>
</listitem>
<listitem>
<para>除法: <literal>/</literal></para>
</listitem>
<listitem>
<para>求模 (求余): <literal>%</literal></para>
</listitem>
</itemizedlist>
<remark>运算精度的问题?</remark>
<para>示例:</para>
<programlisting role="template">${100 - x * x}
${x / 2}
${12 % 10}</programlisting>
<para>假设 <literal>x</literal> 是 5,将会输出:</para>
<programlisting role="output">75
2.5
2</programlisting>
<para>要保证两个操作数都是结果为数字的表达式。
下面的这个例子在运行时,FreeMarker就会发生错误,
因为是字符串 <literal>"5"</literal> 而不是数字5:</para>
<programlisting role="template">${3 * "5"} &lt;#-- WRONG! --&gt;</programlisting>
<para>但这种情况也有一个例外,就是 <literal>+</literal> 号,它是用来 <link
linkend="dgui_template_exp_stringop_interpolation">连接字符串</link>的。
如果 <literal>+</literal> 号的一端是字符串,<literal>+</literal>
号的另外一端是数字,那么数字就会自动转换为字符串类型(使用当前页面语言的适当格式),
之后使用 <literal>+</literal> 号作为字符串连接操作符。示例如下:</para>
<programlisting role="template">${3 + "5"}</programlisting>
<para>将会输出:</para>
<programlisting role="output">35</programlisting>
<para>通常来说,FreeMarker不会自动将字符串转换为数字,反之会自动进行。</para>
<para><indexterm>
<primary>integer division</primary>
</indexterm><indexterm>
<primary>integer part</primary>
</indexterm>有时我们只想获取除法计算(或其它运算)的整数部分,
这可以使用内建函数 <literal>int</literal> 来解决。(关于内建函数
<link linkend="dgui_template_exp_builtin">后续章节</link>会来解释):</para>
<programlisting role="template">${(x/2)?int}
${1.1?int}
${1.999?int}
${-1.1?int}
${-1.999?int}</programlisting>
<para>假设 <literal>x</literal> 是 5,将会输出:</para>
<programlisting role="output">2
1
1
-1
-1</programlisting>
</section>
<section xml:id="dgui_template_exp_comparison">
<title>比较运算</title>
<indexterm>
<primary>comparison operators</primary>
</indexterm>
<para>有时我们需要知道两个值是否相等,或者哪个值更大一点。</para>
<para>为了演示具体的例子,我们在这里使用 <literal>if</literal> 指令。
<literal>if</literal> 指令的用法是: <literal>&lt;#if
<replaceable>expression</replaceable>&gt;...&lt;/#if&gt;</literal>
其中的表达式的值必须是布尔类型,否则将会出错,模板执行中断。
如果表达式的结果是 <literal>true</literal>
那么在开始和结束标记内的内容将会被执行,否则就会被跳过。</para>
<para>测试两个值相等使用 <literal>=</literal>
(或者采用Java和C语言中的 <literal>==</literal> ;二者是完全等同的。)
测试两个值不等使用 <literal>!=</literal>。比如,
假设 <literal>user</literal> 是 ''Big Joe'':</para>
<programlisting role="template">&lt;#if <emphasis>user == "Big Joe"</emphasis>&gt;
It is Big Joe
&lt;/#if&gt;
&lt;#if <emphasis>user != "Big Joe"</emphasis>&gt;
It is not Big Joe
&lt;/#if&gt;</programlisting>