由浅入深:自己动手开发模板引擎——解释型模板引擎
受到群里兄弟們的竭力邀請,老陳終于決定來分享一下.NET下的模板引擎開發(fā)技術(shù)。本系列文章將會帶您由淺入深的全面認(rèn)識模板引擎的概念、設(shè)計、分析和實戰(zhàn)應(yīng)用,一步一步的帶您開發(fā)出完全屬于自己的模板引擎。關(guān)于模板引擎的概念,我去年在百度百科上錄入了自己的解釋(請參考:模板引擎)。老陳曾經(jīng)自己開發(fā)了一套網(wǎng)鳥Asp.Net模板引擎,雖然我自己并不樂意去推廣它,但這已經(jīng)無法阻擋群友的喜愛了!
與置換型模板引擎不同的是,解釋型模板引擎包含了一個專用解釋器,有了解釋器的存在就可以支持一些更加復(fù)雜而嚴(yán)謹(jǐn)?shù)恼Z法。熟悉設(shè)計模式的朋友此時此刻應(yīng)該能夠回想起來“解釋器模式”,它是將一些業(yè)務(wù)封裝成一個或多個命令,然后通過一個復(fù)雜的解釋器(Interpreter)來解析執(zhí)行來滿足生產(chǎn)需求的。實際上,解釋型模版引擎就是一個較為復(fù)雜的解釋器模式的實現(xiàn)而已。具體到模板引擎技術(shù)上來,解釋器的工作任務(wù)實際上就小了很多。按照數(shù)據(jù)封裝模式的不同,解釋型模板引擎分為命令解釋器和反射解釋器兩個大類。
命令解釋器
還記得置換型模板引擎中,我們在最后曾經(jīng)處理了一個較為負(fù)載的Label“{CreationTime:yyyy年MM月dd日 HH:mm:ss}”嗎?實際上,這個就是命令解釋器的一種實現(xiàn)。此Label中的format部分可以看作是CreationTime的一個命令。有了命令,我們可以讓Label去做更多的事情,這是顯而易見的。
命令也可以理解為指令。
為了更加能夠說明這個問題,我們先將我們要處理的模板復(fù)雜化一點,如下:
1 <!--#include file="_Page_Header.shtml" --> 2 <ul> 3 <li>博主姓名:陳彥銘</li> 4 <li>創(chuàng)建日期:{CreationTime:yyyy年MM月dd日 HH:mm:ss}</li> 5 <li>粉絲數(shù)量:{FunsCount:D4}</li> 6 </ul> 7 <!--#include file="_Page_Footer.shtml" -->除了我們已經(jīng)熟悉的{xxx}標(biāo)簽之外,我們又增加了一個文件包含指令“<!--#include file="_Page_Header.shtml" -->”。這個指令是標(biāo)準(zhǔn)的SHTML語法,意思就是將其他文檔中的模板合并到當(dāng)前的指令位置,如此我們便可以實現(xiàn)代碼重用。尤其對于HTML開發(fā)來說,使用SHTML包含語法,還可以在諸如Dreamweaver等IDE中實現(xiàn)“所見即可得”的效果。
關(guān)于如何實現(xiàn)包含指令,我們將在后續(xù)章節(jié)中具體介紹。
反射解釋器
接觸了指令之后,其實我們還是不夠滿足,因為到目前為止,我們的模板只能做簡單的標(biāo)簽置換,這個相當(dāng)不給力。比如有些內(nèi)容我們想讓它根據(jù)情況輸出,那么就需要條件語句的效果,再如我們經(jīng)常會遇到列表等情況,此時要是能有個迭代語句豈不是很給力?難道我們要編寫一堆的Label1、Label2、Label3?
有需求,是好事情!咱們是做技術(shù)的,為需求而生,為滿足需求而戰(zhàn),沒有需求,技術(shù)就都只是浮云!
為什么條件語句和迭代語句要使用反射來實現(xiàn),因為在編寫這篇文章之前,老陳已經(jīng)決定了用反射來實現(xiàn)這些機(jī)制,無論您是否喜歡!Hah..
我們再次將模板語法復(fù)雜化如下:
1 <!--#include file="_Page_Header.shtml" --> 2 <ul> 3 <li>博主姓名:陳彥銘</li> 4 <li>創(chuàng)建日期:{CreationTime:yyyy年MM月dd日 HH:mm:ss}</li> 5 <li>粉絲數(shù)量:{FunsCount:D4}</li> 6 </ul> 7 <hr /> 8 <m:if test="{articles.Count > 0}"> 9 <ul> 10 <m:foreach item="article" collection="articles"> 11 <li><a href="{article.Url}">[{article.Category}] {article.Title}</a></li> 12 </m:foreach> 13 </ul> 14 </m:if> 15 <!--#include file="_Page_Footer.shtml" -->現(xiàn)在有木有感覺到某種給力呢?雖然這不是最給力的……
概念
雖然,我們只增加了兩種語法,而且不多,看起來也貌似并不復(fù)雜,但這背后將會牽扯到方方面面的問題,代碼解析的難度進(jìn)一步增加。我們有必要了解一下后面可能使用到的一些概念。
- 元素:即Element。它是組成模板的最基本的元素,派生出標(biāo)簽、表達(dá)式、Tag等。
- 標(biāo)簽:即Label。形如{item},或{item.getTitle()}等,標(biāo)簽不能嵌套使用,但標(biāo)簽內(nèi)的語法可以較為復(fù)雜;
- 表達(dá)式:即Expression。比如標(biāo)簽大括號之間的內(nèi)容,我們從本節(jié)課開始將其稱之為表達(dá)式,表達(dá)式是可以運行、計算的,例如我們可以輸入較為復(fù)雜的標(biāo)簽:{item.x * item.y};
- 運算符:有了表達(dá)式,那么我們就需要一些運算符支持,例如上述{item.x * item.y}中的 * 運算符;
- Tag:我們將Tag定義為形如<m:if></m:if>或<m:else/>這樣的表示形式,它的主要作用是提供流程控制支持;
- 特性:既然有Tag了,那么我們就需要特性,即Attribute,概念與xml/html中的Attribute一致,它通常為Tag提供可選參數(shù);
- 指令:即Command,其格式類似于Tag,只不過開始和結(jié)束標(biāo)記被我們替換為xml/html的注釋格式,形如:<!--#include file="_Page_Footer.shtml" -->;
這里我列出的概念可能還不夠完整,隨著課程的演進(jìn),我們再一一認(rèn)識他們!
再次提醒:如果之前的課程您沒有掌握,請一定多多研究、體會,如果有問題可以隨時通過評論提問,我會盡力解答!希望大家都能夠真正的看懂、學(xué)會!
優(yōu)點
解釋型模板引擎可以支持更加復(fù)雜的語法、指令,能夠滿足更多的需求。同時由于它不像編譯型模板引擎那樣會受到運行時環(huán)境的限制,解釋型模版引擎用途非常廣泛。
缺點
但是它有一個很大的缺陷就是其內(nèi)部使用了反射機(jī)制,雖然我們也試圖對反射進(jìn)行優(yōu)化(請參考:淺談.NET反射機(jī)制的性能優(yōu)化),但這并不能完全解決反射帶來的一系列問題,這就意味著在某些場合它無法獲得令人滿意。
小結(jié)
本課僅作為解釋型模板引擎的一個引子,也是我寫博過程中的一個緩沖。
在解析本課提出的兩個模板代碼的時候,您需要認(rèn)真的體會我們之前的課程并學(xué)會它們,否則越往后面您就會越發(fā)的感覺到吃力!
為了自己,不要偷懶!學(xué)習(xí)是自己的,不學(xué)就一定不是自己的!
解釋型模板引擎在我歸納的三大類模板引擎當(dāng)中是難度最高的,我會細(xì)心的完成每一節(jié)課程,進(jìn)度會比較慢,大家一定要耐著性子!我們的目標(biāo)是看懂、學(xué)會、掌握,而不是直接應(yīng)用它們!話說,樓主這一番苦心,大家是否表示贊同呢?
受到群里兄弟們的竭力邀請,老陳終于決定來分享一下.NET下的模板引擎開發(fā)技術(shù)。本系列文章將會帶您由淺入深的全面認(rèn)識模板引擎的概念、設(shè)計、分析和實戰(zhàn)應(yīng)用,一步一步的帶您開發(fā)出完全屬于自己的模板引擎。關(guān)于模板引擎的概念,我去年在百度百科上錄入了自己的解釋(請參考:模板引擎)。老陳曾經(jīng)自己開發(fā)了一套網(wǎng)鳥Asp.Net模板引擎,雖然我自己并不樂意去推廣它,但這已經(jīng)無法阻擋群友的喜愛了!
概述
本課我們主要討論“命令解釋器”的實現(xiàn)。命令就是指令,指令也是構(gòu)成更加復(fù)雜的模板引擎的基本元素之一。至此我們可以歸納出來,模板引擎在工作的過程中,首先將字符流轉(zhuǎn)換為Token流,然后再將Token流轉(zhuǎn)換為Element集合(也算是流),然后將特定的Element單獨拿出來或組合在一起形成指令、語句等。寫一個模板引擎,和寫一個小型的編譯器幾乎相當(dāng),因此我們需要耐心、細(xì)心!
目標(biāo)
解析并運行如下模板代碼結(jié)構(gòu):
文件"/_Page_Footer.shtml"包含的代碼:
<!--#include file="_Public_Footer.shtml" -->文件"/_Page_Header.shtml"包含的代碼:
<!--#include file="_Public_Header.shtml" -->文件"/_Public_Footer.shtml"包含的代碼:
</body> </html>文件"/_Public_Header.shtml"包含的代碼:
<!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>{UserName}的博客</title> </head> <body>文件"/Index.shtml"包含的代碼:
1 <!--#include file="_Page_Header.shtml" --> 2 <ul> 3 <li>博主姓名:{UserName}</li> 4 <li>創(chuàng)建日期:{CreationTime:yyyy年MM月dd日 HH:mm:ss}</li> 5 <li>粉絲數(shù)量:{FunsCount:D4}</li> 6 </ul> 7 <!--#include file="_Page_Footer.shtml" -->今天的模板內(nèi)容被切分成了5個部分,嵌套層次達(dá)到了3層,解析難度比較大。實際上在編寫本文之前,我自己的解釋型模板引擎在內(nèi)部是使用正則表達(dá)式的方式來實現(xiàn)嵌套指令解析的。不過,我們今天不會這么做了!
使用正則表達(dá)式實現(xiàn)
本節(jié)課的目的是說明命令解釋器的實現(xiàn),本著循序漸進(jìn)的原則,我們首先考慮使用正則表達(dá)式來實現(xiàn)“<!--#include file="_Page_Header.shtml" -->”命令,在以后的課程中我們將會學(xué)習(xí)更加復(fù)雜的代碼解析方法。
我們需要按照順序使用正則表達(dá)式遞歸的讀取和合并代碼文檔,具體實現(xiàn)如下:
1 /// <summary> 2 /// 表示 #Include 命令解釋器。 3 /// </summary> 4 public static class IncludeCommandParser 5 { 6 private static int _nestedCount; 7 8 /// <summary> 9 /// 處置包含文檔。 10 /// </summary> 11 /// <param name="templateString">包含模板代碼的字符串。</param> 12 /// <param name="basePath">處置包含命令時要使用的基準(zhǔn)路徑。</param> 13 /// <returns>返回 <see cref="string"/>。</returns> 14 public static string Parse(string templateString, string basePath) 15 { 16 if (String.IsNullOrWhiteSpace(templateString)) return String.Empty; 17 if (String.IsNullOrWhiteSpace(basePath)) return templateString; 18 19 if (Directory.Exists(basePath) == false) throw new DirectoryNotFoundException(); 20 21 return _ProcessSSIElement(templateString, basePath); 22 } 23 24 private static string _ProcessSSIElement(string templateString, string basePath) 25 { 26 if (_nestedCount > 10) return templateString; 27 28 var matches = Regex.Matches(templateString, @"<!--#include file=""([^""]+)""\s*-->", RegexOptions.IgnoreCase | RegexOptions.Singleline); 29 30 foreach (Match match in matches) 31 { 32 var file = new FileInfo(Path.Combine(basePath, match.Groups[1].Value.Replace('/', '\\'))); 33 34 if (file.Exists == false) continue; 35 36 var subTemplate = File.ReadAllText(file.FullName).Trim(); 37 38 subTemplate = _ProcessSSIElement(subTemplate, Path.GetDirectoryName(file.FullName)); 39 40 templateString = templateString.Replace(match.Groups[0].Value, subTemplate); 41 } 42 43 _nestedCount++; 44 45 return templateString; 46 } 47 }測試代碼:
1 [Test] 2 public void LoadFileTest() 3 { 4 var fileName = Path.Combine(Environment.CurrentDirectory, "Templates\\Index.shtml"); 5 6 Assert.AreEqual(File.Exists(fileName), true); 7 8 this._templateString = File.ReadAllText(fileName); 9 10 Assert.NotNull(this._templateString); 11 12 Trace.WriteLine(this._templateString); 13 14 Assert.Greater(this._templateString.IndexOf("{CreationTime:yyyy年MM月dd日 HH:mm:ss}", StringComparison.Ordinal), 0); 15 } 16 17 [Test] 18 public void ProcessTest() 19 { 20 this.LoadFileTest(); 21 22 Trace.WriteLine("本次輸出:"); 23 24 var basePath = Path.Combine(Environment.CurrentDirectory, "Templates"); 25 var templateEngine = TemplateEngine.FromString(this._templateString, basePath); 26 27 templateEngine.SetVariable("url", "http://www.ymind.net/"); 28 templateEngine.SetVariable("UserName", "陳彥銘"); 29 templateEngine.SetVariable("title", "陳彥銘的博客"); 30 templateEngine.SetVariable("FunsCount", 98); 31 templateEngine.SetVariable("CreationTime", new DateTime(2012, 4, 3, 16, 30, 24)); 32 33 var html = templateEngine.Process(); 34 Trace.WriteLine(html); 35 }運行結(jié)果:
1 <!--#include file="_Page_Header.shtml" --> 2 <ul> 3 <li>博主姓名:{UserName}</li> 4 <li>創(chuàng)建日期:{CreationTime:yyyy年MM月dd日 HH:mm:ss}</li> 5 <li>粉絲數(shù)量:{FunsCount:D4}個</li> 6 </ul> 7 <!--#include file="_Page_Footer.shtml" --> 8 9 本次輸出: 10 <!DOCTYPE HTML> 11 <html> 12 <head> 13 <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> 14 <title>陳彥銘的博客</title> 15 </head> 16 <body> 17 <ul> 18 <li>博主姓名:陳彥銘</li> 19 <li>創(chuàng)建日期:2012年04月03日 16:30:24</li> 20 <li>粉絲數(shù)量:0098個</li> 21 </ul> 22 </body> 23 </html>
運行結(jié)果達(dá)到了我們的期望值!
總結(jié)和代碼下載
本課只是簡單的介紹命令解釋器的實現(xiàn)思路,實際上還有其他很多辦法可以實現(xiàn)。
從下節(jié)課開始,我們將會接觸到更多代碼標(biāo)記的解析方式,每篇博文篇幅不會太長,但一定會挑重點、擊中要害!
本節(jié)課的內(nèi)容較為簡單,不提供代碼下載。
模板引擎系列教程的規(guī)模比原來預(yù)想的還要龐大,因為我不想僅僅帖出各種代碼就了事,希望能從更多的角度給大家分享。因此,該系列文章以后全部劃入周末寫作。平時只寫文字性內(nèi)容,或小篇幅技術(shù)文章。
希望大家能夠諒解!
解釋型模板引擎與置換型模板引擎最大的區(qū)別是它支持流程控制語句,其次是支持對象訪問(反射實現(xiàn))。今天我們的任務(wù)是設(shè)計模板語法,給我們的后續(xù)開發(fā)提供依據(jù)。
模板語法類型
模板語法設(shè)計
- {title};
- {item.Name};
- {item.GetSize()};
- {item.GetObject("string", false, 0, 0.12, DateTime.Now)};
- <!--#include file="../public_header.shtml" -->,用于引入包含文檔;
- <m:using css="../styles/common.css" combin="true" compress="false" />,為HTML量身定制,用于引入CSS文檔,這個可以滿足CSS文件拆分、合并、壓縮等需要;
- <m:using js="../styles/common.css" combin="true" compress="false" />,為HTML量身定制,用于引入JS文檔;
- <m:var name="age" value="30" />,理解為"30";
- <m:var name="age" value="{30}" />,理解為數(shù)字30;
- <m:var name="age" value="{DateTime.Now}" />;
- <m:if test="">...</m:if>;
- <m:else />;
- <m:elseif test="" />;
- <m:for from="0" to="10" index="i">...</m:for>
- <m:foreach var="item" collection="items" index="i">...</m:foreach>
總結(jié)
本節(jié)課沒有任何實戰(zhàn)性內(nèi)容,只是總結(jié)了一下幾天來我們完成的一些語法設(shè)計。這樣的語法其實很好理解,SGML格式的。只不過解析起來就沒有那么爽了!在構(gòu)建解析過程的時候我自己都覺得非常痛苦,一不小心就會搞錯,甚至不知道錯在哪里了!
后續(xù)課程有兩種方式展現(xiàn)給大家,一種是我直接帖出代碼實現(xiàn),一種是按照如上整理的內(nèi)容拆解成章節(jié)一一詳述。由于我拿不定注意,第一種方案怕大家學(xué)不到東西,第二種方案太慢。所以做個調(diào)查:持續(xù)關(guān)注本系列博文的同學(xué)請在評論里回復(fù)支持哪一種寫作方案。
總結(jié)
以上是生活随笔為你收集整理的由浅入深:自己动手开发模板引擎——解释型模板引擎的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 由浅入深:自己动手开发模板引擎——置换型
- 下一篇: Android中ExpandableLi