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

早过忘川 提交于 2019-12-03 04:17:29

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的类,但是有些方法没有实现,

没有办法,只要增加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工程结构静态分析

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

接下来比较关心core

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

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

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

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

就简洁清爽多了。

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

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

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

上面是Beetl的sonar检查情况

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

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

从上面的数据可以看出:

项目

Beetl

Tiny模板引擎

代码行数 23087 4944
文件数 230 171 
重复 3.3%
0.0%
复杂度 2.8/方法
1.9/方法
包耦合指数 31.5%
31.6%
包耦合循环 >35
>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规范呢?

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模板语言插件,请放到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/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倍左右,特此纠正。

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!