Tiny模板语言(VelocityPlus)初步入门

简介:

1 关于用户手册

本文主要介绍如何在模板中使用Tiny模板语言,通过查阅本手册,可以对Tiny模板语言 TTL(Tiny Template Language)的用法有一个较全面的认识,并学会如何有效地使用Tiny模板语言。同时,本文提供了较多的例子帮您来学习并掌握它。

2 Tiny模板语言概述

Tiny 模板语言是一个参考Velocity语法的模板语言,它对Velocity模板语言中一些功能不太完全及使用过程中比较不方便的地方进行全面的扩展和升级,同时为了更好的适应Web界面层的开发,还提供了强大的布局功能。本文中的例子都使用Tiny 模板语言来开发。

<HTML>
<BODY>
Hello ${customer.Name}!
<table>
#for( mud : mudsOnSpecial )
   #if ( customer.hasPurchased(mud) )
      <tr>
        <td>
          ${flogger.getPromo( mud )}
        </td>
      </tr>
   #end
#end
</table>
</BODY>
</HTML>

感谢您选择Tiny模板引擎!

3 Tiny模板语言能为您做什么?

假设您是一家专门出售Mud的在线商店的页面设计人员,让我们暂且称它为“在线MUD商店”。您的业务非常繁忙,客户下了各种类型和数量的Mud订单。他们都是通过用户名和密码登陆到您的网站,登陆后就允许他们查看订单并购买更多的Mud。现在,一种非常流行的Mud正在打折销售。另外有一些客户规律性地购买另外一种也在打折但不是很流行的Bright Red Mud,由于购买的人并不多,所以它被安置在页面的边缘。另外所有用户的操作信息都是被跟踪并存放于数据库中的,所以某天有一个问题可能会冒出来:如何给每个用户定制一个自己的页面,让他们浏览自己的感兴趣的物品。为什么不使用模板语言来使用户更好的浏览他们感兴趣的商品呢?而Tiny模板语言就是一个非常不错的选择。

Tiny Template使得定制化Web页面非常容易。作为一个Web Site的设计人员,您希望每个用户登陆时都拥有自己的页面。

您咨询了一些公司内的软件工程师,您发现他们每个人都同意客户应该拥有具有个性化的信息。那让我们把软件工程师应该作的事情放在一边,看一看您应该作些什么吧。

您可以在页面内嵌套如下的TTL声明:

<HTML>
<BODY>
Hello ${customer.Name}!
<table>
#foreach( mud : mudsOnSpecial )
   #if ( customer.hasPurchased(mud) )
      <tr>
        <td>
          ${flogger.getPromo( mud )}
        </td>
      </tr>
   #end
#end
</table>
</BODY>
</HTML>

使用Tiny模板引擎实现WEB界面就是这么简单!本文后续会有更全面的TTL语法介绍,掌握这些,您将会全面体会到Tiny模板引擎的威力。

4 Tiny模板语言简介

Tiny模板语言的目标是提供一个简洁、易学的方法将动态内容展现到Web页面上,使得Web页面设计者可以在没有任何编程语言经验就可以在非常短的时间内(一天?)学会使用它,增强您的网站展现力。

TTL使用引用这种方式将动态内容(一般指Java代码中生成的数据对象)显示到您的Web Site上。Tiny模板语言的变量只是引用中的一种,变量是用来描述要展现到视图模板中的Java数据对象。当然,Java代码也可以从模板TTL中获取数据,以下是一个写在HTML中的TTL变量:

#set( a = "Tiny" )

TTL语句:所有的TTL 语句都是以#开头,且包含一个指令(这里是set),当用户访问您的页面时, Tiny模板引擎将搜索页面中的所有“#”符号,如果确定这是一个TTL语句时就按规则处理动态内容, 符号“#”仅仅只是表明这可能是一个TTL声明。

符号“#”所跟的set我们用“指令”这一名词来称呼它(随后介绍更多的指令),set指令使用一个表达式(expression) (包含在一对括号里)将一个值value(这里是字符串“Tiny”)赋给变量a,(变量名在左边,值在右边,用“=”组合起来)。

在以上的例子中,变量是a,无需额外特殊符号标记变量,所赋值的字符串要用引号括起。而字符串的拼接可以使用运算符“+”操作,或者系统函数format进行格式化拼装。

请理解Tiny模板语言基本规则:

${expression}输出表达式的计算结果,而expression(表达式)中变量无需额外符号标记变量。而指令则以#开头来表示,有点“做些什么动作”的意思。

在上面的例子中,#set用来指定值给一个变量名a, 而变量名a的值就是"Tiny"。

5 Hello Tiny!

在您的HTML文档的任何地方,都可以引用一个变量名来输出值, 如下例,先给变量foo 赋值为Tiny,然后将它输出到页面中。

<html>
<body>
#set( foo = "Tiny" )
Hello ${foo}!
</body>
<html>

在这个页面上,您看到的将是 "Hello Tiny!"。

为了让编辑器中的TTL指令更易读,我们强烈建议您每行只有一条TTL指令,当然这不是必须的。 关于set 指令的更多功能我们随后再讨论。

6 注释

注释可以让您在模板中包含对TTL或其它问题的说明描述以便与阅读和理解。但它并不会在最终输出的WEB页面中看到。如下示例是TTL中的一行注释。

## This is a single line comment.
## 这里是行注释内容

单行注释是以“##”开头的一行文字。如要写下多行注释,就要像下面那样,将它们放入“#*”和“*#”间或“#--”和“--#”间:

#*
 Thus begins a multi-line comment. Online visitors won't
 see this text because the Tiny Templating Engine will
 ignore it.
*#
#--这里是块注释内容 --#
#* 这里是块注释内容 *#

虽然引擎只是提供了两种简单的注释方式,但却能满足您在页面上添加必要的说明文字。

之所以支持两种块注释方式,是为了在兼容性及方便性方面提供更大的便捷。#* *#方式是为了与Velocity兼容,这样熟悉Velocity的人员更容易上手。#-- --#是为了便于把Html中的注释改成Tiny模板注释。

7 引用(References)

