Beetl的環境搭建
輸入命令
1 |
git clone https://git.oschina.net/xiandafu/beetl2.0.git |
不一會兒,輸出了下面的內容
1 2 3 4 5 6 7 |
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. |
嗯嗯,好的開頭是成功的一半,不錯,代碼取下來了。
1 2 |
cd beetl2.0 mvn install |
輸出結果:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
[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/confluen ... ctBuildingException [ERROR] [Help 2] http://cwiki.apache.org/confluen ... vableModelException |
咦,這是什么鬼? 猜想是由于我用的是maven 3.1.x導致,于是升級到maven 3.3.3,執行 mvn install,可以看到開始下載相關的資源文件了,OK,起步還是不錯的,這里需要耐心等待一段時間。
咦,停止了,看到一堆錯誤,再看看是什么問題?
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 |
[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/confluen ... ojoFailureException |
看起來是Beetl繼承了jodd的類,但是有些方法沒有實現,

1 |
mvn clean install -fae |
結果還是原樣的錯誤,至此已經無法進行。 根據文件名分析,這個東東可能是對jodd的一個擴展,理論上可以刪除之,于是刪除了類BeetlActionResult,然后重新執行mvn install
這次出來的結果是:
1 2 3 4 5 6 7 8 9 10 11 |
[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模板引擎則完全遵守規范。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
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模板引擎支持的語法有:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
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模板引擎 | ||||||||||||||||
異常處理 | <% try{ callOtherSystemView() }catch(error){ print("暫時無數據"); } %> |
Tiny模板引擎的設計者認為如果讓模板引擎來處理異常,實際上是有點過度設計的意味,而應該是系統的異常處理框架去處理之。模板只參與展示層的處理,不參與業務邏輯處理。 | ||||||||||||||||
虛擬屬性 |
|
${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沒有提供標簽的擴展功能,卻提供了強大的宏定義功能 簡單宏定義
調用方式:
帶內容宏定義 前置信息
調用方式:
運行結果:
由于Tiny采用的是全部在模板語言中實現的方式,因此定義和使用文本內容更方便,同時在定義和使用時的嵌套支持能力會使得DRY原則得以全面實施,可以整個頁面沒有重復內容的出現。 | ||||||||||||||||
布局支持 | content.html內容如下:
layout.html 是布局文件
運行結果: 運行content.html模板文件后,,正文文件的內容將被替換到layoutContent的地方,變成如下內容
|
Tiny的做法是: 首先新建content.layout文件
再新建content.page文件
然后訪問content.page,運行結果就是:
實際上Tiny模板引擎還支持默認布局,多重布局各種花樣玩樣,由于采用了COC的方式,所以不需要在模板語言中顯式引入布局,而是通過目錄結構的方式來確定布局渲染方式。在進行重構的時候更也加方便,比如:同樣一個文件,放在不同的目錄結構中,由于渲染的布局不同,就會出現完全不一樣的效果,這在進行重構的時候也更加方便。 Tiny在.layout中還支持指令#layout,如下:
上面就定義了兩個布局占位,一個叫aaaInfo,一個叫bbbInfo, 在具體的頁面文件中,可以用:
來覆蓋默認的定義,轉而顯示新的內容,如果不覆蓋的話,就顯示默認的信息,這里通過引入Java的OverRide的機制,提供了更靈活多變的布局能力。 | ||||||||||||||||
宏引入 | 由于Tiny支持把公用的宏用獨立的文件來進行存放,相當于Library,但是由于不同的人定義的庫有可能有宏名沖突。因此Tiny引入了#import指令來優先使用先import進來的庫中的宏,如下:
如果出現同名的宏,那么liba中的會被執行 | |||||||||||||||||
安全調用 | Beetl采用的是安全表達式的方式來處理安全讞用 | Tiny的在調用屬性或成員函數時,可以顯式用“?.”來表示安全屬性調用,而用“.”來表示非安全屬性調用,這樣寫模板時需要明確使用哪個,這樣可以及時發現應用中的問題。 | ||||||||||||||||
錯誤提示 |
錯誤提示如下:
beetl只給出了具體的位置在哪一行,以及整個模板(或者比較近位置的模板)內容。 |
錯誤提示如下:
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模板引擎的插件功能
- 大綱支持:支持在大綱當中顯示一些關鍵內容,并可以快速定位
- 語法高亮:支持在編輯器中,根據語法進行著色,使得代碼更容易閱讀和排錯
- 錯誤提示:如果模板語言存在錯誤,則可以在工程導航、錯誤視圖及編輯窗口進行錯誤提示
- 代碼折疊:支持對代碼塊進行代碼折疊,方便查閱
- 語法提示:支持Tiny模板引擎語法提示及Html語法提示方便快速錄入
- 快速定位:支持Tiny模板中開始語句與結束語句間快速切換
- 變量快速提示:點鼠標點擊某變量時,會高亮顯示文件中的所有同名變量
- 宏定義對應位置顯示:在tiny塊處理的標簽頭部按ctrl時,會高亮顯示與其對應的#end,反之亦然
- 格式化:可以按快捷鍵ctrl+shift+F進行格式化了
- 注釋處理:可以按快捷鍵ctrl+/來進行快速設置單行注釋或取消單行注釋,可以按ctrl+shift+/來進行快速設置塊注釋或取消塊注釋
OK,工具上完全不在一個等級上。
代碼質量對比
代碼質量這個本身沒有唯一標準,這里貼一下類似的功能的代碼對比,不做評論:
for語句實現 Beetl版
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 |
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版
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 |
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版
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 72 73 74 75 76 77 78 79 80 81 82 83 84 85 |
public class TemplateInterpreter { TerminalNodeProcessor[] terminalNodeProcessors = new TerminalNodeProcessor[200]; Map<Class< ![]() ![]() 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倍左右,特此糾正。