<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

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

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

      上次我們簡單的認識了一下置換型模板引擎的幾種情況,當然我總結的可能不夠完善,希望大家繼續補充。談到按流替代式模板引擎的原理但并沒有給出真正的實現。跟帖的評論中有一位朋友(Treenew Lyn)說的很好:“Token 解析其實是按一個字符一個字符去解析的”。的確是這樣,而且唯有這樣才能夠實現更加高效、更加準確的模板引擎機制。我們首先將模板代碼分解成一個一個的Token,然后按照順序形成Token流(順序集合),在輸出的時候替換規定好的語法標記即可。

      目的

      假定我們要處理的模板文件類似于如下格式(與上一節一樣):

      1 /// <summary>
      2 /// 模板文本。
      3 /// </summary>
      4 public const string TEMPLATE_STRING = @"<a href=""{url}"">{title}</a><br />";

      我們的目的是將它按照{xxx}這樣的標記分解成Token流。

      方案

      解決這個問題的方案大致有這么幾種:

      1. 最直觀的就是正則表達式;
      2. 比較拐彎但比正則快的就是split+各種技巧;
      3. 按(字符)流解析 ,即將字符流轉化為Token流;

      今天我們只討論第三種情況,第一種很簡單,第二種稍微復雜一點,但相信難不倒您!第三種做法只是不太常見,但如果您接觸過編譯原理(或搜索引擎開發),就不是那么陌生了。

      思路

      首先,我們看看這段模板代碼按字符流輸出回是怎樣的:

       1 // 實現代碼
      2 [Test]
      3 public void Test1()
      4 {
      5 var s = new StringBuilder();
      6
      7 foreach (var c in TestObjects.TEMPLATE_STRING)
      8 {
      9 // 這里我們用回車換行符將輸出隔開
      10 s.AppendLine(c.ToString(CultureInfo.InvariantCulture));
      11 }
      12
      13 Trace.WriteLine(s.ToString());
      14 }
      15
      16 /* 輸出結果如下
      17 <
      18 a
      19
      20 h
      21 r
      22 e
      23 f
      24 =
      25 "
      26 {
      27 u
      28 r
      29 l
      30 }
      31 "
      32 >
      33 {
      34 t
      35 i
      36 t
      37 l
      38 e
      39 }
      40 <
      41 /
      42 a
      43 >
      44 <
      45 b
      46 r
      47
      48 /
      49 >
      50 */

      這個結果顯然與我們期望的相差很遠(請留意飄紅的字符們),其實我們需要的結果是這樣的:

      1 <a href=" 
      2 {url}
      3 ">
      4 {title}
      5 </a><br />

      基本上我們可以總結出如下規律(為了容易理解,我們這里只考慮{xxx}標記):

      1. 從開始到"{"之前的部分算作一個Token;
      2. "{"、"}"之間的部分(含"{"、"}")算作一個Token;
      3. "}"、"{"之間的部分(不含"}"、"{")算作一個Token;
      4. "}"到結尾的部分算作一個Token;

      思路有了,那么算法如何實現呢?為了避免篇幅過長,我這里直接給出一個有限狀態機的解決方案。為了更加直觀的理解這個問題,請您現在將鼠標定位在字符串"<a href=""{url}"">{title}</a><br />"的開始處,然后使用方向鍵向右移動光標,觀察光標在每個位置(pos)的狀態,圖解如下:

      這里出現了4個狀態,分別是“開始”、“進入目標”、“脫離目標”和“結束”。而在實際編碼過程中,我們通常忽略開始和結束,因為這兩個狀態始終都是需要處理的,而且各有且僅有1次,直接硬編碼實現即可。

      題外話:如果您實在難以理解什么是“有限狀態機”的話,那么你可以簡單的理解為“狀態有限的機器(制)”,雖然這么說是非常不準確的,但這個可以幫助你去思考這個概念。另外可以參考“狀態機”。

      將字符流轉化為Token流的過程

      要利用有限狀態機,我們首先要定義一下業務狀態:

       1 /// <summary>
      2 /// 定義解析模式(即狀態)。
      3 /// </summary>
      4 public enum ParserMode
      5 {
      6 /// <summary>
      7 /// 無狀態。
      8 /// </summary>
      9 None = 0,
      10
      11 /// <summary>
      12 /// 進入標簽處理。
      13 /// </summary>
      14 EnterLabel = 1,
      15
      16 /// <summary>
      17 /// 退出標簽處理。
      18 /// </summary>
      19 LeaveLabel = 2
      20 }

      在這里我們定義了三個狀態,實際上只需要兩個。None這個狀態在實踐中沒有實際意義,只是為了在編碼過程中讓語義更加接近現實(面向對象編程中會有很多這種情況)。遇到"{"或"}"的時候就進行狀態變換,而每次狀態變換都需要做一些處理動作,下面是算法的主體骨架:

       1 // 這倆還需要解釋??
      2 private const char _LABEL_OPEN_CHAR = '{';
      3 private const char _LABEL_CLOSE_CHAR = '}';
      4
      5 [Test]
      6 public void Test2()
      7 {
      8 var templateLength = TestObjects.TEMPLATE_STRING.Length;
      9
      10 // 為了模擬光標的定位移動,我們在這里采用for而不是foreach
      11 // 在本例中用for還是foreach都無關緊要
      12 // 以后我們還會討論更加復雜的情況,到時候就需要用到while(bool)了!
      13 for (var index = 0; index < templateLength; index++)
      14 {
      15 var c = TestObjects.TEMPLATE_STRING[index];
      16
      17 switch (c)
      18 {
      19 case _LABEL_OPEN_CHAR:
      20 // ...
      21 this._EnterMode(ParserMode.EnterLabel);
      22 break;
      23
      24 case _LABEL_CLOSE_CHAR:
      25 // ...
      26 this._LeaveMode();
      27 break;
      28
      29 default:
      30 // ...
      31 break;
      32 }
      33 }
      34
      35 // 到達結尾的時候也需要處理寄存器中的內容
      36 // 這就是之前提到的硬編碼解決開始和結束兩個狀態
      37 // ...
      38 }

      在狀態變換之前,我們需要一系列的寄存器(臨時變量)來存儲當前狀態、歷史狀態(限于本例就是上次狀態)、歷史數據以及處理成功的Token等,定義如下:

       1 /// <summary>
      2 /// 表示 Token 順序集合(Token流)。
      3 /// </summary>
      4 private readonly List<string> _tokens = new List<string>();
      5
      6 // 為有限狀態機定義一個寄存器
      7 // 注意:有限狀態機的理解在物理層的電路上和在編程概念上是相通的
      8 private readonly StringBuilder _temp = new StringBuilder();
      9
      10 /// <summary>
      11 /// 表示當前狀態。
      12 /// </summary>
      13 private ParserMode _currentMode;
      14
      15 /// <summary>
      16 /// 表示上一狀態。
      17 /// </summary>
      18 /// <remarks>
      19 /// 如果狀態多余兩個的話,我們總不能再定義一個"_last_last_Mode"吧!
      20 /// 在狀態有多個的時候,需要使用 <see cref="Stack{T}"/> 來保存歷史
      21 /// 狀態,這個我們將在解釋型模版引擎中用到。
      22 /// </remarks>
      23 private ParserMode _lastMode;

      切換模式的時候需要對各個寄存器做相應的處理,我的注釋很詳細就不解釋了:

       1 /// <summary>
      2 /// 進入模式。
      3 /// </summary>
      4 /// <param name="mode"><see cref="ParserMode"/> 枚舉值之一。</param>
      5 private void _EnterMode(ParserMode mode)
      6 {
      7 // 當狀態改變的時候應當保存之前已處理的寄存器中的內容
      8 if (this._temp.Length > 0)
      9 {
      10 this._tokens.Add(this._temp.ToString());
      11
      12 this._temp.Clear();
      13 }
      14
      15 this._lastMode = this._currentMode;
      16 this._currentMode = mode;
      17 }
      18
      19 /// <summary>
      20 /// 離開模式。
      21 /// </summary>
      22 private void _LeaveMode()
      23 {
      24 // 當狀態改變的時候應當保存之前已處理的寄存器中的內容
      25 // 當狀態超過2個的時候,實際上這里的代碼應該是不一樣的
      26 // 雖然現在我們只需要考慮兩種狀態,但為了更加直觀的演示,我特意在這里又寫了一遍
      27 if (this._temp.Length > 0)
      28 {
      29 this._tokens.Add(this._temp.ToString());
      30 this._temp.Clear();
      31 }
      32
      33 // 因為只有兩個狀態,因此
      34 this._currentMode = this._lastMode;
      35 }

      然后再完善一下之前提到的主體骨架,測試,輸出結果如下:

      1 <a href="
      2 {url}
      3 ">
      4 {title}
      5 </a><br />

      我們得到了預期的結果!

      將Token流輸出為業務數據

      在上一節中我們曾經提到過Token流輸出時將標簽置換為業務數據的思路,如果您忘記了,那么請回去再看看吧!

      有了思路,那么實現就非常容易了,聯合業務數據進行測試:

       1 [Test]
      2 public void Test3()
      3 {
      4 this.ParseTemplate(TestObjects.TEMPLATE_STRING);
      5
      6 foreach (var newsItem in TestObjects.NewsItems)
      7 {
      8 foreach (var token in this._tokens)
      9 {
      10 switch (token)
      11 {
      12 case "{url}":
      13 Trace.Write(newsItem.Key);
      14 break;
      15
      16 case "{title}":
      17 Trace.Write(newsItem.Value);
      18 break;
      19
      20 default:
      21 Trace.Write(token);
      22 break;
      23 }
      24 }
      25
      26 Trace.WriteLine(String.Empty);
      27 }
      28 }

      經過測試輸出結果完全正確!

      搞定! 

      總結及代碼下載

      本文主要內容是闡述如何使用有限狀態機這種機制來完成“從字符流向Token流”的轉換的。不過本文為了降低入門門檻,一切舉例和算法都從簡,大家應該很容易上手!

      要補充的是,本文并沒有真正的去封裝一個模板引擎,而僅僅是說明了其工作原理,我想這個比直接給大家一個已經實現的模板引擎要好的多,畢竟這是“漁”而不是“魚”。

      本文代碼下載:置換型模板引擎(1-2).zip


      下集預報:置換型模板引擎(三)將于清明節之后放出,屆時將會封裝一個簡單但完整的基于“按流替代式”的模板引擎,達到實用級別。

      另外,請大家不要催促我博文的寫作,老陳畢竟不是打印機啊!哈哈!

       

      posted @ 2012-04-01 09:20  O.C  閱讀(4514)  評論(9)    收藏  舉報
      主站蜘蛛池模板: 精品偷拍一区二区三区| 亚洲国产欧美一区二区好看电影 | 日本一区不卡高清更新二区| 性色av无码久久一区二区三区| 精品亚洲一区二区三区在线观看| xxxx丰满少妇高潮| 成年午夜性影院| 柠檬福利第一导航在线| 欧美肥老太牲交大战| 67194熟妇人妻欧美日韩| 深夜福利成人免费在线观看| 亚洲色大成网站WWW永久麻豆| 最新国产精品中文字幕| 十八禁国产一区二区三区| 另类 专区 欧美 制服| 孝义市| 91精品国产自产91精品| 国产精品国三级国产av| 日韩在线视频线观看一区| 国产成人精品亚洲日本片| 国产精品久久露脸蜜臀| 午夜成人性爽爽免费视频| 四虎影视www在线播放| 99热精品毛片全部国产无缓冲| 精品国偷自产在线视频99| 国产老熟女国语免费视频| 啊灬啊灬啊灬快灬高潮了电影片段| 伊人春色激情综合激情网| 亚洲综合精品第一页| 韶关市| 亚洲av日韩av一区久久| 亚洲免费人成网站在线观看| 国产成人午夜福利院| 国产国产久热这里只有精品| 久九九精品免费视频| 国产日本一区二区三区久久| 国产一区日韩二区欧美三区| 人妻少妇偷人作爱av| 人成午夜免费视频无码| 大胸美女吃奶爽死视频| 伊人精品成人久久综合97|