TTL中有三种引用references:变量引用(variables),属性引用(properties)和方法引用(methods)。作为使用TTL的页面开发者, 您必须和您的Java工程师在特定的引用名称上达成一致,这样模板和Java代码才可按照你们的预期去结合以输出正确的内容。

所有的引用在模板中的输出都表现为一个字符串。假设一个引用变量 foo ,它是一个int型变量, Tiny模板引擎在处理时将调用它的.toString()去解析这个对象(int),从而获得代表它的字符串。

7.1 变量(variables)

[_a-zA-Z][_a-zA-Z$0-9]*

上面的正则表达式表示,在Tiny框架引擎中,变量必须是以下划线或大小写字母开头的,后续可以跟下划线或大小写字母和数字的字符串,才可以作为变量名。实际上宏的名字,也是遵守同样的规则。

简单地说,变量不需要任何额外的符号开头,一个合法的TTL变量名是以字母开头,后面可以是以下任意字符:

  • 字母(a .. z, A .. Z)
  • 数字(0 .. 9)
  • 下划线 ("_")

以下是正确的TTL变量名:

foo
mudSlinger
mud_slinger

如果想在TTL中引用一个变量如 foo,可以通过set命令设置自己的值,也可以从Java代码中获取。例如, Java变量foo的值为”bar”, 如果想在模板中输出这个值,那么模板中引用${foo}就可以在Web页面中输出所需值。另一种方式是在模板中使用如下TTL也可以达到这个目地。

#set(foo = "bar" )
${foo}

只要符合上面的规范的字符串都可以作为Tiny框架引擎中的变量,即使是Java的关键字也可以。

需要注意的是,由于在进行循环时,Tiny模板引擎会在循环变量名后附加“For”作为状态变量,因此,需要注意避免冲突,以影响使用。

7.2 属性(properties)

TTL的第二种引用是属性引用,属性引用不需要任何额外特殊符号开头,只需简单地按照“变量名字.变量属性”的形式即可。如下例:

customer.address
purchase.total

“customer.address”我们设想可能在两种意思。首先它可能查找一个类型为Map的customer的引用,并以“address”为key的一个数据对象。另外它可能表示的是Java对象customer中的getAddress()这个方法的返回值(当然也可写成 customer.getAddress())。当用户请求Web页面,Tiny模板引擎将根据具体的customer类型进行处理。如果想在页面直接输出这个属性,那么应该加一个取值符号${},例如${customer.address}。

注意:在Tiny语言模板使用属性的引用时,引擎首先会通过Java Bean中的getter/setter实现的,如果没有找到相应的方法,引擎才会再去寻找该对象的属性字段,并且这个属性访问权限只有为public时才能解析引用。而Java对象的其他受保护的数据域是不能直接引用的,Tiny模板引擎遵守Java数据安全规则。

如${foo.name}会解析到 对象foo的 getName()的实例方法或public name这个实例变量,但不会解析到Foos类的 private name这个实例变量。

7.3 方法(methods)

在Java 代码中定义方法是最平常的事。方法引用和其它引用一样也是一个TTL声明,看如下的例子可能可以更快地理解:

customer.getAddress()
purchase.getTotal()
page.setTitle( "My Home Page" )
person.setAttributes(["Strange", "Weird", "Excited"])

上述两个例子中customer.getAddress()和purchase.getTotal()可以等同与属性引用情况: customer.address和 purchase.total。如果您已经领悟到了这点,您的确是很聪明的!后面两个引用也会直接对应Java对象的对应方法,不同的是传入了参数。

8 表达式(Expression)

8.1 取值表达式

上文介绍了变量、属性和方法调用等引用的定义。那么要将这些引用的结果输出打印都需要经过取值表达式。在Tiny引擎模板中取值表达式分为有两种:

(1)${expression}:输出表达式的计算结果

(2)$!{expression}:输出表达式的计算结果,并转义其中的 HTML 标签。

其中expression必须是一个合法的Tiny Template表达式,如果您继续阅读本文后续的介绍将会了解表达式规范。Tiny模板引擎取值表达式{}不可以省略,${expression}和$expression不是等价的。如果表达式执行结果为null或void,则不会输出任何内容。注意:取值表达式作为指令的参数时,不可以采用${expression}形式需要去掉{},同时在字符串参数中,也不支持${expression}。也许您现在还不能理解这点,不过继续阅读下文您会慢慢领悟的。

8.2 Map常量


{expression:expression,...}

在模板中为了数据存储、传递的方便,Tiny模板语言提供了Map常量的表达方式。Map常量经常直接作为指令或自定义宏的参数,下面的例子希望能帮助您快速理解如何定义Map常量。

{}  ##表示空Map
{"aa":"aaValue","bb":"bbValue"}##纯字符串Map
{"aa":1,"bb":"bbValue"}		   ##数字及字符串混合Map

上面的例子是告诉您如何定义Map常量,事实上,在模板中定一个Map变量也类似上面的形式,只不过您还需要使用#set指令,并制定一个具体的参数名。下面的例子希望能帮助您快速理解如何调用Map常量。

#set(map={"aa":1,"key":"bbValue"})
${map.key}
${map["key"]}
${map.get("key")}


{aa:1}和{"aa":1}的含义是不同的,这一点必须要注意。

{aa:1}表示key值是aa变量的值,${"aa":1}表示key值是"aa"的字符串。

因此,如果写为{aa:1}的形式时,如果没有aa变量存在,则会报空指针错误。


如果不能确认,前面的变量是否为空,可以加一个安全调用方式:

${map?.key}
${map?.get("key")}

8.3 数组常量

[expression,...]

在模板中定义数组常量也十分简单,看了下面的例子相信您一定可以掌握。

[]  ##表示空数组
[1..5]##等价于[1,2,3,4,5]
[5..1]##等价于[5,4,3,2,1]
[(1+4)..1]##等价于[5,4,3,2,1]
[1,2,3,4,5]##纯数字数组
[1,"aa",2,"cc",3]##数字及字符串混合数组
[1,aa,2,"cc",3]##数字,变量,字符串混合数组

而调用数组常量的方式有两种,一种是直接通过下标索引的方式,另一种是调用get(index)方式。

${list[1]}
${list.get(1)}


如果不能确认,前面的变量是否为空,可以加一个安全调用方式:${list?.[index]}或${list?.get(1)}


8.4 其他表达式

类型

表达式

说明

逻辑运算

!

&&

||


自增/自减

++

--

不论放在变量前面与后面,没有区别

算术计算

+

-

*

/

%


空值常量

null


移位运算

>> 

>>> 

<< 


比较运算

==

!= 

 >

>= 

 < 

 <=

==的执行逻辑请查看13.3 关系和逻辑运算。

方法调用

functionName([...])

调用框架中的内嵌或扩展方法。

数组读取

array[i]


数字常量

123

123L

99.99F

99.99d

99.99e99 

-99.99E-10d

0xFF00

0xFF00L

0.001

0.001D

1.10D


 前缀0x不可以写成0X,后缀lfde可以用相应LFDE替换

成员方法调用

object.methodName([...])

可以通过框架为某种类型增加新的方法或覆盖原有方法。

成员属性访问

object.fieldName


布尔值常量

true

false


字符串常量

"abc\r\n"

'abc\u00A0\r\n'

不论是单引号框起来的字符串还是双引号框起来的字符串,都是一样的,唯一的区别有,当字符串中包含双引号或单引号的时候可以少用转义符。

位运算

~

^

&

|


三元表达式

exp?a:b

exp?:b

a?:b等价于a?a:b,当表达式比较复杂的时候,这种简写形式会比较漂亮,也更容易阅读

Tiny模板引擎对于布尔表达式进行多种强力支持,不仅仅只有布尔值才可以参与运算,它的运行规则如下:

  • 如果是null,则返回false
  • 如果是布尔值,则返回布尔值
  • 如果是字符串且为空串“”,则返回false
  • 如果是集合,且集合中没有元素,则返回false
  • 如果是数组,且数组长度为0,则返回false
  • 如果是Iterator类型,则如果没有后续元素,则返回false
  • 如果是Enumerator类型,则如果没有后续元素,则返回false
  • 如果是Map类型且其里面没有KV对,则返回false
  • 否则返回true

在访问属性或成员变量的时候,普通的方式是用“.”运算符,也可以使用"?.”运算符,表示如果前置变量非空,都继续执行取属性值或调用成员函数,避免空指针异常的发生。


#if(0)zero#end会显示zero,在Tiny模板语言中,只要有值就会返回true。


9 索引表示法

用类似foo[0]的方式可以获取一个对象的指定索引的值。这种形式类似调用get(Object)方法,实际上是提供了一种简略,比如foo.get(0)。因此以下几种写法都是调用get方法:

foo[0]     ## foo takes in an Integer look up
foo[i]     ## Using another reference as the index  
foo["bar"]   ## Passing a string where foo may be a Map

Java数组适用相同的语法,因为Tiny模板引擎将数组包装成一个对象,它可以通过get(Integer)获得指定索引对象的元素。例如:


foo.bar[1].junk
foo.callMethod()[1]
foo["apple"][4]


10 渲染

无论是变量、属性还是方法等这些引用的最终结果值的渲染输出本质都会转换成String对象。例如要取值输出一个Integer对象${foo},那么Tiny 模板引擎就会调用它的.toString()方法,从而将对象解析转换成一个字符串。

11 与Java无缝对接

至此,您对Tiny模板语言已经有了一定程度的了解,相信您已经迫不及待的想使用它来开发应用了。但您不要忘记,Tiny模板语言与Java是无缝对接的,Tiny模板引擎设计者花费了一定的精力做到了与Java很好的兼容,并且从Java语法中也汲取了一些优点,使得模板设计者更容易使用TTL。比如对于变量foo:${foo.getBar()}等同于${foo.bar}。

现在您大概了解到了Tiny模板语言引用的不同方式但得到的是相同的结果,事实上Tiny模板语言融合了Java和Java Bean的相关简洁语法和规范来解析Java代码中的对象和这些对象的方法及其属性,使得Java对象的大部分功能都可以展示到视图中,同时还有较好的处理性能。

12 模板布局

布局在Tiny模板引擎中是一个非常重要的概念,通过Tiny模板引擎,可以快速进行页面构建与渲染,并且减少程序员工作量。Tiny模板引擎设计者认为越到底层的程序员,所要完成的工作越少。如果您将Tiny模板语言与同类的模板引擎的布局实现相比会发现,Tiny模板引擎中的布局具有一定的强制性,这也同时减少了程序员的开发工作量,程序员不再需要关心布局文件的引入,只要放入有布局文件的目录,就会应用布局;您把它拿出来,它也就没有了。

用于设置Tiny模板语言的布局的文件称为布局文件。布局布局文件的方法结构与普通模板文件完全相同,只是它的扩展名与模板文件名不同,它的文件名以“.layout”结尾,用以区别。布局文件与模板文件完全相同,只是在布局文件中需要放置一个#pageContent标签,用以标示插入点。

为了更好的理解Tiny模板引擎中的布局,直接用示例说明,下面是目录结构:

template
 |
 +- tiny
     |
     +- layout
         |
         +- div
         |  +- aa.page
         |  +- default.layout
         +- default.layout


/template/tiny/layout/div目录中aa.page的内容如下:

Hello,World

/template/tiny/layout/div目录中default.layout的内容如下:

<b>
    #pageContent
</b>

/template/tiny/layout目录中的default.layout的内容如下:

<div>
    #pageContent
</div>

下面是执行结果:

<div>
    <b>
    Hello,World
    </b>
</div>

也就是说,虽然aa.page文件里只有Hello,World,但是由于在与它同名路径中有一个布局文件,因此用布局文件对其进行了渲染,从而变成下面的内容:

<b>
Hello,World
</b>

但是引擎发现它上层目录中另外还有一个布局文件/template/tiny/layout/default.layout,于是引擎就被渲染出上文看到的最终结果。

