由淺入深:自己動手開發模板引擎——解釋型模板引擎(二)


受到群里兄弟們的竭力邀請,老陳終於決定來分享一下.NET下的模板引擎開發技術。本系列文章將會帶您由淺入深的全面認識模板引擎的概念、設計、分析和實戰應用,一步一步的帶您開發出完全屬於自己的模板引擎。關於模板引擎的概念,我去年在百度百科上錄入了自己的解釋(請參考:模板引擎)。老陳曾經自己開發了一套網鳥Asp.Net模板引擎,雖然我自己並不樂意去推廣它,但這已經無法阻擋群友的喜愛了!

概述

本課我們主要討論“命令解釋器”的實現。命令就是指令,指令也是構成更加復雜的模板引擎的基本元素之一。至此我們可以歸納出來,模板引擎在工作的過程中,首先將字符流轉換為Token流,然后再將Token流轉換為Element集合(也算是流),然后將特定的Element單獨拿出來或組合在一起形成指令、語句等。寫一個模板引擎,和寫一個小型的編譯器幾乎相當,因此我們需要耐心、細心!

目標

解析並運行如下模板代碼結構:

  1. /_Page_Footer.shtml
  2. /_Page_Header.shtml
  3. /_Public_Footer.shtml
  4. /_Public_Header.shtml
  5. /Index.shtml

文件"/_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>創建日期:{CreationTime:yyyy年MM月dd日 HH:mm:ss}</li>
5 <li>粉絲數量:{FunsCount:D4}</li>
6 </ul>
7 <!--#include file="_Page_Footer.shtml" -->

今天的模板內容被切分成了5個部分,嵌套層次達到了3層,解析難度比較大。實際上在編寫本文之前,我自己的解釋型模板引擎在內部是使用正則表達式的方式來實現嵌套指令解析的。不過,我們今天不會這么做了!

使用正則表達式實現

本節課的目的是說明命令解釋器的實現,本着循序漸進的原則,我們首先考慮使用正則表達式來實現“<!--#include file="_Page_Header.shtml" -->”命令,在以后的課程中我們將會學習更加復雜的代碼解析方法。

我們需要按照順序使用正則表達式遞歸的讀取和合並代碼文檔,具體實現如下:

 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">處置包含命令時要使用的基准路徑。</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 }

運行結果:

 1 <!--#include file="_Page_Header.shtml" -->
 2 <ul>
 3     <li>博主姓名:{UserName}</li>
 4     <li>創建日期:{CreationTime:yyyy年MM月dd日 HH:mm:ss}</li>
 5     <li>粉絲數量:{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>創建日期:2012年04月03日 16:30:24</li>
20     <li>粉絲數量:0098個</li>
21 </ul>
22 </body>
23 </html>


運行結果達到了我們的期望值!

總結和代碼下載

本課只是簡單的介紹命令解釋器的實現思路,實際上還有其他很多辦法可以實現。

從下節課開始,我們將會接觸到更多代碼標記的解析方式,每篇博文篇幅不會太長,但一定會挑重點、擊中要害!

本節課的內容較為簡單,不提供代碼下載。


模板引擎系列教程的規模比原來預想的還要龐大,因為我不想僅僅帖出各種代碼就了事,希望能從更多的角度給大家分享。因此,該系列文章以后全部划入周末寫作。平時只寫文字性內容,或小篇幅技術文章。

希望大家能夠諒解!


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM