转载

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

Beetl的环境搭建

输入命令

git clone https://git.oschina.net/xiandafu/beetl2.0.git

不一会儿,输出了下面的内容

Cloning into 'beetl2.0'... remote: Counting objects: 5807, done. remote: Compressing objects: 100% (2145/2145), done. remote: Total 5807 (delta 3050), reused 5383 (delta 2733) Receiving objects: 100% (5807/5807), 14.60 MiB | 684.00 KiB/s, done. Resolving deltas: 100% (3050/3050), done. Checking connectivity... done.

嗯嗯,好的开头是成功的一半,不错,代码取下来了。

cd beetl2.0 mvn install

输出结果:

[WARNING]  [WARNING] Some problems were encountered while building the effective settings [WARNING] 'servers.server.id' must be unique but found duplicate server with id tiny-nexus-releases @ /Users/luoguo/Develop/apache-maven-3.1.0/conf/settings.xml [WARNING]  [INFO] Scanning for projects... [ERROR] The build could not read 1 project -> [Help 1] [ERROR]    [ERROR]   The project org.beetl:beetl-core:2.2.4-SNAPSHOT (/Users/luoguo/git/beetl2.0/beetl-core/pom.xml) has 1 error [ERROR]     Non-resolvable parent POM: Could not find artifact org.beetl:beetl-parent:pom:2.2.4-SNAPSHOT and 'parent.relativePath' points at wrong local POM @ line 4, column 10 -> [Help 2] [ERROR]  [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR]  [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/ProjectBuildingException [ERROR] [Help 2] http://cwiki.apache.org/confluence/display/MAVEN/UnresolvableModelException

咦,这是什么鬼?

猜想是由于我用的是maven 3.1.x导致,于是升级到maven 3.3.3,执行 mvn install,可以看到开始下载相关的资源文件了,OK,起步还是不错的,这里需要耐心等待一段时间。

咦,停止了,看到一堆错误,再看看是什么问题?

[ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[13,8] org.beetl.ext.jodd.BeetlActionResult不是抽象的, 并且未覆盖jodd.madvoc.result.ActionResult中的抽象方法getResultType() [ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[60,9] 方法不会覆盖或实现超类型的方法 [INFO] 2 errors  [INFO] ------------------------------------------------------------- [INFO] ------------------------------------------------------------------------ [INFO] Reactor Summary: [INFO]  [INFO] beetl-core ......................................... FAILURE [ 44.926 s] [INFO] beetl-parent ....................................... SKIPPED [INFO] ------------------------------------------------------------------------ [INFO] BUILD FAILURE [INFO] ------------------------------------------------------------------------ [INFO] Total time: 45.061 s [INFO] Finished at: 2015-07-28T14:08:38+08:00 [INFO] Final Memory: 18M/262M [INFO] ------------------------------------------------------------------------ [ERROR] Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.1:compile (default-compile) on project beetl-core: Compilation failure: Compilation failure: [ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[13,8] org.beetl.ext.jodd.BeetlActionResult不是抽象的, 并且未覆盖jodd.madvoc.result.ActionResult中的抽象方法getResultType() [ERROR] /Users/luoguo/git/beetl2.0/beetl-core/src/main/java/org/beetl/ext/jodd/BeetlActionResult.java:[60,9] 方法不会覆盖或实现超类型的方法 [ERROR] -> [Help 1] [ERROR]  [ERROR] To see the full stack trace of the errors, re-run Maven with the -e switch. [ERROR] Re-run Maven using the -X switch to enable full debug logging. [ERROR]  [ERROR] For more information about the errors and possible solutions, please read the following articles: [ERROR] [Help 1] http://cwiki.apache.org/confluence/display/MAVEN/MojoFailureException
看起来是Beetl继承了jodd的类,但是有些方法没有实现, 悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

没有办法,只要增加fae指令再来执行:

mvn clean install -fae

结果还是原样的错误,至此已经无法进行。

根据文件名分析,这个东东可能是对jodd的一个扩展,理论上可以删除之,于是删除了类 BeetlActionResult,然后重新执行mvn install

这次出来的结果是:

[INFO] Reactor Summary: [INFO]  [INFO] beetl-core ......................................... SUCCESS [03:52 min] [INFO] beetl-parent ....................................... SUCCESS [  0.008 s] [INFO] ------------------------------------------------------------------------ [INFO] BUILD SUCCESS [INFO] ------------------------------------------------------------------------ [INFO] Total time: 03:52 min [INFO] Finished at: 2015-07-28T14:26:09+08:00 [INFO] Final Memory: 25M/309M [INFO] ------------------------------------------------------------------------

从环境搭建的过程来看,只要是maven 3.3.3,搭建还算顺利,美中不足是有一个Jodd的扩展

BeetlActionResult类有问题。通过直接删除,编译是通过了,有多大的影响,暂时还不清楚。

Beetl工程结构静态分析

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

从这里看,整体来说还可以,把一些bak文件上传上来,稍嫌不严谨,另外有些jpg文件直接放在根目录也有一点点乱,如果整理一下就更好了。

接下来比较关心core

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

这里面有几个东东,就有点难理解了,为什么这里放了个jar文件?为什么这里放了个lib目录?为什么这里放了个performance工程?性能评测的代码怎么会放到core工程中??

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

上面这个应该就是关键工程了?core应该就是引擎核心代码所在的位置,ext应该是它对各种开源框架方面的扩展或支持。有这些扩展还是非常不错的,方便使用者上手,赞一个。但是把ext和core放在一个工程里还是有点随意了,如果能把ext单独开个工程就更好了。

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

从上面的目录结构看还是不错的,但是很显然下面的一些类和接口看起来就比较乱了,应该相当有改进的空间。

相对应的,可以看看Tiny模板引擎的目录结构:

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

就简洁清爽多了。

再来看看beetl模板的代码行数:

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

可以看到core工程中的java代码是20291行,不算空行,不算注释行。

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

Tiny模板引擎的代码行数,纯纯的java代码只有4944行,也就是beetl的代码整整是Tiny模板引擎4倍多。

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

上面是Beetl的sonar检查情况

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

上面的统计数据是Tiny模板引擎的统计数据:

这里的数据和上面用Statistics统计的数据稍有区别,但是基本上差别不大。

从上面的数据可以看出:

项目

Beetl

Tiny模板引擎

代码行数 23087 4944
文件数 230 171
重复 0.0%
复杂度 2.8/方法 1.9/方法
包耦合指数 31.6%
包耦合循环 >18

从代码规模来说,Tiny完胜,只有Beetl的不到1/4。代码重复率方面Beetl也相当不错了,当然Tiny的更好一点。复杂度Beetl方面也不错,当然 Tiny的要更好一点。包耦合指数方面差不多,但是包耦合循环方面,tiny只有Beetl的一半。

从上面的数据来看,Tiny的方法更小,包依赖的长度更短,更容易维护。

OK,从上面的静态分析来看,Beetl的包结构组织有进步的空间,有一定的代码重复,整体代码质量还不错,但是包耦合度有点高,所以其可维护性较Tiny稍弱。

Beetl语法

到main/antlr中查找Beetl语法定义文件,居然没有找到,最后终于在下面的位置main/java/org/beetl/core/parser/BeetlParser.g4找到了,为什么不能完全遵循Maven规范呢?

悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比

Tiny模板引擎则完全遵守规范。

statement        :   block   #blockSt     |   textStatment    #textOutputSt     |   constantsTextStatment #staticOutputSt     |   COMMENT_TAG commentTypeTag  #commentTagSt     |   If parExpression statement (Else statement)? #ifSt     |   For LEFT_PAR forControl RIGHT_PAR statement  ( Elsefor statement)?  #forSt     |   While parExpression statement   #whileSt     |   Switch parExpression switchBlock    #siwchSt     |   Select g_switchStatment #selectSt     |   Try  block (Catch  LEFT_PAR Identifier? RIGHT_PAR  block   )?    #trySt     |   Return expression? END  #returnSt     |   Break END   #breakSt     |   Continue END    #continueSt     |   Var varDeclareList END  #varSt     |   Directive  directiveExp #directiveSt      |   assignMent END  #assignSt     |   functionTagCall #functionTagSt      |   statementExpression END   #statmentExpSt      |   Ajax Identifier COLON block   #ajaxSt      |   END   #end

上面是Beetl支持的语法。

tiny模板引擎支持的语法有:

directive   :   set_directive             |   if_directive             |   while_directive             |   for_directive             |   break_directive             |   import_directive             |   continue_directive             |   stop_directive             |   include_directive             |   macro_directive             |   layout_directive             |   layout_impl_directive             |   call_block_directive             |   call_directive             |   endofline_directive             |   blank_directive             |   tabs_directive             |   indent_directive             |   dent_directive             |   call_macro_directive             |   call_macro_block_directive             |   bodycontent_directive             |   invalid_directive             ;

二者做个对比:

语法体系的差异,Beetl采用的是类似jsp的方式,而Tiny模板引擎采用的是Velocity的方式,二者各有优缺点,因此并无好坏之分,只是萝卜青菜上的差异。从我本人来说,是非常讨厌类似于<% ... %>来方式圈定脚本,而更喜欢Velocity的直接用指令嵌入的方式来进行使用,所以我选择了类 Velocity的方式。因此语法体系方面没有什么好比较的。

对于常规指令Beetl和Tiny模板引擎都有良好的支持

  • 循环指令两者都支持for和while,都支持break,contine,stop/return等。同时也都支持else,也就是当循环次数为0时,执行一些操作,比如:有数据的时候在循环体内展示数据,没有数据的时候显示else中的默认内容。
  • 在条件判断方面Beetl支持了if、switch、select等指令,而tiny模板引擎则是由强大的#if() ... #elseif()... #else...#end指令格式来完成所有的条件判断,两者功能都可以互相覆盖。
项目

Beetl

Tiny

定义临时变量 var number=1 #set(number=1)
定义页面变量 template.binding("number",1) #!set(number=1)
属性引用 ${user.wife.name} ${user.wife.name}
算述表达式 <%
var a1 = 12;
var b1 = (a1+15)/3-2*a1;
var bc = -1-b1;
%>
${bc}

#set(a1=12, b1 = (a1+15)/3-2*a1, bc = -1-b1 )

${bc}

当然,#set指令也可以一行写一个赋值指令

逻辑表达式 <%
var a1 = 12;
var b1 = a1==12;
var b2 = a1!=12;
%>
${b1}
${b2}
#set(a1 = 12,b1 = a1==12,b2 = a1!=12)
${b1}
${b2}
循环语句 <%
print("总共"+userList.~size+"<br>");
for(user in userList){
%>
${userLP.index}    ${user.name} <br>
<%}%>
总共${userList.size()

}

#for(user in userList)

${userFor.index}    ${user.name}

#end

条件语句 <%
var user = map["001"];
if(user.name=="lijz"){
print(user.name);
}else{
return ;
}
%>

#set(user = map."001")

#if(

user.name=="lijz")

${user.name}

#else

#return

#end

函数调用 <%
print("hello");
println("hello");
printf("hello,%s,your age is %s","lijz",12+"");
%>

${format("hello")}${format("hello/n")}

${format( "hello,%s,your age is %s","lijz",12 )}

格式化 <%
var now = date();
var date = date("2013-1-1","yyyy-MM-dd");
%>
now=${now,dateFormat='yyyy年MM月dd日'}
date=${date,dateFormat='yyyy年MM月dd日'}
or
now=${now,'yyyy年MM月dd日'}

tiny模板引擎不允许动态创建对象,但是允许通过自定义函数或SpringBean来获取对象。

假设,这里在上下文中在now和date两个变量

now=${format(now,'yyyy年MM月dd日 HH:mm:SS')}

date=${format(date,'yyyy年MM月dd日')}
成员方法调用 <%
var list = [5,2,4];
%>
${ @java.util.Collections.max(list)}

#set( list = [5,2,4])

${list.get(1)}

安全输出

<%

var user1 = null;

var user2 = null;

var user3 = {"name":"lijz",wife:{'name':'lucy'}};

%>

${user1.wife.name!"单身"}
${user2.wife.name!}
${user3.wife.name!"单身"}
#set(user1 = null, user2 = null,

user3 = {"name":"lijz",wife:{'name':'lucy'}})

%>

${user1?.wife?.name?:"单身"}
${user2?.wife?.name ?:"单身" }
${user3?.wife?.name ?:"单身" }
注释 <%

//最大值是12;

/*最大值是12*/

var max = 12;

%>

## 最大值是12;

#*最大值是12*#

#set( max = 12)

上面做了两个模板引擎的常规指令的示例和对比,基本上采用Beetl在线示例中的示例然后用Tiny

模板引擎的语法来同样实现的功能。

下面来说说一些有意思的高级功能

项目 Beetl Tiny模板引擎
异常处理

<%

try

{

callOtherSystemView()

} catch

(error){

        print( "暂时无数据"

);

}

%>

Tiny模板引擎的设计者认为如果让模板引擎来处理异常,实际上是有点过度设计的意味,而应该是系统的异常处理框架去处理之。模板只参与展示层的处理,不参与业务逻辑处理。

虚拟属性

${user. ~ genderShowName}

${user.toJson()}

Tiny支持为某种类增加一些扩展的成员函数,和Beetl的虚拟属性的意思是相同的,但是在函数调用过程中,使用方式与原生成员函数没有区别。如果扩展的方法是getXxx,那么就可以直接调用object.xxx的方式按属性的方式来进行调用。

函数扩展

<%

var date =

date();

var len = strutil.len( "cbd"

);

println( "len=" +

len);

%>

Tiny也提供了函数扩展体系,也完全可以添加类似的函数扩展,调用方式也差不多。#set(date =date(),len=strutil.len("cbd"))

标签的支持
public class CmsContentTag extends GeneralVarTagBinding 

{

public void render()

{

Object id= this.getAttributeValue("id");

try

{

ctx.byteWriter.writeString("当前定义了一个窜上:"+id.toString());

} catch (IOException e)

{

e.printStackTrace();

}

}

}

Tiny没有提供标签的扩展功能,却提供了强大的宏定义功能

简单宏定义 

#macro cmsContent(id) 当前定义了一个内容:${id} #end

调用方式:

#cmsContent("abc")

带内容宏定义

前置信息

#macro contentFrame() 前置信息 #bodyContent 后置信息 #end

调用方式:

#@contentFrame() 这里是一些信息 #end

运行结果:

后置信息 这里是一些信息 调用方式:

由于Tiny采用的是全部在模板语言中实现的方式,因此定义和使用文本内容更方便,同时在定义和使用时的嵌套支持能力会使得DRY原则得以全面实施,可以整个页面没有重复内容的出现。

布局支持 content.html内容如下:
<%  //content.html内容如下:  layout("/inc/layout.html"){%>  this is 正文  ..........  <%%}%>
layout.html 是布局文件
<%  <%include("/inc/header.html"){} %>  this is content:${layoutContent}  this is footer:   <%%}%>
运行结果:
运行content.html模板文件后,,正文文件的内容将被替换到layoutContent的地方,变成如下内容 this is header this is content:this is 正文 ............ this is footer:

Tiny的做法是:

首先新建content.layout文件

this is header this is content:#pageContent this is footer

再新建content.page文件

this is 正文

然后访问content.page,运行结果就是:

this is header this is content:this is 正文 this is footer

实际上Tiny模板引擎还支持默认布局,多重布局各种花样玩样,由于采用了COC的方式,所以不需要在模板语言中显式引入布局,而是通过目录结构的方式来确定布局渲染方式。在进行重构的时候更也加方便,比如:同样一个文件,放在不同的目录结构中,由于渲染的布局不同,就会出现完全不一样的效果,这在进行重构的时候也更加方便。

Tiny在.layout中还支持指令#layout,如下:

#layout(aaaInfo) this is aaaInfo #end  #layout(bbbInfo) this is bbbInfo #end

上面就定义了两个布局占位,一个叫aaaInfo,一个叫bbbInfo,

在具体的页面文件中,可以用:

#@layout(aaaInfo) this is new aaaInfo #end  #@layout(aaaInfo) this is new aaaInfo #end

来覆盖默认的定义,转而显示新的内容,如果不覆盖的话,就显示默认的信息,这里通过引入Java的OverRide的机制,提供了更灵活多变的布局能力。

宏引入

由于Tiny支持把公用的宏用独立的文件来进行存放,相当于Library,但是由于不同的人定义的库有可能有宏名冲突。因此Tiny引入了#import指令来优先使用先import进来的库中的宏,如下:

#import("/a/b/liba.component") #import("/a/b/libb.component")

如果出现同名的宏,那么liba中的会被执行

安全调用 Beetl采用的是安全表达式的方式来处理安全谳用 Tiny的在调用属性或成员函数时,可以显式用“?.”来表示安全属性调用,而用“.”来表示非安全属性调用,这样写模板时需要明确使用哪个,这样可以及时发现应用中的问题。
错误提示
<% var a = 1; var b = a/0; %>
错误提示如下:
>>DIV_ZERO_ERROR:0 位于3行 资源:/org/beetl/sample/s0125/error1.txt 1|<% 2|var a = 1; 3|var b = a/0; 4|%>
beetl只给出了具体的位置在哪一行,以及整个模板(或者比较近位置的模板)内容。
#set(a=1,b=1/0)
错误提示如下:
路径:/a.page 位置[1行11列]-[1行13列] =================================================================== 1/0 ===================================================================
Tiny则明确给出了精确的坐标,x1,y1-x2,y2,同时还给出了具体出问题的内容,相对来说程序员查找问题更加迅捷。

工具的支持

beetl的插件功能

Beetl插件如约而来!
悠然乱弹:“最好的模板引擎”Beetl 剖析及与Tiny模板引擎对比
安装说明:
本插件是beetl模板语言插件,请放到dropins目录下重启即可。如果以前安装过,需要删除以前保本
如果文件以.btl结尾,则自动以插件方式打开,否则,可以通过右键此文件,选择open-with,并选择beetl editor,不建议使用btl结尾,请尽量使用原有编辑器,参考使用说明4快捷使用beetl editor
使用说明:

1 工程属性里有个beetl属性,可以指定定界符号等,默认是<%%> ${}。也可以指定模板根目录(可选,不必手工填写,在模板单击定位里会提示你选择) 2 ctrl-2 定位到下一个beetl 块 3 ctrl-3 定位到上一个beetl块 4 ctrl-4 将普通文件以beetl editor方式打开,并保持同步编辑  5 ctrl-5 静态文本全部折叠和打开静态文本折叠 6 可以ctrl+单击字符串定位到字符串对应的模板文件,第一次使用的时候,需要选择模板根目录,随后,也可以在project属性的beetl配置里配置模板根目录 7 alt-/ 进行上下文提示。也可以键入此快速输入定界符号和占位符号 8 alt-shift-p 从{ 快速移动到 匹配的},或者反之亦然。如果只单击{ 则会框选住匹配的} 而光标不移动 9 选中任何id,都能全文框选住同样的id。 10 ctrl-/ 单行注释,或者取消注释 11 通常eclipse具有的快捷操作方式,beetl仍然予以保留不变  12 具备一定的错误提示,目前只提示第一个发现的错误。

Tiny模板引擎的插件功能

  1. 大纲支持:支持在大纲当中显示一些关键内容,并可以快速定位
  2. 语法高亮:支持在编辑器中,根据语法进行着色,使得代码更容易阅读和排错
  3. 错误提示:如果模板语言存在错误,则可以在工程导航、错误视图及编辑窗口进行错误提示
  4. 代码折叠:支持对代码块进行代码折叠,方便查阅
  5. 语法提示:支持Tiny模板引擎语法提示及Html语法提示方便快速录入
  6. 快速定位:支持Tiny模板中开始语句与结束语句间快速切换
  7. 变量快速提示:点鼠标点击某变量时,会高亮显示文件中的所有同名变量
  8. 宏定义对应位置显示:在tiny块处理的标签头部按ctrl时,会高亮显示与其对应的#end,反之亦然
  9. 格式化:可以按快捷键ctrl+shift+F进行格式化了
  10. 注释处理:可以按快捷键ctrl+/来进行快速设置单行注释或取消单行注释,可以按ctrl+shift+/来进行快速设置块注释或取消块注释

由于篇幅太长,因此这里不贴完整内容,详细请看链接: http://my.oschina.net/tinyframework/admin/edit-blog?blog=365370

OK,工具上完全不在一个等级上。

代码质量对比

代码质量这个本身没有唯一标准,这里贴一下类似的功能的代码对比,不做评论:

for语句实现 

Beetl版

public final class ForStatement extends Statement implements IGoto {  public Expression idNode;  public Expression exp;  public Statement forPart;  public Statement elseforPart;  public boolean hasGoto = false;  public short itType = 0;  public boolean hasSafe;   /**   * for(idNode in exp) {forPath}elsefor{elseforPart}   * @param idNode   * @param exp   * @param forPart   * @param elseforPart   * @param token   */  public ForStatement(VarDefineNode idNode, Expression exp, boolean hasSafe, Statement forPart,    Statement elseforPart, GrammarToken token)  {   super(token);   this.idNode = idNode;   this.exp = exp;   this.hasSafe = hasSafe;   this.elseforPart = elseforPart;   this.forPart = forPart;   }  public final void execute(Context ctx)  {   // idNode 是其后设置的   int varIndex = ((IVarIndex) idNode).getVarIndex();   Object collection = exp.evaluate(ctx);   IteratorStatus it = null;   if (collection == null)   {    if (!this.hasSafe)    {     BeetlException ex = new BeetlException(BeetlException.NULL);     ex.pushToken(exp.token);     throw ex;    }    else    {     it = new IteratorStatus(Collections.EMPTY_LIST);    }   }   else   {    it = IteratorStatus.getIteratorStatusByType(collection, itType);    if (it == null)    {     BeetlParserException ex = new BeetlParserException(BeetlParserException.COLLECTION_EXPECTED_ERROR);     ex.pushToken(exp.token);     throw ex;    }   }   ctx.vars[varIndex + 1] = it;   // loop_index   //  ctx.vars[varIndex+2] = 0;   //  ctx.vars[varIndex+3] = it.getSize();   //     if (this.hasGoto)   {     while (it.hasNext())    {     ctx.vars[varIndex] = it.next();     forPart.execute(ctx);     switch (ctx.gotoFlag)     {      case IGoto.NORMAL:       break;      case IGoto.CONTINUE:       ctx.gotoFlag = IGoto.NORMAL;       continue;      case IGoto.RETURN:       return;      case IGoto.BREAK:       ctx.gotoFlag = IGoto.NORMAL;       return;     }    }    if (!it.hasData())    {     if (elseforPart != null)      elseforPart.execute(ctx);    }    return;   }   else   {    while (it.hasNext())    {     ctx.vars[varIndex] = it.next();     forPart.execute(ctx);     }    if (!it.hasData())    {     if (elseforPart != null)      elseforPart.execute(ctx);    }   }  }  @Override  public final boolean hasGoto()  {   // TODO Auto-generated method stub   return hasGoto;  }  @Override  public final void setGoto(boolean occour)  {   this.hasGoto = occour;  }  @Override  public void infer(InferContext inferCtx)  {   exp.infer(inferCtx);   if (exp.getType().types != null)   {    if (Map.class.isAssignableFrom(exp.getType().cls))    {     idNode.type = Type.mapEntryType;    }    else    {     //list or array     idNode.type = exp.getType().types[0];    }   }   else   {    idNode.type = Type.ObjectType;   }   int index = ((IVarIndex) idNode).getVarIndex();   inferCtx.types[index] = idNode.type;   inferCtx.types[index + 1] = new Type(IteratorStatus.class, idNode.type.cls);   forPart.infer(inferCtx);   if (elseforPart != null)   {    elseforPart.infer(inferCtx);   }  } }

Tiny版

public class ForProcessor implements ContextProcessor<TinyTemplateParser.For_directiveContext> {  public Class<TinyTemplateParser.For_directiveContext> getType() {   return TinyTemplateParser.For_directiveContext.class;  }  public boolean processChildren() {   return false;  }  public Object process(TemplateInterpreter interpreter, TemplateFromContext templateFromContext, TinyTemplateParser.For_directiveContext parseTree, TemplateContext pageContext, TemplateContext context, TemplateEngineDefault engine, Writer writer, String fileName) throws Exception {   String name = parseTree.for_expression().IDENTIFIER().getText();   Object values = interpreter.interpretTree(engine, templateFromContext, parseTree.for_expression().expression(),pageContext, context, writer,fileName);   ForIterator forIterator = new ForIterator(values);   context.put(name + "For", forIterator);   boolean hasItem = false;   while (forIterator.hasNext()) {    TemplateContext forContext=new TemplateContextDefault();    forContext.setParent(context);    hasItem = true;    Object value = forIterator.next();    forContext.put(name, value);    try {     interpreter.interpretTree(engine, templateFromContext, parseTree.block(),pageContext, forContext, writer,fileName );    } catch (ForBreakException be) {     break;    } catch (ForContinueException ce) {     continue;    }   }   if (!hasItem) {    TinyTemplateParser.Else_directiveContext elseDirectiveContext = parseTree.else_directive();    if (elseDirectiveContext != null) {     interpreter.interpretTree(engine, templateFromContext, elseDirectiveContext.block(), pageContext,context, writer,fileName);    }   }   return null;  } } 

解释引擎核心处理代码

Beetl版

beetl版源代码,由于太长,所以就不贴内容了,详细请点击查看源码

Tiny版

public class TemplateInterpreter {  TerminalNodeProcessor[] terminalNodeProcessors = new TerminalNodeProcessor[200];  Map<Class<ParserRuleContext>, ContextProcessor> contextProcessorMap = new HashMap<Class<ParserRuleContext>, ContextProcessor>();  OtherTerminalNodeProcessor otherNodeProcessor = new OtherTerminalNodeProcessor();  public void addTerminalNodeProcessor(TerminalNodeProcessor processor) {   terminalNodeProcessors[processor.getType()] = processor;  }  public void addContextProcessor(ContextProcessor contextProcessor) {   contextProcessorMap.put(contextProcessor.getType(), contextProcessor);  }  public TinyTemplateParser.TemplateContext parserTemplateTree(String sourceName, String templateString) {   char[] source = templateString.toCharArray();   ANTLRInputStream is = new ANTLRInputStream(source, source.length);   // set source file name, it will be displayed in error report.   is.name = sourceName;   TinyTemplateParser parser = new TinyTemplateParser(new CommonTokenStream(new TinyTemplateLexer(is)));   return parser.template();  }  public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, String templateString, String sourceName, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {   interpret(engine, templateFromContext, parserTemplateTree(sourceName, templateString), pageContext, context, writer,fileName );   writer.flush();  }  public void interpret(TemplateEngineDefault engine, TemplateFromContext templateFromContext, TinyTemplateParser.TemplateContext templateParseTree, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {   for (int i = 0; i < templateParseTree.getChildCount(); i++) {    interpretTree(engine, templateFromContext, templateParseTree.getChild(i), pageContext, context, writer,fileName );   }  }  public Object interpretTree(TemplateEngineDefault engine, TemplateFromContext templateFromContext, ParseTree tree, TemplateContext pageContext, TemplateContext context, Writer writer, String fileName) throws Exception {   Object returnValue = null;   if (tree instanceof TerminalNode) {    TerminalNode terminalNode = (TerminalNode) tree;    TerminalNodeProcessor processor = terminalNodeProcessors[terminalNode.getSymbol().getType()];    if (processor != null) {     returnValue = processor.process(terminalNode, context, writer);    } else {     returnValue = otherNodeProcessor.process(terminalNode, context, writer);    }   } else if (tree instanceof ParserRuleContext) {    try {     ContextProcessor processor = contextProcessorMap.get(tree.getClass());     if (processor != null) {      returnValue = processor.process(this, templateFromContext, (ParserRuleContext) tree, pageContext, context, engine, writer,fileName);     }     if (processor == null || processor != null && processor.processChildren()) {      for (int i = 0; i < tree.getChildCount(); i++) {       Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName );       if (value != null) {        returnValue = value;       }      }     }    } catch (StopException se) {     throw se;    } catch (TemplateException te) {     if (te.getContext() == null) {      te.setContext((ParserRuleContext) tree,fileName);     }     throw te;    } catch (Exception e) {     throw new TemplateException(e, (ParserRuleContext) tree,fileName);    }   } else {    for (int i = 0; i < tree.getChildCount(); i++) {     Object value = interpretTree(engine, templateFromContext, tree.getChild(i), pageContext, context, writer,fileName );     if (returnValue == null && value != null) {      returnValue = value;     }    }   }   return returnValue;  }  public static void write(Writer writer, Object object) throws IOException {   if (object != null) {    writer.write(object.toString());   }  } } 

嗯嗯,不到100行的规模

当然整个通读下来,就会慢慢发现为什么Tiny的代码行数这么少功能却又多的原因之所在了。

总结

Beetl算得上是较好的模板语言框架和不错的开源项目,但是距离“最好的”三个字还是有一定差距的,作为@闲.大赋 的粉丝,偶会持续支持他,也希望他能再积再累,真正当得起“最好的”三个字。

补充说明

beetl里面有4014行由antlr生成的代码,实际统计中,由于Beetl的目录结构没有按标准化的来,导致统计中包含了这部分代码,因此实际上,应该是在16000+,因此规模是Tiny模板引擎的3倍左右,特此纠正。

正文到此结束
Loading...