在Tiny模板语言中使用布局十分方便,布局文件不需要在模板文件进行显式声明。布局文件的命名规范为:模板基本文件名+“.”+布局文件扩展名,比如:模板文件名为:aa.page,则模板基本文件名为aa,其布局文件扩展名为layout,这个时候它的布局文件名就是:aa.layout。

Tiny模板引擎中有一个特殊的布局文件名,它就是default+"."+布局文件扩展名,比如:布局文件扩展名为layout,这个时候它的布局文件名就是:default.layout。


为了说明模板布局的进阶用法,请看下面的举例,假如有下面的目录:

template
 |
 +- tiny
     |
     +- layout
         +- div
         |  +- aa.page
         |  +- aa.layout
         |  +- bb.page
         |  + default.layout
         +- default.layout

/template/tiny/layout/div目录中aa.page的内容如下:

Hello,World

/template/tiny/layout/div目录中bb.page的内容如下:

Hello,悠然

/template/tiny/layout/div目录中default.layout的内容如下:

<b>
    #pageContent
</b>

/template/tiny/layout/div目录中aa.layout的内容如下:

<h3>
    #pageContent
</h3>

/template/tiny/layout目录中的default.layout的内容如下:

<div>
    #pageContent
</div>

aa.page执行结果如下:

<div>
    <h3>
    Hello,World
    </h3>
</div>

bb.page执行结果如下:

<div>
    <b>
    Hello,悠然
    </b>
</div>

聪明的您一定明白了,布局文件的渲染过程是这样的,优先渲染与模板文件同名的布局文件,只有不存在与模板文件同名的布局文件的时候,才会渲染default.layout布局文件。详细的渲染过程分析如下:

aa.page先去找同级aa.layout,如果找到了,渲染同级aa.layout。再往上一级递归渲染到上一级的default.layout。

bb.page先去找同级bb.layout,如果找不到,去找同级default.layout。如果找到,渲染同级default.layout。再往上一级递归,渲染到上一级的default.layout。

Tiny模板引擎的默认扩展名为layout、component、page,分别代表而已文件、宏文件、页面文件。在实际应用当中,也可以根据自己的喜好设置为其它的名字。


13 指令集

在模板中您可以使用“引用”生成动态内容,而指令简单地说就是设计者在模板中操作Java对象,方便页面设计者有效地控制输出内容的格式。

指令总是以#开头后面紧跟具体的指令符。

13.1 #set指令

#set(name1=expression,name2=expression,[...])用于向当前上下文赋值
#!set(name1=expression,name2=expression,[...])用于向当前模板的上下文赋值。

#set指令通常是用来给一个引用赋值。赋值对象不仅可以是变量引用,还可以是属性引用。如下示:

#set( primate = "monkey" )


注意:Tiny模板语言的#set指令赋值的内容可以直接是变量名,但是不需采用${变量名}的形式赋值。如#set( customer.Behavior = $primate )就是一种错误的写法。

“左操作数被赋值“是引用操作的一个规则。=号右侧可能是以下类型之一:

  • Variable reference变量引用 
  • String literal字符串 
  • Property reference 属性引用
  • Method reference 命令引用
  • Number literal 数字
  • ArrayList 数组
  • Map 映射

请看下面例子,可以帮助聪明的您理解上述类型设置的理解:

#set( monkey = bill ) ## variable reference
#set( blame = whitehouse.Leak ) ## property reference
#set( number = 123 ) ##number literal
#set( friend = "monica" ) ## string literal
#set( say = ["Not", friend, "fault"] ) ## ArrayList
#set( map = {"banana" : "good", "kg" : 1}) ## Map


注意:在ArrayList类型引用的例子中,其原素定义在数组 [..]中, 因此,您可以使 ${Say.get(0)}访问第一个元素。

类似的,引用Map 的例子中, 原素定义在 { } 中,其键和值间以“:”隔成一对,使用 ${map.get("bannana") }在上例中将返回 'good', 如果写成${map.banana}也会有同样效果。

下面的例子是一般的计算表达式结果通过#set指令赋值:

#set( value = foo + 1 )
#set( value = bar - 1 )
#set( value = foo * bar )
#set( value = foo / bar )

如果您在模板中对一个变量进行多次赋值 ,可以对其值进行替换,比如下例中,最终name变量中赋的值为字符串“def”。

#set(name="abc",name="def")


在TIny模板语言中,不论是定义的变量还是循环变量,在宏模板执行过程中将全程有效,直到被修改。 

在Tiny语言中,引入了变量作用域近者优先的概念,当前区块有,则取当前区块,如果当前区块没有,则取离得最近的变量。

比如:

#for(i:[1,2,3,4,5])
    #for(i:[6,7,8,9])
        ${i}
    #end
    ${i}
#end

在上面的代码中,两个区块的${i}不会任何影响。

在Tiny模板语言中已经内置上下文Context,如果调用宏,会产生一个上下文;如果进入循环语句,也会创建一个上下文。这些创建的上下文,在其生命周期是有限的,出了生命周期以后,设置在他上面的变量就不能被访问了。如果在宏里或循环里,想把值设到自己的生命周期结束之后还可以被继续使用,就要设置到模板的上下文上。

设置到当前上下文用#set,设置到模板的上下文上,则用#!set,如果当前位置就在模板中,使用#set和#!set没有任何区别。 


注意: #set 不需要使用 #end 来声明结尾。

13.2 条件判断

#if...#else...#elseif..#end 指令用来根据条件在页面中输出内容,如下简单的例子:

#if( foo )
   Tiny!
#end

根据变量foo计算后是否为true决定输出,这时会有三类情况:

(1)foo是null值,那么模板引擎处理结果为false。

(2)foo的是值是一个非null的boolean(true/false)型变量,那么计算结果直接取其值。

(3)它是一个非null的实例,若是String、Collection、Map、Array等类型,则当其长度或大小大于0返回true否则返回false,若是Iterator则当迭代器有下一个元素返回true否则返回false;其他非null实例都返回true。

在 #if 和 #end 的内容是否会输出,由foo是否为true决定。这里,如果foo为true,输出将是:Tiny!如果foo 为null或false,将不会有任何输出。

