本来是没有自己写一个模板引擎的计划的,因为按我的理解,一直认识这种“语言”级的引擎,难度是非常大的。总感觉自己的水平不够,因此不敢有这个念头。直到大量使用Velocty的时候,碰到velocty诸多尽如人意的地方,但是又无能为力,退回到JSP吧,又心不有甘。于是就期望着寻找一种语法结构接近velocty,但是又没有Velocity这些不方便之处的模板语言。于是进到一个模板语言群,一群大佬们个个至少是一个模板语言的作者,于是作者在里面表达了自己的期望,大佬们都介绍了自己的模板引擎,于是作者一个接一个的看源码,看文档。说实际,看文档,感觉都非常不错,都有自己的特色,看语法也都不错,除了一部分自己特别关注的点没有之外,其部分都非常不错了。但是距离自己的诉求还是有差距,怎么办呢?于是就准备找一个最接近的模板引擎来进行一定的扩展,挑来挑去就挑中了jetbrick这个模板语言(在此对Jetbrick致以强烈的衷心的感谢!!)。 之所以挑这个呢,是因为以下几个原因:
于是下载了源码,初端详还是不错的,但是在实现在几点和笔记诉求有差距: 第一我希望是弱类型,jetbrick是强类型,性能是有提高但是开发过程会比较不方便。 另外由于jetbrick作者期望在编译器进行强类型推测,因此导致运行期的内容与编译期的内容有比较强的耦合。 另外有一些语言特性,属于个人爱好上的原因,也有一些差异,因此就决定自己编写一个。 |
语法规范
为了更好的说明Tiny的语法规范,因此全程对比Velocity
说明,<>中的内容表示必须有至少一个,[]中的内容表示可以有可以没有。
References
表达式
${表达式}
表达式是指最后的运算结果是一个值,表达式中可以使用变量
=>与Velocity区别,这里大括号必须有,不能省略,“-”号不允许出现
${a+b-c+d}
${"abc"+1}
${user.name}
${user.items[1].count+3}
${user.func(1,2,3,4)+map.def+map["aa"]}
变量
<a->[_a-zA-z0-9]
=>与Velocity区别,“-”号不允许出现
示例:
合法
abc
ab_c
Ab9_
非法
9bc
_bc
a-b
属性值
属性语法与变量名一样
区别大括号必须有,“-”号不可以有
属性值实际上也是个表达式,因此这么写也是可以的
${a.("aa"+bb)},如果bb的值为3,则等同于${a.aa3},等同于a."aa3"
指令
#set - 赋值指令
Format:
#set ( ref = [ ", ' ]arg[ ", ' ] )
示例
#{set}不被支持
变量名前面不可以加$
#set(aa=1)
#set(aa=2,b=2,c=aa+2,d=func("info"))
#set(aa=[aa,"bb",2,3,4]
#set(aa={"aa":1,bb:2,aa.bb.func()+3:5}
#if/#elseif/#else - 判断语句
格式:
#if( [condition] ) [output] [ #elseif( [condition] ) [output] ]* [ # [ { ] else [ } ] [output] ] # [ { ] end [ } ]
用法:
#if(aa)
#end
#if(aa||bb)
#end
这里即可以是逻辑表达式,也可以是非逻辑表达式
情况如下:
如果是null,则false
如果是boolean值,看true/false
如果是String,看长度>0
如果是Collection,看Size>0
如果是Iterator看hasNext
如果是数组,看长度>0
如果是Enumerator,看hasMoreElements
如果是Map看size
其它情况,就返回true
下面所有的逻辑表达式都支持
Operator Name
Symbol
Alternative Symbol
Example
Equals Number
==
eq
#if( $foo == 42 )
Equals String
==
eq
#if( $foo == "bar" )
Object Equivalence
==
eq
#if( $foo == $bar )
Not Equals
!=
ne
#if( $foo != $bar )
Greater Than
>
gt
#if( $foo > 42 )
Less Than
<
lt
#if( $foo < 42 )
Greater Than or Equal To
>=
ge
#if( $foo >= 42 )
Less Than or Equal To
<=
le
#if( $foo <= 42 )
Boolean NOT
!
not
#if( !$foo )
Notes:
==用的是equals
可以用下面的方式来避免与显示内容分不开
#if(foo == bar)it's true!#{else}it's not!#end</li>
注意:与Velocity区别:变量名前面不要加“$”符号
#for - for循环指令
Format:
# for(varName : collection) 显示内容1 [#[{]else[}] ]  显示内容2 # [ { ] end [ } ]
表示对集合进行循环,执行显示内容1,如果集合为空,则执行显示内容2
注意:与Velocity区别:增了了#else指令
可以是collection的内容:
数组、集合、Iterator,Enumeration,Map,对象,甚至null
如果不是集合对象,只是一个普通对象,就只循环次,这比较适合于有时候会返回列表,有时候会返回一个对象的情况,就避免了增加复杂的判断。
#include - 包含指令
Format:
#include(expression[,{aa:1,bb:2}])
示例:
#include("/aa/bb/cc.vm")
#include("/aa/bb/cc.vm")
与Velocity的区别:用于把另外的页面在当前位置进行渲染,后面只能加一个Map用来传递参数
#parse:指令已经没有意义被取消
#stop - Stops the template engine
Format:
#stop[(expresson)]
Usage:
#if(aa==bb)
#stop
#end
等价于
#stop(aa==bb)
#break - Stops the current directive
Format:
#break[(expresson)]
Usage:
#if(aa==bb)
#break
#end
等价于
#break(aa==bb)
#continue[(expresson)]
Usage:
#if(aa==bb)
#continue
#end
等价于
#continue(aa==bb)
#evaluate - Dynamically evaluates a string or reference由于难用被取消,且用处太小被取消
增加指令#[@]call
#call("aa"+"bb",1,2)
等同于
#aabb(1,2)
#call(format("Sys%sMdl%s",1,2),1,2)
等同于
#Sys1Mdl2(1,2)
#@call("aa"+"bb",1,2)
...
#end
等同于
#@aabb(1,2)
...
#end
#@call(format("Sys%sMdl%s",1,2),1,2)
...
#end
等同于
#@Sys1Mdl2(1,2
...
#end
另外支持命名传递,详见宏调用
#define - 由于鸡肋被取消,调用者中的所有变量对于被调用者都可见,不再需要显式传递
#macro - 定义宏
Format:
# macro macroName( arg1 [, arg2,arg3 ... argn ] ) [ VM VTL code... ] # [ { ] end [ } ]
与 Velocity不同:macroName由括号里放在了括号外面,避免与参数混一起,参数之间必须用逗号隔开
调用宏有两种方式
#vmname( arg1,arg2 )
#@vmname(arg1,args)
....
#end
与Velocity不同:支持命名调用:
比如下面的方式; #vmname(arg2=3),也可以混用:#vmname(1,2,arg5=3,arg4=4)
注释:
##单行注释
#-- ... --#  #* ... *#,两种格式支持,是考虑到在<!-- -->的时候,改成#-- --#更方便
非解析标记,下面的内容会原样输出
#[[
This has invalid syntax that would normally need "poor man's escaping" like:
#define()
${blah
]]#
新增加内容:i18n支持:
$${aaa.bbb.ccc}
表示显示aaa.bbb.ccc对应的国际化内容
当然,还有强大的布局(页面继承),容易的使用(会vm的到现在已经会用),方便的扩展(非常容易扩展),微小的体量(引擎只有2000行代码),还想要什么,可以尽情提出。
新增内容:Java对象方法扩展,即在不修改原类的情况下,对java类添加
比如可以为String增加bold函数,通过下面的方式来进行加粗
${“悠然是个好同志”.bold()}
也可以给 Object增加toJson,toXml等方法,从而直接用下面的方式输出json或xml:
${user.toJson()},${user.toXml()}
当然,现在还有一点计划中的特性没有实现,那就是装饰方式的布局方式,可能有些同学不了解,那就先留点悬念吧。
下面看看实际演练:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
|
<!
DOCTYPE
html>
<
html
>
<
head
>
<
title
>StockModel - Httl</
title
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=${outputEncoding}"
/>
<
style
type
=
"text/css"
>
body {
color: #333333;
line-height: 150%;
}
td {
text-align: center;
}
thead {
font-weight: bold;
background-color: #C8FBAF;
}
.odd {
background-color: #F3DEFB;
}
.even {
background-color: #EFFFF8;
}
</
style
>
</
head
>
<
body
>
<
h1
>StockModel - jetbrick-template</
h1
>
<
table
>
<
thead
>
<
tr
>
<
th
>#</
th
>
<
th
>id</
th
>
<
th
>code</
th
>
<
th
>name</
th
>
<
th
>price</
th
>
<
th
>range</
th
>
<
th
>amount</
th
>
<
th
>gravity</
th
>
</
tr
>
</
thead
>
<
tbody
>
#for(item : items)
<
tr
class
=
"${itemFor.index % 2 == 1 ? "
even" : "odd"}">
<
td
>${itemFor.index}</
td
>
<
td
>${item.id}</
td
>
<
td
>${item.code}</
td
>
<
td
style
=
"text-align: left;"
>${item.name}</
td
>
<
td
>${item.price}</
td
>
<
td
style="color: ${item.range>=10?'red':'blue'};">${item.range}%</
td
>
<
td
>${item.amount}</
td
>
<
td
style="color: ${item.gravity>=20?'red':'blue'};">${item.gravity}%</
td
>
</
tr
>
#end
</
tbody
>
</
table
>
</
body
>
</
html
>
|
下面是渲染结果:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
|
<!
DOCTYPE
html>
<
html
>
<
head
>
<
title
>StockModel - Httl</
title
>
<
meta
http-equiv
=
"Content-Type"
content
=
"text/html; charset=gbk"
/>
<
style
type
=
"text/css"
>
body {
color: #333333;
line-height: 150%;
}
td {
text-align: center;
}
thead {
font-weight: bold;
background-color: #C8FBAF;
}
.odd {
background-color: #F3DEFB;
}
.even {
background-color: #EFFFF8;
}
</
style
>
</
head
>
<
body
>
<
h1
>StockModel - jetbrick-template</
h1
>
<
table
>
<
thead
>
<
tr
>
<
th
>#</
th
>
<
th
>id</
th
>
<
th
>code</
th
>
<
th
>name</
th
>
<
th
>price</
th
>
<
th
>range</
th
>
<
th
>amount</
th
>
<
th
>gravity</
th
>
</
tr
>
</
thead
>
<
tbody
>
<
tr
class
=
"even"
>
<
td
>1</
td
>
<
td
>1</
td
>
<
td
>600663</
td
>
<
td
style
=
"text-align: left;"
>Company 01</
td
>
<
td
>20.55</
td
>
<
td
style
=
"color: red;"
>10.01%</
td
>
<
td
>2.13 HM</
td
>
<
td
style
=
"color: red;"
>24.29%</
td
>
</
tr
>
<
tr
class
=
"odd"
>
<
td
>2</
td
>
<
td
>2</
td
>
<
td
>600822</
td
>
<
td
style
=
"text-align: left;"
>Company 02</
td
>
<
td
>14.69</
td
>
<
td
style
=
"color: red;"
>10.04%</
td
>
<
td
>1.56 HM</
td
>
<
td
style
=
"color: red;"
>36.79%</
td
>
</
tr
>
</
tbody
>
</
table
>
</
body
>
|
补充一下,上面本来是有20行数据的,为了节省空间,给删除了18行。
下面是程序代码:
1
2
3
4
5
6
7
8
|
public
static
void
main(String[] args)
throws
TemplateException {
TemplateEngine engine =
new
TemplateEngineDefault();
TemplateContext context=
new
TemplateContextDefault();
context.put(
"outputEncoding"
,
"GBK"
);
context.put(
"items"
, StockModel.dummyItems());
engine.addTemplateLoader(
new
FileObjectTemplateLoader(
"jetSample"
,
"D:\\git\\ebm\\src\\main\\resources\\templates"
));
engine.renderTemplate(
"/tiny.html"
,context,
new
OutputStreamWriter(System.out));
}
|
当然了,可能大家对性能非常感兴趣。
Tiny框架的性能比Beetl-1.26稍微落后一点点,但是明显比Velocity和FreeMarker是快多了。
与前面几个引擎比较,性能差异主要在:
1.强类型与弱类型方面的差异,TinyTemplate采用的是弱类型,而一些模板引擎采用的是强类型。
2.其它引擎都已经经过了长时间的优化,而 TinyTemplate只是刚二经过不到一周的初始期。通过后面的一些优化,他将有一定的提升(但是达不到Jetbrick这个程度)
这天这个只是开胃菜,亲们耐心等待我的正式发布吧。
附录:
TinyTemplate历史三天编写,代码行数量截止发稿为在3410,预计完稿后,在3700行左右。
JetBrick为1.3万+
Beetl为1.7万+
对于Velocity用户来说,迁移非常容易。
本来希望velocity升级2.0的但是实在等不到,因此只好自己动手升级了。
凡是有好的意见、建议、需求的,全部采纳并快速实现,在此提前致以感谢!