最近在開發自動化套件,想在自己寫的程式產生器中借用T4產生Code。

典型T4應用多發生於專案編輯階段,透過存檔動作或PowerShell Script產生程式碼。簡單嘗試後,發現T4早已設想周到,在程式中用T4產生文字是小菜一碟,透過Runtime Text Template(執行階段文字範本)即可輕鬆達成, MSDN有篇詳細說明可以參考。

我做了一個簡單範例,從PTT取得文章清單後,透過T4輸出成文字檔。

首先,在專案中新增一個Runtime Text Template: (在General分類下,直接用關鍵字"template"過濾比較快)

與傳統T4範本不同,每個Runtime T4範本都附加了同名的cs檔案,定義同名的類別物件(在本例中RTT4.tt範本,對應的類別名稱即為RTT4),如此便可在程式碼中建立範本物件RTT4,呼叫其.TransformText()方法就能獲得轉換結果。

使用Runtime T4時,呼叫端常需傳遞參數物件給T4範本,以達到動態變更產生內容的彈性。要實現此點,可透過T4類別為partial class的特性,在專案另外加入類別名稱相同partial class檔案(即上圖中的RTT4Code.cs),於其中另外定義內部欄位、屬性,並透過建構式接收參數。

範例先要定義文章資料物件:

    public class Post 
    {
        public string Date;
        public string Author;
        public string Subject;
    }

接著在partial class加入接收List<Post>的建構式,並宣告一個內部欄位posts,用以儲存文章清單。

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
 
namespace RuntimeT4
{
    partial class RTT4
    {
        private List<Post> posts;
        public RTT4(List<Post> posts)
        {
            this.posts = posts;
        }
    }
}

T4範本如下,注意其中的this.posts,就是我們在partial class中所定義的內部欄位。

<#@ template language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Collections.Generic" #>
<# 
int i = 1;
foreach (var p in this.posts) 
{ #>
<#= i++ #>.<#= p.Date #> / <#= p.Author #> / <#= p.Subject #>
<# } #>

呼叫端程式如下(結果抓資料部分比主體多出許多 orz),它會抓回PTT笨版第一頁,從HTML取出文章列表交由RTT4範本執行,.TransformText()後可得到一個字串,即為產出結果。

    class Program
    {
        static void Main(string[] args)
        {
            //連上PTT網站取回文章資料
              List<Post> posts = GetPTTPosts();
            
            //將文章資料當成參數傳給T4
            RTT4 t = new RTT4(posts);
            //執行TransformText()即可取得結果
    Console.WriteLine(t.TransformText());
            Console.Read();
        }
        //取得PTT文章清單
        static List<Post> GetPTTPosts()
        {
            WebClient wc = new WebClient();
            wc.Encoding = Encoding.GetEncoding(950);
            string h = wc.DownloadString(
                "http://www.ptt.cc/bbs/StupidClown/index1.html");
            List<Post> posts = new List<Post>();
            string date = null, author = null, subject = null;
            Regex re = new Regex(">(?<t>[^<]+?)<");
            Func<string, string> getInnerText = 
                s => re.Match(s).Groups["t"].Value;
            foreach (string line in h.Split('\n'))
            {
                if (line.StartsWith("<td width=\"50\">"))
                    date = getInnerText(line);
                else if (line.StartsWith("<td width=\"120\">"))
                    author = getInnerText(line);
                else if (line.StartsWith("<td width=\"500\">"))
                {
                    subject = getInnerText(line);
                    posts.Add(new Post()
                    {
                        Date = date,
                        Author = author,
                        Subject = subject
                    });
                }
 
            }
            return posts;
        }

執行結果:

1. 4/23 / vivianJ / 我大概是唯一一個被"阿"過的女生吧
2. 4/25 / purinfocus / 小護士回憶錄2
3. 4/27 / EchoMary / 話說今天期中考..
4. 5/05 / wubaiwife / 是任性還是笨﹖
5. 5/22 / m0630821 / [閒聊] 油表上的E是?
6. 6/13 / hohoholalala / 去郵局
7. 6/22 / cynthia730 / 語無倫次
8. 6/22 / chachalee / 你真的是我媽嗎?
9. 6/25 / superlubu / [動畫] 三人成虎大鬧台北第七集  慘劇
10. 8/04 / keikolin / [耍笨] 我終於當媽了
11. 8/05 / utou / [動畫]笨蛋的故事
12. 9/12 / okaoka0709 / [轉錄]Re: [火大] 我的姓真的很特別嗎
13. 9/25 / Liska / [童年] 中秋節烤肉= =|||
14.10/03 / JKH945 / [生日+笨夢] 19歲最後一個夢
15.10/28 / winniechi / [耍笨] 類疊的用法
16.11/10 / olivetrees / 我弟應該算是最早的詐騙集團
17.11/10 / keikoYAMADA / 古蹟修護系
18.12/12 / glenmarlboro / 突然想到一件笨事
19.12/14 / QQQWERT / [耍笨] 其實...應該很多人做過這種事
20.12/18 / Chucky9527 / [耍笨] 我的朋友的褲子拉鍊忘了關

學會這招,就能在自己的程式中輕鬆使用T4生Code,以後就不必用StringBuilder硬兜囉!

本日口號: T4好威呀!


Comments

Be the first to post a comment

Post a comment