#elseif 或 #else 可以 #if 和组合使用。如果第一个表达式为true,将会不计算以后的流程,如下例假设foo 是15 而bar为6。

#if( foo < 10 )
    <strong>Go North</strong>
#elseif( foo == 10 )
    <strong>Go East</strong>
#elseif( bar == 6 )
    <strong>Go South</strong>
#else
    <strong>Go West</strong>
#end

输出将会是

 Go South.

其中#if指令及#end指令必须包含,#elseif及#else指令可以省略,#elseif可以多次出现,而#else最多只能出现一次。多个条件之间可以用&&、||等进行连接。有时候#else或#end会和后面的字符内容连起来,从而导致模板引擎无法正确识别,这时就需要用#{else}或#{end}方式,避免干扰。

13.3 关系和逻辑运算

==相等运算

Tiny模板语言使用==来做比较,如下例.:

#set (foo = "deoxyribonucleic acid")
#set (bar = "ribonucleic acid")

#if (foo == bar)
  In this case it's clear they aren't equivalent. So...
#else
  They are not equivalent and this will be the output.
#end


浮点数比较,不推荐采用==方式进行比较,因为这样会由于精度原因出现误差,而导致看似相同的结果在执行equals的时候返回false。


注意:== 计算与Java中的 == 计算有些不同,不能用来测试对象是否相等(指向同一块内存)。在相等运算时,Tiny模板引擎首先会判断操作对象是否为null,若两个操作数都为null,则判为相等,若两操作数中仅有一个为null则判为不相等;若两个操作数都是非null但类型相同,则调用其equals()方法。如果是不同的对象,会调用它们的toString()方法再调用两个String的equals()结果来比较。

AND运算

## logical AND
#if( foo && bar )
   <strong> This AND that</strong>
#end

仅当foo 和bar都为true时,#if()才会输出中间内容.

OR 运算

## logical OR
#if( foo || bar )
    <strong>This OR That</strong>
#end

foo或bar只要有一个为true就可以输出。

NOT运算

NOT运算则只有一个操作参数或表达式 :

##logical NOT
#if( !foo )
 <strong>NOT that</strong>
#end

13.4 循环语句

13.4.1 for循环

#for|foreach(var:expression)
...
#else
...
#end
#for表示对expression进行循环处理,当expression不可以循环时,执行#else指令部分的内容。虽然foreach也表示循环,但是为了通用建议使用#for.
#for(number:[1,2,3,4,5])
    value:${number}
#end
#foreach(book:user.books)
    书名:${book.title},标题:${book.author.name}
#else
  No books found.
#end

其中expression必须是一个合法的Tiny Template表达式,具体参考表达式小节。

#end指令在使用的时候如果有歧义可以用#{end}代替。循环变量及循环状态变量只在循环体内可以使用,循环体外则不可用。

<ul>
#for ( product : allProducts )
    <li>${product.name}</li>
#end
</ul>

在上述例子中,allProducts或是一个List、 Map或是 Array类型的容器集合,#for每一次循环都会将容器集合中的一个对象赋给暂存变量product(称为循环变量),allProducts指定给变量 product 是一个引用到其中一个Java对象的引用。如果product确实是一个Java代码中的Product类,它可以通过product.name访问(或者Product.getName())。

我们假设 allProducts 是一个HashMap,您会发现要取出其中的东西是多么的简单:

<ul>
#for ( key : allProducts.keySet())
    <li>Key: ${key} -> Value: ${allProducts.get(key)}</li>
#end
</ul>


Tiny模板引擎对于表达式进行了多种强力支持,不仅仅只有集合类型才可以参与运算,它的执行规则如下:

  •  如果是null,则不执行循环体。
  •  如果是Map,则循环变量存放其entry,可以用循环变量.key、循环变量.value的方式读取其中的值
  •  如果是Collection或Array则循环变量放其中的元素
  •  如果是Enumeration、Iterator,则循环变量存放其下一个变量。
  •  如果是enum类,则循环变量存放其枚举值
  •  否则,则把对象作为循环对象,但是只循环一次
循环状态变量

每个#for语句,会在循环体内产生两个变量,一个是变量本身,一个是变量名+“For”,比如:

#for(num:[1,2,3,4,5])
...
#end


例如上面的例子中,在循环体内可以有两个变量可以访问一个是“num”,一个是"numFor"。其中numFor是其状态变量,用于查看for循环中的一些内部状态,下面对numFor属性进行详细说明:

  • numFor.index 可用于内部循环计数,从 1 开始计数。
  • numFor.size 获取循环总数。如果对 Iterator 进行循环,或者对非 Collection 的 Iterable 进行循环,则返回 -1。
  • numFor.first 是否为第一个元素。
  •  numFor.last 是否为最后一个元素。
  • numFor.odd 是否为第奇数个元素。
  •  numFor.even 是否为第偶数个元素。


循环中断:#break

循环中断语句只能用在循环体内,用于表示跳出当前循环体。

#break(expression)
#break

其中expression必须是一个合法的TinyTemplate表达式,具体参考表达式小节。

#for(num:[1,2,3])
    #break(num==2)
#end

上面的例子表示当num的值为2的时候,跳出循环体。

它等价于:

#for(num:[1,2,3])
    #if(num==2)#break#end
#end

可以看出第一种写法更方便。

#break只能跳出一层循环,不能跳出多层循环。#break只能放在循环体中,放在循环体外,会导致运行异常。

循环继续:# continue

循环继续语句,只能用在循环体内,表示不再执行下面的内容,继续下一次循环。

#continue(expression)
#continue

其中expression必须是一个合法的Tiny Template表达式,具体参考表达式小节。

#for(num:[1,2,3])
    #continue(num==2)
#end

表示当num的值为2的时候,执行下一次循环。

它等价于:

#for(num:[1,2,3])
    #if(num==2)#continue#end
#end

可以看出上面的写法更方便。

#continue只能继续当前循环,不能继续外层循环。#continue只能放在循环体中,放在循环体外,会导致运行异常。

13.4.2 while循环

