FreeMarker 语法


Struts2 第十八课:FreeMarker 语法知识 FreeMarker 的模板文件并不比 HTML 页面复杂多少,FreeMarker 模板文件主要由如下 4 个 部分组成: 1,文本:直接输出的部分 2,注释:<#-- ... -->格式部分,不会输出 3,插值:即${...}或#{...}格式的部分,将使用数据模型中的部分替代输出 4,FTL 指令:FreeMarker 指定,和 HTML 标记类似,名字前加#予以区分,不会输出 下面是一个 FreeMarker 模板的例子,包含了以上所说的 4 个部分

Welcome!


<#-- 注释部分 -->
<#-- 下面使用插值 -->

Welcome ${user} !


We have these animals:

<#-- 使用 FTL 指令 --> <#list animals as being>

  • ${being.name} for ${being.price} Euros
    <#list>


    1, FTL 指令规则 在 FreeMarker 中,使用 FTL 标签来使用指令,FreeMarker 有 3 种 FTL 标签,这和 HTML 标签 是完全类似的. 1,开始标签:<#directivename parameter> 2,结束标签: 3,空标签:<#directivename parameter/> 实际上,使用标签时前面的符号#也可能变成@,如果该指令是一个用户指令而不是系统内建 指令时,应将#符号改成@符号. 使用 FTL 标签时,应该有正确的嵌套,而不是交叉使用,这和 XML标签的用法完全一样.如果全 用不存在的指令,FreeMarker 不会使用模板输出,而是产生一个错误消息.FreeMarker 会忽略 FTL 标签中的空白字符.值得注意的是< , /> 和指令之间不允许有空白字符. 2, 插值规则 FreeMarker 的插值有如下两种类型:1,通用插值${expr};2,数字格式化插值:#{expr}或 #{expr;format} 2.1 通用插值 对于通用插值,又可以分为以下 4 种情况: 1,插值结果为字符串值:直接输出表达式结果 2,插值结果为数字值:根据默认格式(由#setting 指令设置)将表达式结果转换成文本输出.可 以使用内建的字符串函数格式化单个插值,如下面的例子: <#settion number_format="currency"/> <#assign answer=42/> ${answer} ${answer?string} <#-- the same as ${answer} --> ${answer?string.number} ${answer?string.currency} ${answer?string.percent} ${answer} 输出结果是: $42.00 $42.00 42 $42.00 4,200% 3,插值结果为日期值:根据默认格式(由#setting 指令设置)将表达式结果转换成文本输出.可 以使用内建的字符串函数格式化单个插值,如下面的例子: ${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")} ${lastUpdated?string("EEE, MMM d, ''yy")} ${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")} 输出结果是: 2008-04-08 08:08:08 Pacific Daylight Time Tue, Apr 8, '03 Tuesday, April 08, 2003, 08:08:08 PM (PDT) 4,插值结果为布尔值:根据默认格式(由#setting 指令设置)将表达式结果转换成文本输出.可 以使用内建的字符串函数格式化单个插值,如下面的例子: <#assign foo=true/> ${foo?string("yes", "no")} 输出结果是: yes 2.2 数字格式化插值 数字格式化插值可采用#{expr;format}形式来格式化数字,其中 format 可以是: mX:小数部分最小 X 位 MX:小数部分最大 X 位 如下面的例子: <#assign x=2.582/> <#assign y=4/> #{x; M2} <#-- 输出 2.58 --> #{y; M2} <#-- 输出 4 --> #{x; m2} <#-- 输出 2.6 --> #{y; m2} <#-- 输出 4.0 --> #{x; m1M2} <#-- 输出 2.58 --> #{x; m1M2} <#-- 输出 4.0 --> 3, 表达式 表达式是 FreeMarker 模板的核心功能,表达式放置在插值语法${}之中时,表明需要输出表达 式的值;表达式语法也可与 FreeMarker 标签结合,用于控制输出.实际上 FreeMarker 的表达 式功能非常强大,它不仅支持直接指定值,输出变量值,也支持字符串格式化输出和集合访问 等功能. 3.1 直接指定值 使用直接指定值语法让 FreeMarker 直接输出插值中的值,而不是输出变量值.直接指定值可 以是字符串,数值,布尔值,集合和 MAP 对象. 1,字符串 直接指定字符串值使用单引号或双引号限定,如果字符串值中包含特殊字符需要转义,看下 面的例子: ${"我的文件保存在 C:\\盘"} ${'我名字是\"annlee\"'} 输出结果是: 我的文件保存在 C:\盘 我名字是"annlee" FreeMarker 支持如下转义字符: \";双引号(u0022) \';单引号(u0027) \\;反斜杠(u005C) \n;换行(u000A) \r;回车(u000D) \t;Tab(u0009) \b;退格键(u0008) \f;Form feed(u000C) \l;< \g;> \a;& \{;{ \xCode;直接通过 4 位的 16 进制数来指定 Unicode 码,输出该 unicode 码对应的字符. 如果某段文本中包含大量的特殊符号,FreeMarker 提供了另一种特殊格式:可以在指定字符 串内容的引号前增加 r 标记,在 r 标记后的文件将会直接输出.看如下代码: ${r"${foo}"} ${r"C:\foo\bar"} 输出结果是: ${foo} C:\foo\bar 2,数值 表达式中的数值直接输出,不需要引号.小数点使用"."分隔,不能使用分组","符号.FreeMarker 目前还不支持科学计数法,所以"1E3"是错误的.在 FreeMarker 表达式中使用数值需要注意以 下几点: 1,数值不能省略小数点前面的 0,所以".5"是错误的写法 2,数值 8 , +8 , 8.00 都是相同的 3,布尔值 直接使用 true 和 false,不使用引号. 4,集合 集合以方括号包括,各集合元素之间以英文逗号","分隔,看如下的例子: <#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as x> ${x} 输出结果是: 星期一 星期二 星期三 星期四 星期五 星期六 星期天 除此之外,集合元素也可以是表达式,例子如下: [2 + 2, [1, 2, 3, 4], "whatnot"] 还可以使用数字范围定义数字集合,如 2..5 等同于[2, 3, 4, 5],但是更有效率.注意,使用数字范 围来定义集合时无需使用方括号,数字范围也支持反递增的数字范围,如 5..2 5,Map 对象 Map 对象使用花括号包括,Map 中的 key-value 对之间以英文冒号":"分隔,多组 key-value 对 之间以英文逗号","分隔.下面是一个例子: {"语文":78, "数学":80} Map 对象的 key 和 value 都是表达式,但是 key 必须是字符串 3.2 输出变量值 FreeMarker 的表达式输出变量时,这些变量可以是顶层变量,也可以是 Map 对象中的变量, 还可以是集合中的变量,并可以使用点(.)语法来访问 Java 对象的属性.下面分别讨论这些情 况 1,顶层变量 所谓顶层变量就是直接放在数据模型中的值,例如有如下数据模型: Map root = new HashMap(); //创建数据模型 root.put("name","annlee"); //name 是一个顶层变量 对于顶层变量,直接使用${variableName}来输出变量值,变量名只能是字母,数字,下划 线,$,@和#的组合,且不能以数字开头号.为了输出上面的 name 的值,可以使用如下语法: ${name} 2,输出集合元素 如果需要输出集合元素,则可以根据集合元素的索引来输出集合元素,集合元素的索引以方 括号指定.假设有索引: ["星期一","星期二","星期三","星期四","星期五","星期六","星期天"].该索引名为 week,如果需 要输出星期三,则可以使用如下语法: ${week[2]} //输出第三个集合元素 此外,FreeMarker 还支持返回集合的子集合,如果需要返回集合的子集合,则可以使用如下语 法: week[3..5] //返回 week 集合的子集合,子集合中的元素是 week 集合中的第 4-6 个元素 3,输出 Map 元素 这里的 Map 对象可以是直接 HashMap 的实例,甚至包括 JavaBean 实例,对于 JavaBean 实 例而言,我们一样可以把其当成属性为 key,属性值为 value 的 Map 实例.为了输出 Map 元素 的值,可以使用点语法或方括号语法.假如有下面的数据模型: Map root = new HashMap(); Book book = new Book(); Author author = new Author(); author.setName("annlee"); author.setAddress("gz"); book.setName("struts2"); book.setAuthor(author); root.put("info","struts"); root.put("book", book); 为了访问数据模型中名为 struts2 的书的作者的名字,可以使用如下语法: book.author.name //全部使用点语法 book["author"].name book.author["name"] //混合使用点语法和方括号语法 book["author"]["name"] //全部使用方括号语法 使用点语法时,变量名字有顶层变量一样的限制,但方括号语法没有该限制,因为名字可以是 任意表达式的结果. 3.3 字符串操作 FreeMarker 的表达式对字符串操作非常灵活,可以将字符串常量和变量连接起来,也可以返 回字符串的子串等. 字符串连接有两种语法: 1,使用${..}或#{..}在字符串常量部分插入表达式的值,从而完成字符串连接. 2,直接使用连接运算符+来连接字符串 例如有如下数据模型: Map root = new HashMap(); root.put("user","annlee"); 下面将 user 变量和常量连接起来: ${"hello, ${user}!"} //使用第一种语法来连接 ${"hello, " + user + "!"} //使用+号来连接 上面的输出字符串都是 hello,annlee!,可以看出这两种语法的效果完全一样. 值得注意的是,${..}只能用于文本部分,不能用于表达式,下面的代码是错误的: <#if ${isBig}>Wow! <#if "${isBig}">Wow! 应该写成:<#if isBig>Wow! 截取子串可以根据字符串的索引来进行,截取子串时如果只指定了一个索引值,则用于取得 字符串中指定索引所对应的字符;如果指定两个索引值,则返回两个索引中间的字符串子串. 假如有如下数据模型: Map root = new HashMap(); root.put("book","struts2,freemarker"); 可以通过如下语法来截取子串: ${book[0]}${book[4]} //结果是 su ${book[1..4]} //结果是 tru 3.4 集合连接运算符 这里所说的集合运算符是将两个集合连接成一个新的集合,连接集合的运算符是+,看如下的 例子: <#list ["星期一","星期二","星期三"] + ["星期四","星期五","星期六","星期天"] as x> ${x} 输出结果是:星期一 星期二 星期三 星期四 星期五 星期六 星期天 3.5 Map 连接运算符 Map 对象的连接运算符也是将两个 Map 对象连接成一个新的 Map 对象,Map 对象的连接运 算符是+,如果两个 Map 对象具有相同的 key,则右边的值替代左边的值.看如下的例子: <#assign scores = {"语文":86,"数学":78} + {"数学":87,"Java":93}> 语文成绩是${scores.语文} 数学成绩是${scores.数学} Java 成绩是${scores.Java} 输出结果是: 语文成绩是 86 数学成绩是 87 Java 成绩是 93 3.6 算术运算符 FreeMarker 表达式中完全支持算术运算,FreeMarker 支持的算术运算符包括:+, - , * , / , % 看如下的代码: <#assign x=5> ${ x * x - 100 } ${ x /2 } ${ 12 %10 } 输出结果是: -75 2.5 2 在表达式中使用算术运算符时要注意以下几点: 1,运算符两边的运算数字必须是数字 2,使用+运算符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符串再连接, 如:${3 + "5"},结果是:35 使用内建的 int 函数可对数值取整,如: <#assign x=5> ${ (x/2)?int } ${ 1.1?int } ${ 1.999?int } ${ -1.1?int } ${ -1.999?int } 结果是:2 1 1 -1 -1 3.7 比较运算符 表达式中支持的比较运算符有如下几个: 1,=或者==:判断两个值是否相等. 2,!=:判断两个值是否不等. 3,>或者 gt:判断左边值是否大于右边值 4,>=或者 gte:判断左边值是否大于等于右边值 5,<或者 lt:判断左边值是否小于右边值 6,<=或者 lte:判断左边值是否小于等于右边值 注意:=和!=可以用于字符串,数值和日期来比较是否相等,但=和!=两边必须是相同类型的值, 否则会产生错误,而且 FreeMarker 是精确比较,"x","x ","X"是不等的.其它的运行符可以作用 于数字和日期,但不能作用于字符串,大部分的时候,使用 gt 等字母运算符代替>会有更好的效 果,因为 FreeMarker 会把>解释成 FTL 标签的结束字符,当然,也可以使用括号来避免这种情 况,如:<#if (x>y)> 3.8 逻辑运算符 逻辑运算符有如下几个: 逻辑与:&& 逻辑或:|| 逻辑非:! 逻辑运算符只能作用于布尔值,否则将产生错误 3.9 内建函数 FreeMarker 还提供了一些内建函数来转换输出,可以在任何变量后紧跟?,?后紧跟内建函数, 就可以通过内建函数来轮换输出变量.下面是常用的内建的字符串函数: html:对字符串进行 HTML 编码 cap_first:使字符串第一个字母大写 lower_case:将字符串转换成小写 upper_case:将字符串转换成大写 trim:去掉字符串前后的空白字符 下面是集合的常用内建函数 size:获取序列中元素的个数 下面是数字值的常用内建函数 int:取得数字的整数部分,结果带符号 例如: <#assign test="Tom & Jerry"> ${test?html} ${test?upper_case?html} 结果是:Tom & Jerry TOM & JERRY 3.10 空值处理运算符 FreeMarker 对空值的处理非常严格,FreeMarker 的变量必须有值,没有被赋值的变量就会抛 出异常,因为 FreeMarker 未赋值的变量强制出错可以杜绝很多潜在的错误,如缺失潜在的变 量命名,或者其他变量错误.这里所说的空值,实际上也包括那些并不存在的变量,对于一个 Java 的 null 值而言,我们认为这个变量是存在的,只是它的值为 null,但对于 FreeMarker 模板 而言,它无法理解 null 值,null 值和不存在的变量完全相同. 为了处理缺失变量,FreeMarker 提供了两个运算符: !:指定缺失变量的默认值 ??:判断某个变量是否存在 其中,!运算符的用法有如下两种: variable!或 variable!defaultValue,第一种用法不给缺失的变量指定默认值,表明默认值是空 字符串,长度为 0 的集合,或者长度为 0 的 Map 对象. 使用!指定默认值时,并不要求默认值的类型和变量类型相同.使用??运算符非常简单,它总是 返回一个布尔值,用法为:variable??,如果该变量存在,返回 true,否则返回 false 3.11 运算符的优先级 FreeMarker 中的运算符优先级如下(由高到低排列): 1,一元运算符:! 2,内建函数:? 3,乘除法:*, / , % 4,加减法:- , + 5,比较:> , < , >= , <= (lt , lte , gt , gte) 6,相等:== , = , != 7,逻辑与:&& 8,逻辑或:|| 9,数字范围:.. 实际上,我们在开发过程中应该使用括号来严格区分,这样的可读性好,出错少 4 FreeMarker 的常用指令 FreeMarker 的 FTL 指令也是模板的重要组成部分,这些指令可实现对数据模型所包含数据 的抚今迭代,分支控制.除此之外,还有一些重要的功能,也是通过 FTL 指令来实现的. 4.1 if 指令 这是一个典型的分支控制指令,该指令的作用完全类似于 Java 语言中的 if,if 指令的语法格式 如下: <#if condition>... <#elseif condition>... <#elseif condition>... <#else> ... 例子如下: <#assign age=23> <#if (age>60)>老年人 <#elseif (age>40)>中年人 <#elseif (age>20)>青年人 <#else> 少年人 输出结果是:青年人 上面的代码中的逻辑表达式用括号括起来主要是因为里面有>符号,由于 FreeMarker 会将> 符号当成标签的结束字符,可能导致程序出错,为了避免这种情况,我们应该在凡是出现这些 符号的地方都使用括号. 4.2 switch , case , default , break 指令 这些指令显然是分支指令,作用类似于 Java 的 switch 语句,switch 指令的语法结构如下: <#switch value> <#case refValue>...<#break> <#case refValue>...<#break> <#default>... 4.3 list, break 指令 list 指令是一个迭代输出指令,用于迭代输出数据模型中的集合,list 指令的语法格式如下: <#list sequence as item> ... 上面的语法格式中,sequence 就是一个集合对象,也可以是一个表达式,但该表达式将返回一 个集合对象,而 item 是一个任意的名字,就是被迭代输出的集合元素.此外,迭代集合对象时, 还包含两个特殊的循环变量: item_index:当前变量的索引值 item_has_next:是否存在下一个对象 也可以使用<#break>指令跳出迭代 例子如下: <#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as x> ${x_index + 1}.${x}<#if x_has_next>, <#if x="星期四"><#break> 4.4 include 指令 include 指令的作用类似于 JSP 的包含指令,用于包含指定页.include 指令的语法格式如下: <#include filename [options]> 在上面的语法格式中,两个参数的解释如下: filename:该参数指定被包含的模板文件 options:该参数可以省略,指定包含时的选项,包含 encoding 和 parse 两个选项,其中 encoding 指定包含页面时所用的解码集,而parse指定被包含文件是否作为 FTL文件来解析, 如果省略了 parse 选项值,则该选项默认是 true. 4.5 import 指令 该指令用于导入 FreeMarker 模板中的所有变量,并将该变量放置在指定的 Map 对象 中,import 指令的语法格式如下: <#import "/lib/common.ftl" as com> 上面的代码将导入/lib/common.ftl 模板文件中的所有变量,交将这些变量放置在一个名为 com 的 Map 对象中. 4.6 noparse 指令 noparse 指令指定 FreeMarker 不处理该指定里包含的内容,该指令的语法格式如下: <#noparse>... 看如下的例子: <#noparse> <#list books as book> ${book.name}作者:${book.author} 输出如下: <#list books as book> ${book.name}作者:${book.author} 4.7 escape , noescape 指令 escape 指令导致 body 区的插值都会被自动加上 escape 表达式,但不会影响字符串内的插 值,只会影响到 body 内出现的插值,使用 escape 指令的语法格式如下: <#escape identifier as expression>... <#noescape>... 看如下的代码: <#escape x as x?html> First name:${firstName} Last name:${lastName} Maiden name:${maidenName} 上面的代码等同于: First name:${firstName?html} Last name:${lastName?html} Maiden name:${maidenName?html} escape指令在解析模板时起作用而不是在运行时起作用,除此之外,escape指令也嵌套使用, 子 escape 继承父 escape 的规则,如下例子: <#escape x as x?html> Customer Name:${customerName} Items to ship; <#escape x as itemCodeToNameMap[x]> ${itemCode1} ${itemCode2} ${itemCode3} ${itemCode4} 上面的代码类似于: Customer Name:${customerName?html} Items to ship; ${itemCodeToNameMap[itemCode1]?html} ${itemCodeToNameMap[itemCode2]?html} ${itemCodeToNameMap[itemCode3]?html} ${itemCodeToNameMap[itemCode4]?html} 对于放在escape指令中所有的插值而言,这此插值将被自动加上escape表达式,如果需要指 定 escape 指令中某些插值无需添加 escape 表达式,则应该使用 noescape 指令,放在 noescape 指令中的插值将不会添加 escape 表达式. 4.8 assign 指令 assign 指令在前面已经使用了多次,它用于为该模板页面创建或替换一个顶层变量,assign 指令的用法有多种,包含创建或替换一个顶层变量,或者创建或替换多个变量等,它的最简单 的语法如下:<#assign name=value [in namespacehash]>,这个用法用于指定一个名为 name的变量,该变量的值为value,此外,FreeMarker 允许在使用assign指令里增加in子句,in 子句用于将创建的 name 变量放入 namespacehash 命名空间中. assign 指令还有如下用法:<#assign name1=value1 name2=value2 ... nameN=valueN [in namespacehash]>,这个语法可以同时创建或替换多个顶层变量,此外,还有一种复杂的用法, 如果需要创建或替换的变量值是一个复杂的表达式,则可以使用如下语法格式:<#assign name [in namespacehash]>capture this,在这个语法中,是指将 assign 指令的内 容赋值给 name 变量.如下例子: <#assign x> <#list ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期天"] as n> ${n} ${x} 上面的代码将产生如下输出:星期一 星期二 星期三 星期四 星期五 星期六 星期天 虽然 assign 指定了这种复杂变量值的用法,但是我们也不要滥用这种用法,如下例 子:<#assign x>Hello ${user}!,以上代码改为如下写法更合适:<#assign x="Hello ${user}!"> 4.9 setting 指令 该指令用于设置 FreeMarker 的运行环境,该指令的语法格式如下:<#setting name=value>, 在这个格式中,name 的取值范围包含如下几个: locale:该选项指定该模板所用的国家/语言选项 number_format:指定格式化输出数字的格式 boolean_format:指定两个布尔值的语法格式,默认值是 true,false date_format,time_format,datetime_format:指定格式化输出日期的格式 time_zone:设置格式化输出日期时所使用的时区 4.10 macro , nested , return 指令 macro 可以用于实现自定义指令,通过使用自定义指令,可以将一段模板片段定义成一个用户 指令,使用 macro 指令的语法格式如下: <#macro name param1 param2 ... paramN> ... <#nested loopvar1, loopvar2, ..., loopvarN> ... <#return> ... 在上面的格式片段中,包含了如下几个部分: name:name 属性指定的是该自定义指令的名字,使用自定义指令时可以传入多个参数 paramX:该属性就是指定使用自定义指令时报参数,使用该自定义指令时,必须为这些参数传 入值 nested 指令:nested 标签输出使用自定义指令时的中间部分 nested 指令中的循环变量:这此循环变量将由 macro 定义部分指定,传给使用标签的模板 return 指令:该指令可用于随时结束该自定义指令. 看如下的例子: <#macro book> //定义一个自定义指令 j2ee <@book /> //使用刚才定义的指令 上面的代码输出结果为:j2ee 在上面的代码中,可能很难看出自定义标签的用处,因为我们定义的 book 指令所包含的内容 非常简单,实际上,自定义标签可包含非常多的内容,从而可以实现更好的代码复用.此外,还可 以在定义自定义指令时,为自定义指令指定参数,看如下代码: <#macro book booklist> //定义一个自定义指令 booklist 是参数 <#list booklist as book> ${book} <@book booklist=["spring","j2ee"] /> //使用刚刚定义的指令 上面的代码为 book 指令传入了一个参数值,上面的代码的输出结果为:spring j2ee 不仅如此,还可以在自定义指令时使用 nested 指令来输出自定义指令的中间部分,看如下例 子: <#macro page title> FreeMarker 示例页面 - ${title?html}

    ${title?html}

    <#nested> //用于引入用户自定义指令的标签体 上面的代码将一个 HTML 页面模板定义成一个 page指令,则可以在其他页面中如此 page 指 令: <#import "/common.ftl" as com> //假设上面的模板页面名为 common.ftl,导入页面 <@com.page title="book list">
  • spring
  • j2ee
  • 从上面的例子可以看出,使用 macro 和 nested 指令可以非常容易地实现页面装饰效果,此外, 还可以在使用 nested 指令时,指定一个或多个循环变量,看如下代码: <#macro book> <#nested 1> //使用 book 指令时指定了一个循环变量值 <#nested 2> <@book ;x> ${x} .图书 当使用 nested 指令传入变量值时,在使用该自定义指令时,就需要使用一个占位符(如 book 指令后的;x).上面的代码输出文本如下: 1 .图书 2 .图书 在 nested 指令中使用循环变量时,可以使用多个循环变量,看如下代码: <#macro repeat count> <#list 1..count as x> //使用 nested 指令时指定了三个循环变量 <#nested x, x/2, x==count> <@repeat count=4 ; c halfc last> ${c}. ${halfc}<#if last> Last! 上面的输出结果为: 1. 0.5 2. 1 3. 1.5 4. 2 Last; return 指令用于结束 macro 指令,一旦在 macro 指令中执行了 return 指令,则 FreeMarker 不 会继续处理 macro 指令里的内容,看如下代码: <#macro book> spring <#return> j2ee <@book /> 上面的代码输出:spring,而 j2ee 位于 return 指令之后,不会输出. 来自: http://hi.baidu.com/annleecn/blog/item/331804b2d84f20a1d9335ad8.html 快速入门 (1)模板 + 数据模型 = 输出 FreeMarker 基于设计者和程序员是具有不同专业技能的不同个体的观念他们是 分工劳动的:设计者专注于表示——创建 HTML 文件、图片、Web 页面的其它可 视化方面;程序员创建系统,生成设计页面要显示的数据。经 常 会 遇 到 的 问 题 是 : 在 Web 页面(或其它类型的文档)中显示的信息在设计页面时是无效的,是基于 动态数据的。在这里,你可以在 HTML(或其它要输出的文本)中加入一些特定 指令,FreeMarker 会在输出页面给最终用户时,用适当的数据替代这些代码。 先来解释一下 freemaker 的基本语法了, <# ... > 中存放所有 freemaker 的内容,之外的内容全部原样输出。 <@ ... /> 是函数调用两个定界符内的 内容中,第一个符号表示指令或者函数名,其后的跟随参数。freemaker 提供 的控制包括如下: <#if condition><#elseif condition><#else> 条 件判断 <#list hash_or_seq as var> 遍历 hash 表或者 collection (freemaker 称作 sequence)的成员 <#macro name param1 param2 ... ><#nested param> 宏,无返回参数 <#function name param1 param2><#return val>函数,有返回参数 var?member_function(...) 用函数对 var 进行转换,freemaker 称为 build-ins。实际内部实现类似 member_function(var, ...) stringA[M .. N] 取子字符串,类似 substring(stringA, M, N) {key:value, key2:value2 ...} 直接定义一个 hash 表 [item0, item1, item2 ...] 直接定义一个序列 hash0[key0] 存取 hash 表中 key 对应的 元素 seq0[5] 存取序列指定下标的元素 <@function1 param0 param1 ... /> 调用函数 function1 <@macro0 param0 param1 ; nest_param0 nest_param1 ...> nest_body 调用宏,并处理宏的嵌套 <#assign var = value > 定义变量并初始化 <#local var = value> 在 macro 或者 function 中定义局部变量并初始化 <#global var = value > 定义全局变量并初始化 ${var} 输出并替换为表达式的值 <#visit xmlnode> 调用 macro 匹配 xmlnode 本身及其子节点 <#recurse xmlnode> 调用 macro 匹配 xmlnode 的子节点 下面是一个例子: Welcome!

    Welcome ${user}!

    Our latest product: ${latestProduct.name}! 这个例子是在简单的 HTML 中加入了一些由${…}包围的特定代码,这些特定代码 是FreeMarker 的指令,而包含FreeMarker 的指令的文件就称为模板(Template)。 至 于 user、latestProduct.url 和 latestProduct.name 来自于数据模型(data model)。数据模型由程序员编程来创建,向模板提供变化的信息,这些信息来 自于数据库、文件,甚至于在程序中直接生成。模板设计者不关心数据从那儿来, 只知道使用已经建立的数据模型。 下面是一个可能的数据模型: (root) | +- user = "Big Joe" | +- latestProduct | +- url = "products/greenmouse.html" | +- name = "green mouse" 数据模型类似于计算机的文件系统,latestProduct 可以看作是目录。 2、数据模型 (1)基础 在快速入门中介绍了在模板中使用的三种基本对象类型:scalars、hashes 和 sequences,其实还可以有其它更多的能力: · scalars:存储单值 · hashes:充当其它对象的容器,每个都关联一个唯一的查询名字 · sequences:充当其它对象的容器,按次序访问 · 方法:通过传递的参数进行计算,以新对象返回结果 · 用户自定义 FTL 标记:宏和变换器 通常每个变量只具有上述的一种能力,但一个变量可以具有多个上述能力,如下 面的例子: (root) | +- mouse = "Yerri" | +- age = 12 | +- color = "brown"> mouse 既是 scalars 又是 hashes,将上面的数据模型合并到下面的模板: ${mouse} <#-- use mouse as scalar --> ${mouse.age} <#-- use mouse as hash --> ${mouse.color} <#-- use mouse as hash --> 输出结果是: Yerri 12 brown (2)Scalar 变量 Scalar 变量存储单值,可以是: · 字符串:简单文本,在模板中使用引号(单引号或双引号)括起 · 数字:在模板中直接使用数字值 · 日期:存储日期/时间相关的数据,可以是日期、时间或日期-时间 (Timestamp);通常情况,日期值由程序员加到数据模型中,设计者只 需要显示它们 · 布尔值:true 或 false,通常在<#if …>标记中使用 (3)hashes 、sequences 和集合 有些变量不包含任何可显示的内容,而是作为容器包含其它变量,者有两种类型: · hashes:具有一个唯一的查询名字和它包含的每个变量相关联 · sequences:使用数字和它包含的每个变量相关联,索引值从 0 开始 集合变量通常类似 sequences,除非无法访问它的大小和不能使用索引来获得它 的子变量;集合可以看作只能由<#list …>指令使用的受限 sequences (4)方法 方法变量通常是基于给出的参数计算值。 下面的例子假设程序员已经将方法变量 avg 放到数据模型中,用来计算数字平均 值: 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 python and elephant is: ${avg(animals.python.price, animals.elephant.price)} (5)宏和变换器 宏和变换器变量是用户自定义指令(自定义 FTL 标记),会在后面讲述这些高级 特性 (6)节点 节点变量表示为树型结构中的一个节点,通常在 XML 处理中使用,会在后面的专 门章节中讲 3、模板 (1)整体结构 模板使用 FTL(FreeMarker 模板语言)编写,是下面各部分的一个组合: · 文本:直接输出 · Interpolation:由${和},或#{和}来限定,计算值替代输出 · FTL 标记:FreeMarker 指令,和 HTML 标记类似,名字前加#予以区分,不 会输出 · 注释:由<#--和-->限定,不会输出 下面是以一个具体模板例子: Welcome! <#-- Greet the user with his/her name -->

    Welcome ${user}!

    We have these animals:

      <#list animals as being>
    • ${being.name} for ${being.price} Euros
    注意事项: · FTL 区分大小写,所以 list 是正确的 FTL 指令,而 List 不是;${name} 和${NAME}是不同的 · Interpolation 只能在文本中使用 · FTL 标记不能位于另一个 FTL 标记内部,例如: <#if <#include 'foo'>='bar'>... · 注释可以位于 FTL 标记和 Interpolation 内部,如下面的例子:

    Welcome ${user <#-- The name of user -->}!

    We have these animals:

      <#list <#-- some comment... --> animals as <#-- again... --> being> ... · 余的空白字符会在模板输出时移除 (2)指令 在 FreeMarker 中,使用 FTL 标记引用指令。有三种 FTL 标记,这和 HTML 标记是 类似的: · 开始标记:<#directivename parameters> · 结束标记: · 空内容指令标记:<#directivename parameters/> 有两种类型的指令:预定义指令和用户定义指令。 用户定义指令要使用@替换#,如<@mydirective>...(会在后面 讲述)。 FTL 标记不能够交叉,而应该正确的嵌套,如下面的代码是错误的:
        <#list animals as being>
      • ${being.name} for ${being.price} Euros <#if use = "Big Joe"> (except for you) <#-- WRONG! -->
      如果使用不存在的指令,FreeMarker 不会使用模板输出,而是产生一个错误消 息。 FreeMarker 会忽略 FTL 标记中的空白字符,如下面的例子: <#list animals as being > ${being.name} for ${being.price} Euros 但是,<、 \a & \{ { \xCode 4 位 16 进制 Unicode 代码 有一类特殊的字符串称为 raw 字符串,被认为是纯文本,其中的\和{等不具有特 殊含义,该类字符串在引号前面加 r,下面是一个例子: ${r"${foo}"} ${r"C:\foo\bar"} 输出的结果是: ${foo} C:\foo\bar · 数字 直接输入,不需要引号 精度数字使用“.”分隔,不能使用分组符号 目前版本不支持科学计数法,所以“1E3”是错误的 不能省略小数点前面的 0,所以“.5”是错误的 数字 8、+8、08 和 8.00 都是相同的 · 布尔值 true 和 false,不使用引号 · 序列 由逗号分隔的子变量列表,由方括号限定,下面是一个例子: <#list ["winter", "spring", "summer", "autumn"] as x> ${x} 输出的结果是: winter spring summer autumn 列表的项目是表达式,所以可以有下面的例子: [2 + 2, [1, 2, 3, 4], "whatnot"] 可以使用数字范围定义数字序列,例如 2..5 等同于[2, 3, 4, 5],但是更有效 率,注意数字范围没有方括号 可以定义反递增的数字范围,如 5..2 · 散列(hash) 由逗号分隔的键/值列表,由大括号限定,键和值之间用冒号分隔,下面是一个 例子: {"name":"green mouse", "price":150} 键和值都是表达式,但是键必须是字符串 获取变量 · 顶层变量: ${variable},变量名只能是字母、数字、下划线、$、@和# 的组合,且不能以数字开头 · 从散列中获取数据 可以使用点语法或方括号语法,假设有下面的数据模型: (root) | +- book | | | +- title = "Breeding green mouses" | | | +- author | | | +- name = "Julia Smith" | | | +- info = "Biologist, 1923-1985, Canada" | +- test = "title" 下面都是等价的: book.author.name book["author"].name book.author.["name"] book["author"]["name"] 使用点语法,变量名字有顶层变量一样的限制,但方括号语法没有该限制,因为 名字是任意表达式的结果 · 从序列获得数据:和散列的方括号语法语法一样,只是方括号中的表达式 值必须是数字;注意:第一个项目的索引是 0 序列片断:使用[startIndex..endIndex]语法,从序列中获得序列片断(也是序 列);startIndex 和 endIndex 是结果为数字的表达式 · 特殊变量:FreeMarker 内定义变量,使用.variablename 语法访问 字符串操作 · Interpolation(或连接操作) 可以使用${..}(或#{..})在文本部分插入表达式的值,例如: ${"Hello ${user}!"} ${"${user}${user}${user}${user}"} 可以使用+操作符获得同样的结果 ${"Hello " + user + "!"} ${user + user + user + user} ${..}只能用于文本部分,下面的代码是错误的: <#if ${isBig}>Wow! <#if "${isBig}">Wow! 应该写成: <#if isBig>Wow! · 子串 例子(假设 user 的值为“Big Joe”): ${user[0]}${user[4]} ${user[1..4]} 结果是(注意第一个字符的索引是 0): BJ ig J 序列操作 · 连接操作:和字符串一样,使用+,下面是一个例子: <#list ["Joe", "Fred"] + ["Julia", "Kate"] as user> - ${user} 输出结果是: - Joe - Fred - Julia - Kate 散列操作 · 连接操作:和字符串一样,使用+,如果具有相同的 key,右边的值替代 左边的值,例如: <#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 算术运算 · +、-、×、/、%,下面是一个例子: ${x * x - 100} ${x / 2} ${12 % 10} 输出结果是(假设 x 为 5): -75 2.5 2 操作符两边必须是数字,因此下面的代码是错误的: ${3 * "5"} <#-- WRONG! --> 使用+操作符时,如果一边是数字,一边是字符串,就会自动将数字转换为字符 串,例如: ${3 + "5"} 输出结果是: 35 使用内建的 int(后面讲述)获得整数部分,例如: ${(x/2)?int} ${1.1?int} ${1.999?int} ${-1.1?int} ${-1.999?int} 输出结果是(假设 x 为 5): 2 1 1 -1 -1 · 比较操作符 使用=(或==,完全相等)测试两个值是否相等,使用!= 测试两个值是否不相等 =和!=两边必须是相同类型的值,否则会产生错误,例如<#if 1 = "1">会引起错 误 Freemarker 是精确比较,所以对"x"、"x "和"X"是不相等的 对数字和日期可以使用<、<=、>和>=,但不能用于字符串 由于 Freemarker 会将>解释成 FTL 标记的结束字符,所以对于>和>=可以使用括 号来避免这种情况,例如<#if (x > y)> 另一种替代的方法是,使用 lt、lte、gt 和 gte 来替代<、<=、>和>= · 逻辑操作符 &&(and)、||(or)、!(not),只能用于布尔值,否则会产生错误 例子: <#if x < 12 && color = "green"> We have less than 12 things, and they are green. <#if !hot> <#-- here hot must be a boolean --> It's not hot. · 内建函数 内建函数的用法类似访问散列的子变量,只是使用“?”替代“.”,下面列出常 用的一些函数 · o 字符串使用的: html:对字符串进行 HTML 编码 cap_first:使字符串第一个字母大写 lower_case:将字符串转换成小写 upper_case:将字符串转换成大写 trim:去掉字符串前后的空白字符 · o 序列使用的: size:获得序列中元素的数目 · o 数字使用的: int:取得数字的整数部分(如-1.9?int 的结果是-1) 例子(假设 test 保存字符串"Tom & Jerry"): ${test?html} ${test?upper_case?html} 输出结果是: Tom & Jerry TOM & JERRY · 操作符优先顺序 操作符组 操作符 后缀 [subvarName] [subStringRange] . (methodParams) 一元 +expr、-expr、! 内建 ? 乘法 *、 / 、% 加法 +、- 关系 <、>、<=、>=(lt、lte、gt、gte) 相等 ==(=)、!= 逻辑 and && 逻辑 or 双竖线 数字范围 .. (4)Interpolation Interpolation 有两种类型: 1. 通用 Interpolation:${expr} 1. 数字 Interpolation:#{expr}或#{expr; format} 注意:Interpolation 只能用于文本部分 · 通用 Interpolation 插入字符串值:直接输出表达式结果 插入数字值:根据缺省格式(由#setting 指令设置)将表达式结果转换成文本 输出;可以使用内建函数 string 格式化单个 Interpolation,下面是一个例子: <#setting number_format="currency"/> <#assign answer=42/> ${answer} ${answer?string} <#-- the same as ${answer} --> ${answer?string.number} ${answer?string.currency} ${answer?string.percent} 输出结果是: $42.00 $42.00 42 $42.00 4,200% 插入日期值:根据缺省格式(由#setting 指令设置)将表达式结果转换成文本 输出;可以使用内建函数 string 格式化单个 Interpolation,下面是一个使用 格式模式的例子: ${lastUpdated?string("yyyy-MM-dd HH:mm:ss zzzz")} ${lastUpdated?string("EEE, MMM d, ''yy")} ${lastUpdated?string("EEEE, MMMM dd, yyyy, hh:mm:ss a '('zzz')'")} 输出的结果类似下面的格式: 2003-04-08 21:24:44 Pacific Daylight Time Tue, Apr 8, '03 Tuesday, April 08, 2003, 09:24:44 PM (PDT) 插入布尔值:根据缺省格式(由#setting 指令设置)将表达式结果转换成文本 输出;可以使用内建函数 string 格式化单个 Interpolation,下面是一个例子: <#assign foo=true/> ${foo?string("yes", "no")} 输出结果是: yes · 数字 Interpolation 的#{expr; format}形式可以用来格式化数字,format 可以是: mX:小数部分最小 X 位 MX:小数部分最大 X 位 例子: <#-- If the language is US English the output is: --> <#assign x=2.582/> <#assign y=4/> #{x; M2} <#-- 2.58 --> #{y; M2} <#-- 4 --> #{x; m1} <#-- 2.6 --> #{y; m1} <#-- 4.0 --> #{x; m1M2} <#-- 2.58 --> #{y; m1M2} <#-- 4.0 --> 4、杂项 (1)用户定义指令 宏和变换器变量是两种不同类型的用户定义指令,它们之间的区别是宏是在模板 中使用 macro 指令定义,而变换器是在模板外由程序定义,这里只介绍宏 · 基本用法 宏是和某个变量关联的模板片断,以便在模板中通过用户定义指令使用该变量, 下面是一个例子: <#macro greet> Hello Joe! 作为用户定义指令使用宏变量时,使用@替代 FTL 标记中的# <@greet> 如果没有体内容,也可以使用: <@greet/> · 参数 在 macro 指令中可以在宏变量之后定义参数,如: <#macro greet person> Hello ${person}! 可以这样使用这个宏变量: <@greet person="Fred"/> and <@greet person="Batman"/> 输出结果是: Hello Fred! and Hello Batman! 宏的参数是 FTL 表达式,所以下面的代码具有不同的意思: <@greet person=Fred/> 这意味着将 Fred 变量的值传给 person 参数,该值不仅是字符串,还可以是其它 类型,甚至是复杂的表达式 可以有多参数,下面是一个例子: <#macro greet person color> Hello ${person}! 可以这样使用该宏变量: <@greet person="Fred" color="black"/> 其中参数的次序是无关的,因此下面是等价的: <@greet color="black" person="Fred"/> 只能使用在 macro 指令中定义的参数,并且对所有参数赋值,所以下面的代码是 错误的: <@greet person="Fred" color="black" background="green"/> <@greet person="Fred"/> 可以在定义参数时指定缺省值,如: <#macro greet person color="black"> Hello ${person}! 这样<@greet person="Fred"/>就正确了 宏的参数是局部变量,只能在宏定义中有效 · 嵌套内容 用户定义指令可以有嵌套内容,使用<#nested>指令执行指令开始和结束标记之 间的模板片断 例子: <#macro border>
      <#nested>
      这样使用该宏变量: <@border>The bordered text 输出结果:
      The bordered text
      <#nested>指令可以被多次调用,例如: <#macro do_thrice> <#nested> <#nested> <#nested> <@do_thrice> Anything. 输出结果: Anything. Anything. Anything. 嵌套内容可以是有效的 FTL,下面是一个有些复杂的例子: <@border>
        <@do_thrice>
      • <@greet person="Joe"/>
      }}} 输出结果: EG.一个对象 BOOK 1.输出 ${book.name} 空值判断:${book.name?if_exists }, ${book.name?default(‘xxx’)}//默认值 xxx ${ book.name!"xxx"}//默认值 xxx 日期格式:${book.date?string('yyyy-MM-dd')} 数字格式:${book?string.number}--20 ${book?string.currency}--<#-- $20.00 --> ${book?string.percent}—<#-- 20% --> 插入布尔值: <#assign foo=ture /> ${foo?string("yes","no")} <#-- yes --> 2.逻辑判断 a: <#if condition>... <#elseif condition2>... <#elseif condition3>...... <#else>... 其中空值判断可以写成<#if book.name?? > b: <#switch value> <#case refValue1> ... <#break> <#case refValue2> ... <#break> ... <#case refValueN> ... <#break> <#default> ... 3.循环读取 <#list sequence as item> ... 空值判断<#if bookList?size = 0> e.g. <#list employees as e> ${e_index}. ${e.name} 输出: 1. Readonly 2. Robbin freemarker 的 Eclipse 插件 · If you use Eclipse 2.x: 1. Open the Window menu, then Open Perspective -> Install/Update 2. Click with the right mouse button on the Feature Updates view, then select New -> Site Bookmark 3. In the displayed dialog box, type "FreeMarker" for Name and "http://www.freemarker.org/eclipse/update" for URL. Leave the "Bookmark type" radio buttons on "Eclipse update site". 4. Click Finish 5. Open the tree node under the newly created update site named "FreeMarker", select the "FreeMarker X.Y.Z" feature, and install it using the Install now button in the preview pane. · If you use Eclipse 3.x: 1. Help -> Software updates -> Find and install.... 2. Choose "Search for new features to install". 3. Click Add Update Site..., and type "FreeMarker" for Name and "http://www.freemarker.org/eclipse/update" for URL. 4. Check the box of the "FreeMarker" feature. 5. "Next"-s until it is installed...
      • Hello Joe!
      • Hello Joe!
      • Hello Joe!
      宏定义中的局部变量对嵌套内容是不可见的,例如: <#macro repeat count> <#local y = "test"> <#list 1..count as x> ${y} ${count}/${x}: <#nested> <@repeat count=3>${y?default("?")} ${x?default("?")} ${count?default("?")} 输出结果: test 3/1: ? ? ? test 3/2: ? ? ? test 3/3: ? ? ? · 在宏定义中使用循环变量 用户定义指令可以有循环变量,通常用于重复嵌套内容,基本用法是:作为 nested 指令的参数传递循环变量的实际值,而在调用用户定义指令时,在<@…> 开始标记的参数后面指定循环变量的名字 例子: <#macro repeat count> <#list 1..count as x> <#nested x, x/2, x==count> <@repeat count=4 ; c, halfc, last> ${c}. ${halfc}<#if last> Last! 输出结果: 1. 0.5 2. 1 3. 1.5 4. 2 Last! 指定的循环变量的数目和用户定义指令开始标记指定的不同不会有问题 调用时少指定循环变量,则多指定的值不可见 调用时多指定循环变量,多余的循环变量不会被创建 (2)在模板中定义变量 在模板中定义的变量有三种类型: · plain 变量:可以在模板的任何地方访问,包括使用 include 指令插入的 模板,使用 assign 指令创建和替换 · 局部变量:在宏定义体中有效,使用 local 指令创建和替换 · 循环变量:只能存在于指令的嵌套内容,由指令(如 list)自动创建 宏的参数是局部变量,而不是循环变量;局部变量隐藏(而不是覆盖)同名的 plain 变量;循环变量隐藏同名的局部变量和 plain 变量,下面是一个例子: <#assign x = "plain"> 1. ${x} <#-- we see the plain var. here --> <@test/> 6. ${x} <#-- the value of plain var. was not changed --> <#list ["loop"] as x> 7. ${x} <#-- now the loop var. hides the plain var. --> <#assign x = "plain2"> <#-- replace the plain var, hiding does not mater here --> 8. ${x} <#-- it still hides the plain var. --> 9. ${x} <#-- the new value of plain var. --> <#macro test> 2. ${x} <#-- we still see the plain var. here --> <#local x = "local"> 3. ${x} <#-- now the local var. hides it --> <#list ["loop"] as x> 4. ${x} <#-- now the loop var. hides the local var. --> 5. ${x} <#-- now we see the local var. again --> 输出结果: 1. plain 2. plain 3. local 4. loop 5. local 6. plain 7. loop 8. loop 9. plain2 内部循环变量隐藏同名的外部循环变量,如: <#list ["loop 1"] as x> ${x} <#list ["loop 2"] as x> ${x} <#list ["loop 3"] as x> ${x} ${x} ${x} 输出结果: loop 1 loop 2 loop 3 loop 2 loop 1 模板中的变量会隐藏(而不是覆盖)数据模型中同名变量,如果需要访问数据模 型中的同名变量,使用特殊变量 global,下面的例子假设数据模型中的 user 的 值是 Big Joe: <#assign user = "Joe Hider"> ${user} <#-- prints: Joe Hider --> ${.globals.user} <#-- prints: Big Joe --> (3)名字空间 通常情况,只使用一个名字空间,称为主名字空间 为了创建可重用的宏、变换器或其它变量的集合(通常称库),必须使用多名字 空间,其目的是防止同名冲突 · 创建库 下面是一个创建库的例子(假设保存在 lib/my_test.ftl 中): <#macro copyright date>

      Copyright (C) ${date} Julia Smith. All rights reserved.
      Email: ${mail}

      <#assign mail = "jsmith@acme.com"> 使用 import 指令导入库到模板中,Freemarker 会为导入的库创建新的名字空间, 并可以通过 import 指令中指定的散列变量访问库中的变量: <#import "/lib/my_test.ftl" as my> <#assign mail="fred@acme.com"> <@my.copyright date="1999-2002"/> ${my.mail} ${mail} 输出结果:

      Copyright (C) 1999-2002 Julia Smith. All rights reserved.
      Email: jsmith@acme.com

      jsmith@acme.com fred@acme.com 可以看到例子中使用的两个同名变量并没有冲突,因为它们位于不同的名字空间 可以使用 assign 指令在导入的名字空间中创建或替代变量,下面是一个例子: <#import "/lib/my_test.ftl" as my> ${my.mail} <#assign mail="jsmith@other.com" in my> ${my.mail} 输出结果: jsmith@acme.com jsmith@other.com 数据模型中的变量任何地方都可见,也包括不同的名字空间,下面是修改的库: <#macro copyright date>

      Copyright (C) ${date} ${user}. All rights reserved.

      <#assign mail = "${user}@acme.com"> 假设数据模型中的 user 变量的值是 Fred,则下面的代码: <#import "/lib/my_test.ftl" as my> <@my.copyright date="1999-2002"/> ${my.mail} 输出结果:

      Copyright (C) 1999-2002 Fred. All rights reserved.

      Fred@acme.com 补充(静态方法的调用): 方法 1: ##定义配置文件 freemarkerstatic.properties _Validator=com.longyou.util.Validator _Functions=com.longyou.util.Functions _EscapeUtils=com.longyou.util.EscapeUtils /调用代码 ${_Functions.toUpperCase("Hello")}
      ${_EscapeUtils.escape("狼的原野")} 方法 2: ${stack.findValue("@package.ClassName@method")}
    还剩39页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 6 金币 [ 分享pdf获得金币 ] 2 人已下载

    下载pdf

    pdf贡献者

    randolph

    贡献于2013-03-22

    下载需要 6 金币 [金币充值 ]
    亲,您也可以通过 分享原创pdf 来获得金币奖励!
    下载pdf