#while(expression)
...
#end
此指令表示对判断expression进行循环处理,当expression运算结果为false时结束循环。其中expression必须是一个合法的TinyTemplate表达式,具体参考表达式小节。

while循环指令是模板引擎2.0.10新增加的指令,更早的版本是不支持的。

下面是具体使用例子。

#set(i=0)
#while(i<10)
  #set(i=i+1)
  ${i}
#end

Tiny模板引擎中的循环语句,expression可以支持任意的表达式。

13.5 模板嵌套语句#include

#include(expression)
#include(expression,{key:value,key:value})

#include内的这个表达式应该是一个字符串,用于指定要嵌套的子模板的路径。它后可以跟参数,也可以不跟参数,如果跟参数的话,只能跟一个map类型的值。

示例

#include("/a/b/aa.page")   ##表示绝对路径
#include("../a/b/aa.page") ##表示相对路径


#include(format("file%s-%s.page",1,2))
##表示采用格式化函数执行结果作为路径,这个例子中为:与当前访问路径相同路径中的"file1-2.page"文件
#include("/a/b/aa.page",{aa:user,bb:book})##表示带参数访问,会带过去两个参数aa和bb。

子模板可以访问所有祖先模板中的变量。出于封装性方面的考虑,在Tiny模板语言中子模板不能修改父模板中变量的值,从而避免不可预知的问题。

13.6 宏定义语句#macro

在Tiny模板引擎中,宏是一个非常强大灵活的东西,使用它可以避免在模板中编写重复的代码,也可方便一些具体业务的开发。

#macro macroName([varName[=expression][,varName[=expression]]])
    #bodyContent
#end

宏的名字和变量的名字必须符合Tiny变量的定义规范,宏的参数的个数可以为0~N个。宏定义的参数可以设置默认值,参数分隔可以采用英文逗号或者空格。#bodyContent表示中间可以包含任意的符合Tiny模板规范的内容,#bodyContent也可以不存在,因此对应了不同的调用方式。

在Tiny模板语言中宏的调用方式有两种,一种是单行调用方式,格式如下:

#macroName([expression|varName=expression[,expression|varName=expression]*])

另外一种是带内容调用方式,格式如下:

#@macroName([expression|varName=expression[,expression|varName=expression]*])
......
#end

参数支持按顺序赋值方式,也支持命名赋值(varName=值)方式。参数分隔可以采用英文逗号或者空格。

#@macroName([expression|varName=expression[,expression|varName=expression]*])
......
#end

例如在模板中定义如下宏:

#macro header(subTitle)
    <h1>Tiny框架:${subTitle}</h1>
#end

调用方式:

#header("homepage")
#header("about")

运行结果:

<h1>Tiny框架:homepage</h1>
<h1>Tiny框架:about</h1>

为了进一步说明宏定义,请看下面的进阶示例

#macro div()
<div>
    #bodyContent
</div>
#end
#macro p()
    <p>
    #bodyContent
    </p>
#end

调用方式:

#@div()
    #@p()
        <em>一些信息</em><b>一些内容</b>
    #end
#end

运行结果:

<div>
    <p>
        <em>一些信息</em><b>一些内容</b>
    </p>
</div>


自定义宏macro的访问有两种方式:一种是包含内容的,一种是不包含内容的。

如果参数变量与外部变量的名称完全相同,这个变量可以不在调用时传递,Tiny模板引擎会自动读取外部变量对应的值。通过命名传值可以避免复杂的传值指令及不必再费心考虑参数顺序。


另外宏的定义支持嵌套定义,也就是说支持下面的形式:

#macro aa()
	aa-Content
	#macro bb()
		bb-Content
	#end
#end

它等价于下面的方式:

#macro aa()
	aa-Content
#end
#macro bb()
	bb-Content
#end

考虑到代码的易读性,建议还是采用第二种的定义方式。


在Velocity中定义宏的时候是不可以调用带内容的宏的,而在强大的Tiny模板引擎中,则可以无限定义,强力支持。

#macro macroName()
    #@subMacroName1()
        #@subMacroName1()
            #bodyContent
        #end
    #end
#end

13.7 宏引入语句#import

如果项目中存在同名宏,那么就会涉及加载的选择问题。#import指令可以确定引入宏的执行顺序,从而解决宏冲突的问题。

#import(expression)
#import("/a/b/filename")   ##表示绝对路径,其中filename表示宏名称。

这个表达式的执行结果应该是一个字符串,其标示了要引入的宏的路径。

#import("/aa/aa/aa.component") 表示引入/aa/aa/aa.component。

13.8 布局重写语句#layout #@layout 

在使用Tiny模板语言开发的Web项目中如果定义布局文件(*.layout)和页面文件(*.page)就会遇到如下需求:针对特定页面展示不同的布局样式,目前有两种实现方式:

  1. 通过增加同名布局文件来实现。目前Tiny模板引擎查找布局文件顺序:先查找某一级目录跟渲染页面文件同名的布局文件,如果没找到再查找default.layout,之后再往上递归查找。针对要展示特殊布局样式的页面文件,建立同名的布局文件,将特殊布局菜单在该文件里面实现即可。此方案需要额外增加一个布局文件。
  2. 通过layout指令实现。如果不想增加布局文件也可以采用layout指令。用户在布局文件(*.layout)定义布局的样式和名称,然后在页面文件(*.page)里面调用。如果用户重写的布局不存在或未实现,则显示在布局文件(*.layout)定义布局的样式;如果在页面文件(*.page)通过#@layout重写布局文件中存在的布局,则调用的重写后的布局。采用这种方式,需要在编写页面文件(*.page)时清楚知道布局文件的层次结构,保证能重写到指定的布局。


#layout(layoutName) ...... #end  

#layout指令用于布局文件(*.layout),定义布局文件的名称,制定通用的布局格式。

#@layout(layoutName) ...... #end

#@layout 指令用于页面文件(*.page),如果layoutName的布局存在,则模板引擎在渲染布局时使用用户在#@layout 指令里定义的布局覆盖原有布局。

layout指令就是为实现布局文件的特殊渲染而设计的,以下是简单示例,方便大家理解:

首先,创建布局文件default.layout。

this is default layout start
#layout(weblayout)
this is default
   #pageContent
#end
this is default layout end

上述例子中,我们定义了名为weblayout的布局样式。接下来就是定义页面文件a.page。

#@layout(weblayout)
    this is layout of weblayout.
    #pageContent
#end
this is a.page

在模板中weblayout的布局样式被用户自定义的样式给取代,模板渲染的结果如下:

this is default layout start

    this is layout of weblayout.
    
this is a.page

this is default layout end


我们再尝试修改a.page,重写一个不存在的布局样式weblayout2.

#@layout(weblayout2)
    this is layout of weblayout2.
    #pageContent
#end
this is a.page

渲染的效果如下:

this is default layout start

	this is default
	
this is a.page

this is default layout end

13.9 停止执行#stop

 #stop 指令用来指示在模板的某处直接结束当前处理,终止模板的渲染,引擎停止解析。

#stop(expression)
#stop

当模板中调用#stop时无条件直接终止模板渲染。Tiny模板引擎还支持带条件执行终止模板渲染,请看下面的例子:

#stop(num==2)

上述例子表示当num==2时执行#stop终止模板渲染,它等价于:

#if(num==2)#stop#end

 可以看出上面的写法更方便。

13.10 返回指令#return

返回指令,停止某个宏的后续逻辑的渲染,类似Java方法中的return指令;如果用户在page页面使用该指令,会影响整个页面的渲染。

#macro log(level info)
  #if(level=='debug')
    <debug>${info}</debug>
  #else
    #return
  #end
#end
##以上示例在宏中使用return指令,如果触发return机制,模板引擎会停止对宏以后内容的渲染


#while(i<10)
  #set(i=i+1)
  #if(i>7)
    #return
  #end
  ${i}
#end


在page页面中使用return指令,可能会导致以后页面停止渲染,用户请慎用!

return指令的作用与stop指令相同,部分场合可以相互替换,但是还是存在以下差异:

(1)return是指退出当前执行场景,如宏或模板。

(2)stop是直接终止模板引擎的继续渲染,仅输出已经渲染内容。

13.11 行结束指令

#eol
#{eol}

表示显式输出一个“\r\t”。

在Tiny模板引擎中,默认会把文本输出内容进行trim操作,因此,默认是没有回车换行符的。因此,如果想额外增加一个回车换行符,就需要增加#eol指令。但在HTML页面中并不是<Br>。

14 系统内置函数

系统内嵌函数是指在模板引擎中默认就会注册的函数,直接可以拿来使用的函数。

14.1 读取文本资源函数read,readContent

读取文本资源函数(read,readContent) ,用于读取指定路径的文本资源,并返回指定结果。如果读取的是模板文件,得到的将是未经渲染的文本内容。

read("src/test.txt")
readContent("src/test.txt")

函数调用后的返回值是加载文本资源的字符串。

14.2 解析模板parse 

 解析模板可以用内置函数parse引入一个包含TTL的模板文件,Tiny模板引擎把解析这个文件的结果作为函数返回值。

parse(expression)
parse("/a/b/filename")   ##表示绝对路径,其中filename表示宏名称。

 与 #include 指令不同,引入的模板经过内置函数parse处理后可以得到一个变量引用,方便模板中其他地方再引用。

14.3 格式化函数fmt,format,formatter

格式化函数(fmt,format,formatter),用于对数据进行格式化,并返回执行结果。该类函数的底层实现是调用了java.util.Formatter实现的,因此具体如何填写格式化串可以参考java.util.Formatter用法。

#set(result=format("hello,%s%s",name,city))
${format("hello,%s",name)}

14.4 宏调用方法call,callMacro

宏调用方法(call,callMacro),用于执行一个宏,并把执行完成的结果作为字符串返回。call函数的返回值为宏的运行结果。call有类似macro语法,也是支持单行调用和多行带内容调用。

单行调用:

#call("macroName")
#call("macroName",1,2)
#callMacro("macroName")
#callMacro("macroName",1,2)

 多行带内容调用,需要结束标识。

#@call("macroName")
  ......
#end

因此,以下三种方式是等价的:

${call("macroName")}
#call("macroName")
#macroName()

14.5 实例判断函数is,instanceOf,instance

is(object,classType...)
instanceOf(object,classType...)
instance(object,classType...)

类实例判断函数会根据传入对象判断是否为指定类的实例,返回值为true或者false。

${instance("abc","java.lang.String","java.lang.Byte")}
${instanceOf(10,"java.lang.Integer")}

14.6 求值函数eval,evaluate

eval("模板内容")evaluate(templateContentVarName)

求值 函数(eval,evaluate),用于执行一段宏代码,执行后的结果为字符串

#set(result=eval("hello,${name}"))${eval("hello,${name}")}

14.7 随机数函数rand,random

随机数函数用于生成指定类型的随机数。目前支持int、long、float、double和uuid五种类型。注意该函数是在模板引擎2.0.16版本之后提供的,更早版本是不支持这个函数的

rand("随机数类型")
rand()
random("随机数类型")
random()

返回值:随机数,如果不指定类型,默认返回int类型;如果指定类型,返回指定类型的随机数

${rand()}
${random()}
${rand("int")}
${random("long")}
${rand("float")}
${rand("double")}
${rand("uuid")}

14.8  类型转换函数

Tiny模板语言中有可能经常会遇到String类型的引用转成其他基本类型(Integer、Double、Float、Long、Bool)的引用,Tiny模板引擎为您提供了一系列函数,请看下面的示例。

${toInt("8")+toInt("2")}
${toDouble("2")}
${toFloat("20.4")+toFloat("20.8")}
${toBool("true")?1:2}

执行结果如下:

10
2.0
41.199997
1

14.9 日期格式转换formatDate

为了使Java的Date类型在模板语言能按照您的指定格式输出,Tiny模板引擎内置了formatDate/formatdate(date,formatPatten)。formatDate系统函数的第一个参数是Date类型,第二参数是格式化模式。另外Tiny模板引擎还提供一个获取当前系统时间Date对象的系统函数now()。

${formatDate(now(),"yyyy年MM月dd日 HH:mm:ss")}

上面的例子模板语言解析得到当前系统时间,并按照格式“ yyyy年MM月dd日 HH:mm:ss”输出。


15 其它特性和细节

15.1 数学计算

Tiny模板语言自带一些数学函数,可以用#set指令在模板中使用。下面是四则运算的例子:

#set( foo = bar + 3 )
#set( foo = bar - 4 )
#set( foo = bar * 6 )
#set( foo = bar / 2 )

两个整数间的除法运算的结果仍是整数,小数部分会被剔除。余数可以用取余运算符(%)得到,例如:

#set( foo = bar % 5 )

15.2 范围操作符

范围操作符可以与#set和#foreach语句一起使用。用它来生成包含整数的数组非常方便:

[n..m]

其中n和m必须是整数。m可以大于n也可以小于n。如果m小于n,整数序列是递减的。

例子一:

#for( foo : [1..5] )${foo} #end

例子二:

#for(bar : [2..-2] )${bar} #end

例子三:

#set( arr = [0..1] )
#for( i : arr )${i} #end

例子四:

 [1..3]


例子一结果:

 1 2 3 4 5

例子二结果:

 2 1 0 -1 -2

例子三结果:

 0 1

例子四结果:

 [1..3]

15.3 连接字符串

很简单,看例子就是 :

#set( size = "Big" )#set( name = "Ben" )The clock is ${size+name}.

上面模板的输出将是

  The clock is BigBen

或者:

#set( size = "Big" )
#set( name = "Ben" )
#set(clock = size + name )
The clock is ${clock}.

它们都是同样的输出,最后一个例子如下:

#set( size = "Big" )
#set( name = "Ben" )
#set(clock = size+"Tall" + name )
The clock is ${clock}.

输出是:

 The clock is BigTallBen.

除此之外,您还可以通过系统函数fmt输出拼装字符串。

#set( size = "Big" )#set( name = "Ben" )${fmt("The clock is %s %s",size,name)}

上面模板的输出将是

 The clock is Big Ben

15.4 文本内容转义输出

如果有些内容本来是文本内容,但是由于与模板引擎的指令或表达式产生冲突,此时可以采用转义方式进行处理。

比如:如果一个变量email己定义了(比如它的值是 foo),而这里您不是要读取变量email的值,想直接输出${email}这样一个字符串,就需要使用转义字符”\”。

## The following line defines $email in this template:
#set(email = "foo" )
${email}
\${email}
\\${email}
\\\${email}
\#macro abc()

上面的模板在Web页面上的输出将是:

foo
${email}
\foo
\${email}
#macro abc()

另外,还可以使用#[[...]]#不解析文本块的所有内容。例如:

This ia test info.<a href="#">link</a>
#[[
${abc}#macro aa()info #end
]]#

它的运行结果为:

This ia test info.<a href="#">link</a>

${abc}#macro aa()info #end

非模板引擎支持的类似的指令,比如:#aabbff,不会被识别成指令,而会原样输出,可以不进行转义。"\" 后面跟的字符不是 "#" 和"$",也不需要进行转义,直接输出。


相关文章
|
4月前
|
C语言
C 语言函数:入门指南
一个函数包括两个部分: 声明:函数名称、返回类型和参数(如果有) 定义:函数体(要执行的代码)
91 2
|
11天前
|
程序员 C++
C++语言模板学习应用案例
C++模板实现通用代码,以适应多种数据类型。示例展示了一个计算两数之和的模板函数`add&lt;T&gt;`,可处理整数和浮点数。在`main`函数中,展示了对`add`模板的调用,分别计算整数和浮点数的和,输出结果。
10 2
|
8月前
|
JSON 自然语言处理 JavaScript
go 语言实战入门案例之命令行排版词典
go 语言实战入门案例之命令行排版词典
50 0
|
SQL 开发框架 安全
Go Web编程实战(10)----模板引擎库text/template包的使用
Go Web编程实战(10)----模板引擎库text/template包的使用
276 0
Go Web编程实战(10)----模板引擎库text/template包的使用
|
自然语言处理 PHP Python
【Python】简单Web框架从零开始(四):300行代码搞定模板渲染【上】
通过使用学习tornado、bottle的模板语言,我也效仿着实现可以独立使用的模板渲染的代码模块,模板语法来自tornado和bottle的语法。可以用来做一些简单的网页渲染,邮件内容生成等HTML显示方面。以下就是简单的语法使用介绍。
175 0
|
自然语言处理 Python
【Python】简单Web框架从零开始(四):300行代码搞定模板渲染【下】
通过使用学习tornado、bottle的模板语言,我也效仿着实现可以独立使用的模板渲染的代码模块,模板语法来自tornado和bottle的语法。可以用来做一些简单的网页渲染,邮件内容生成等HTML显示方面。以下就是简单的语法使用介绍。
138 0
|
资源调度 前端开发
Gatsby 入门示例
Gatsby 初始化项目
248 0
|
开发框架 自然语言处理 前端开发
一种新的DSL生成和通用语言框架:pypy
本文关键字:DSL框架和自动化生成工具,pypy as dsl framework and jit framework
806 0
一种新的DSL生成和通用语言框架:pypy
|
Java 索引
极简模板语言实现
在项目中遇到这样一个功能,需要用户输入一段带参数的字符串模板,然后服务端根据一个上下文去解析它。查了一些资料发现了[StrSubstitutor](https://commons.apache.org/proper/commons-lang/javadocs/api-3.4/org/apache/commons/lang3/text/StrSubstitutor.html) 这个小工具。其自带例子
1077 0
|
Web App开发 关系型数据库 数据库
Python全栈 Web(Django框架、模板继承、模型)
Flask、Python、Django、框架、服务器、客户端、浏览器、交互、WEB、Python前端、CSS、JAVA、HTML、H5、PHP、JavaScript、JQuery、分布式开发、项目、flask项目、项目实战、django框架、Python项目、Python的Flask框架、Python.
1373 0