基於T4模板的文檔生成

看了好幾個代碼自動生成的工具,用起來很方便,但有些方面仍是不夠自由;這些日子裏忙裏偷閒摸索了一番,我的覺的基於T4模板的代碼生成方案仍是不錯的。html

下面就看看這個T4究竟是什麼東東……編程

T4 = Text Template Transformation Toolkit數組

不知道電腦前的你是否接觸過Asp或jsp之類的動態網頁編程語言,我的感受就和那些動態網頁的的編寫思路差很少只不過那些編譯前是.asp、.aspx,或.jsp,這個T4編譯前是的擴展名是tt(.tt)網絡

先看一個簡單的tt文件app

Sample.ttdom

複製代碼
<#@ template debug="false" hostspecific="false" language="C#" #>
<#@ assembly name="System.Core" #>
<#@ import namespace="System.Text" #>
<#@ import namespace="System.Linq" #>
<#@ import namespace="System.Collections.Generic" #>
<#@ output extension=".cs" #>
using System;
namespace Artech.CodeGeneration
{
public class Program
{
static void Main(string[] args)
{jsp

<#
        for(int i=0;i<3;i++)
        {
        #>

Console.WriteLine("Hello, {0}","<#=GetWords() #>");
<#
}
#>
}
}
}
<#+
public string GetWords()
{
return "哈利波特 World !";
}編程語言

>

複製代碼ide

這就是一個簡單的tt文件,也就是常說的那個T4模板,經過模板引擎編譯後生成以下內容:工具

複製代碼
namespace Artech.CodeGeneration
{
public class Program
{
static void Main(string[] args)
{

Console.WriteLine("Hello, {0}","哈利波特 World !");
                    Console.WriteLine("Hello, {0}","哈利波特 World !");
                    Console.WriteLine("Hello, {0}","哈利波特 World !");

            }
    }

}
複製代碼
感受咋樣,和那種jsp/asp像不?

貌似這麼簡單的東西在生成代碼目標下(咱們的最終目標)也沒什麼做用,代碼的生成有不少都是基於動態的內容,不可能像上面例子那樣,一成不變,就一個hello world!

下面稍微介紹下基礎的知識,否則看起來挺爽的,可真下手實幹了才發現不是這麼回事,那就壞菜了,哈哈。

天比較熱,下面的這個小節就是基礎知識,差很少都是千篇一概的,我就Ctrl + C 和Ctrl + V 了,各位看官,內心鄙視一下及能夠了哈

咱們暫時使用微軟的一套模板引擎(網絡上也有好幾套其餘的T4模板引擎,資料較少,本人也沒過多的時間看,各位看官,有知道的話能夠對比下)。

首先添加引用的類庫:

Microsoft.VisualStudio.TextTemplating.10.0.dll

Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll

Microsoft.VisualStudio.TextTemplating.Modeling.10.0.dll

Microsoft.VisualStudio.TextTemplating.VSHost.10.0.dll

相信都看到了,這幾個DLL後面都帶了個10.0的小尾巴,其實就是Visual Studio 2010中的東西,微軟的T4模板時從VS2010開始被VS支持的,咱們也知道能借用上面的這幾個動態庫了。如今通常都用VS2012了,若是電腦上只安裝了VS2012的話,能夠引用這幾個類庫:

Microsoft.VisualStudio.TextTemplating.12.0.dll

Microsoft.VisualStudio.TextTemplating.Interfaces.10.0.dll

Microsoft.VisualStudio.TextTemplating.Interfaces.11.0.dll(注:上面三個dll須要再.net4.5的環境下使用,若是使用較低的.net版本的話可能找到某些類)

一個完整的T4模板基本上有這幾塊組成,它們基本上能夠分紅5類:指令塊(Directive Block)、文本塊(Text Block)、代碼語句塊(Statement Block)、表達式塊(Expression Block)和類特性塊(Class Feature Block)

一、指令塊(Directive Block)

和ASP.NET頁面的指令同樣,它們出如今文件頭,經過<#@…#>表 示。其中<#@ template …#>指令是必須的,用於定義模板的基本屬性,好比編程語言、基於的文化、是否支持調式等等。比較經常使用的指令還包括用於程序集引用的<#@ assembly…#>,用於導入命名空間的<#@ import…#>等等。

二、文本塊(Text Block)

文本塊就是直接原樣輸出的靜態文本,不須要添加任何的標籤。在上面的模板文件中,處理定義在<#… #>、<#+… #>和<#=… #>中的文本都屬於文本塊。好比在指令塊結束到第一個「<#」標籤之間的內容就是一段靜態的文本塊。

如上面Sample.tt文件中的這部份內容:

複製代碼
using System;
namespace Artech.CodeGeneration
{
public class Program
{
static void Main(string[] args)
{
……
複製代碼
三、代碼語句塊(Statement Block)

代碼語句塊經過<#Statement#>的形式表示,中間是一段經過相應編程語言編寫的程序調用,咱們能夠經過代碼語句快控制文本轉化的流程。在上面的代碼中,咱們經過代碼語句塊實現對一個數組進行遍歷,輸出重複的Console.WriteLine(「Hello, {0}」, 「Xxx」)語句。

通常的代碼塊都是循環輸出一些動態內容(如從其餘地方傳過來的參數,等……)

如sample.tt文件中的:

複製代碼
<#
for(int i=0;i<3;i++)
{
#>
Console.WriteLine("Hello, {0}","<#=GetWords() #>");
<#
}
#>
複製代碼
四、表達式塊(Expression Block)

表達式塊以<#=Expression#>的形式表示,經過它之際上動態的解析的字符串表達內嵌到輸出的文本中。好比在上面的foreach循環中,每次迭代輸出的人名就是經過表達式塊的形式定義的(<#= person#>)

五、類特性塊(Class Feature Block)

若是文本轉化須要一些比較複雜的邏輯,咱們須要寫在一個單獨的輔助方法中,甚至是定義一些單獨的類,咱們就是將它們定義在類特性塊中。類特性塊的表現形式爲<#+ FeatureCode #>,對於Hello World模板,獲得人名列表的InitializePersonList方法就定義在類特性塊中。

<#+
public string GetWords()
{
return "哈利波特 World !";
}

>

我的理解,類特徵塊就是定一些小的工具類,暫時在這個模板中使用一下,通常爲數據轉換,或其餘簡單處理

瞭解T4模板的「五大塊」以後,相信讀者對定義在HelloWord.tt中的模板體現的文本轉化邏輯應該和清楚了吧。

基礎的一些語法已經介紹的差很少了,具體的請各位百度或谷歌了……


模板有了,基礎類庫有了,那怎麼讓這個模板投入戰鬥呢?

複製代碼
class Program
{
public static void Main(string[] args)
{
Console.WriteLine("Hello World!");

// TODO: Implement Functionality Here
        TemplateMgmt.CSTemplatingEngineHost host = new TemplateMgmt.CSTemplatingEngineHost();
        Microsoft.VisualStudio.TextTemplating.Engine engine = new Microsoft.VisualStudio.TextTemplating.Engine();
        #region  這裏的設置是爲了動態的傳遞參數使用的
        host.Session = new Microsoft.VisualStudio.TextTemplating.TextTemplatingSession();
        host.Session.Add("content","世界你好");
        #endregion
    
        host.TemplateFileValue = "sample.tt";
        
        string templateContent = System.IO.File.ReadAllText("Sample.tt");
        
        
        string outputCode = engine.ProcessTemplate(templateContent,host);
        Console.WriteLine(outputCode);
        
        foreach (var element in host.Errors) {
            Console.WriteLine(element.ToString());
        }
        
        Console.Write("Press any key to continue . . . ");
        Console.ReadKey(true);
    }
}

複製代碼

這段代碼中涉及到了一個:CSTemplatingEngineHost類的定義以下:

複製代碼
using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Threading.Tasks;

namespace TemplateMgmt
{
//The text template transformation engine is responsible for running
//the transformation process.
//The host is responsible for all input and output, locating files,
//and anything else related to the external environment.
//-------------------------------------------------------------------------
internal class CSTemplatingEngineHost : Microsoft.VisualStudio.TextTemplating.ITextTemplatingEngineHost,Microsoft.VisualStudio.TextTemplating.ITextTemplatingSessionHost
{
//the path and file name of the text template that is being processed
//---------------------------------------------------------------------
///


/// 模板文件的路徑
///

internal string TemplateFileValue;
public string TemplateFile
{
get { return TemplateFileValue; }
}
//This will be the extension of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private string fileExtensionValue = ".txt";
public string FileExtension
{
get { return fileExtensionValue; }
}
//This will be the encoding of the generated text output file.
//The host can provide a default by setting the value of the field here.
//The engine can change this value based on the optional output directive
//if the user specifies it in the text template.
//---------------------------------------------------------------------
private Encoding fileEncodingValue = Encoding.UTF8;
public Encoding FileEncoding
{
get { return fileEncodingValue; }
}
//These are the errors that occur when the engine processes a template.
//The engine passes the errors to the host when it is done processing,
//and the host can decide how to display them. For example, the host
//can display the errors in the UI or write them to a file.
//---------------------------------------------------------------------
private CompilerErrorCollection errorsValue;
public CompilerErrorCollection Errors
{
get { return errorsValue; }
}
//The host can provide standard assembly references.
//The engine will use these references when compiling and
//executing the generated transformation class.
//--------------------------------------------------------------
public IList StandardAssemblyReferences
{
get
{
return new string[]
{
//If this host searches standard paths and the GAC,
//we can specify the assembly name like this.
//---------------------------------------------------------
//"System"

//Because this host only resolves assemblies from the 
                //fully qualified path and name of the assembly,
                //this is a quick way to get the code to give us the
                //fully qualified path and name of the System assembly.
                //---------------------------------------------------------
                typeof(System.Uri).Assembly.Location,
                typeof(System.Linq.Enumerable).Assembly.Location
            };
        }
    }
    //The host can provide standard imports or using statements.
    //The engine will add these statements to the generated 
    //transformation class.
    //--------------------------------------------------------------
    public IList<string> StandardImports
    {
        get
        {
            return new string[]
            {
                "System"
            };
        }
    }
    //The engine calls this method based on the optional include directive
    //if the user has specified it in the text template.
    //This method can be called 0, 1, or more times.
    //---------------------------------------------------------------------
    //The included text is returned in the context parameter.
    //If the host searches the registry for the location of include files,
    //or if the host searches multiple locations by default, the host can
    //return the final path of the include file in the location parameter.
    //---------------------------------------------------------------------
    public bool LoadIncludeText(string requestFileName, out string content, out string location)
    {
        content = System.String.Empty;
        location = System.String.Empty;

        //If the argument is the fully qualified path of an existing file,
        //then we are done.
        //----------------------------------------------------------------
        if (File.Exists(requestFileName))
        {
            content = File.ReadAllText(requestFileName);
            return true;
        }
        //This can be customized to search specific paths for the file.
        //This can be customized to accept paths to search as command line
        //arguments.
        //----------------------------------------------------------------
        else
        {
            return false;
        }
    }
    //Called by the Engine to enquire about 
    //the processing options you require. 
    //If you recognize that option, return an 
    //appropriate value. 
    //Otherwise, pass back NULL.
    //--------------------------------------------------------------------
    public object GetHostOption(string optionName)
    {
        object returnObject;
        switch (optionName)
        {
            case "CacheAssemblies":
                returnObject = true;
                break;
            default:
                returnObject = null;
                break;
        }
        return returnObject;
    }
    //The engine calls this method to resolve assembly references used in
    //the generated transformation class project and for the optional 
    //assembly directive if the user has specified it in the text template.
    //This method can be called 0, 1, or more times.
    //---------------------------------------------------------------------
    public string ResolveAssemblyReference(string assemblyReference)
    {
        //If the argument is the fully qualified path of an existing file,
        //then we are done. (This does not do any work.)
        //----------------------------------------------------------------
        if (File.Exists(assemblyReference))
        {
            return assemblyReference;
        }
        //Maybe the assembly is in the same folder as the text template that 
        //called the directive.
        //----------------------------------------------------------------
        string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), assemblyReference);
        if (File.Exists(candidate))
        {
            return candidate;
        }
        //This can be customized to search specific paths for the file
        //or to search the GAC.
        //----------------------------------------------------------------
        //This can be customized to accept paths to search as command line
        //arguments.
        //----------------------------------------------------------------
        //If we cannot do better, return the original file name.
        return "";
    }
    //The engine calls this method based on the directives the user has 
    //specified in the text template.
    //This method can be called 0, 1, or more times.
    //---------------------------------------------------------------------
    public Type ResolveDirectiveProcessor(string processorName)
    {
        //This host will not resolve any specific processors.
        //Check the processor name, and if it is the name of a processor the 
        //host wants to support, return the type of the processor.
        //---------------------------------------------------------------------
        if (string.Compare(processorName, "XYZ", StringComparison.OrdinalIgnoreCase) == 0)
        {
            //return typeof();
        }
        //This can be customized to search specific paths for the file
        //or to search the GAC
        //If the directive processor cannot be found, throw an error.
        throw new Exception("Directive Processor not found");
    }
    //A directive processor can call this method if a file name does not 
    //have a path.
    //The host can attempt to provide path information by searching 
    //specific paths for the file and returning the file and path if found.
    //This method can be called 0, 1, or more times.
    //---------------------------------------------------------------------
    public string ResolvePath(string fileName)
    {
        if (fileName == null)
        {
            throw new ArgumentNullException("the file name cannot be null");
        }
        //If the argument is the fully qualified path of an existing file,
        //then we are done
        //----------------------------------------------------------------
        if (File.Exists(fileName))
        {
            return fileName;
        }
        //Maybe the file is in the same folder as the text template that 
        //called the directive.
        //----------------------------------------------------------------
        string candidate = Path.Combine(Path.GetDirectoryName(this.TemplateFile), fileName);
        if (File.Exists(candidate))
        {
            return candidate;
        }
        //Look more places.
        //----------------------------------------------------------------
        //More code can go here...
        //If we cannot do better, return the original file name.
        return fileName;
    }
    //If a call to a directive in a text template does not provide a value
    //for a required parameter, the directive processor can try to get it
    //from the host by calling this method.
    //This method can be called 0, 1, or more times.
    //---------------------------------------------------------------------
    public string ResolveParameterValue(string directiveId, string processorName, string parameterName)
    {
        if (directiveId == null)
        {
            throw new ArgumentNullException("the directiveId cannot be null");
        }
        if (processorName == null)
        {
            throw new ArgumentNullException("the processorName cannot be null");
        }
        if (parameterName == null)
        {
            throw new ArgumentNullException("the parameterName cannot be null");
        }
        //Code to provide "hard-coded" parameter values goes here.
        //This code depends on the directive processors this host will interact with.
        //If we cannot do better, return the empty string.
        return String.Empty;
    }
    //The engine calls this method to change the extension of the 
    //generated text output file based on the optional output directive 
    //if the user specifies it in the text template.
    //---------------------------------------------------------------------
    public void SetFileExtension(string extension)
    {
        //The parameter extension has a '.' in front of it already.
        //--------------------------------------------------------
        fileExtensionValue = extension;
    }
    //The engine calls this method to change the encoding of the 
    //generated text output file based on the optional output directive 
    //if the user specifies it in the text template.
    //----------------------------------------------------------------------
    public void SetOutputEncoding(System.Text.Encoding encoding, bool fromOutputDirective)
    {
        fileEncodingValue = encoding;
    }
    //The engine calls this method when it is done processing a text
    //template to pass any errors that occurred to the host.
    //The host can decide how to display them.
    //---------------------------------------------------------------------
    public void LogErrors(CompilerErrorCollection errors)
    {
        errorsValue = errors;
    }
    //This is the application domain that is used to compile and run
    //the generated transformation class to create the generated text output.
    //----------------------------------------------------------------------
    public AppDomain ProvideTemplatingAppDomain(string content)
    {
        //This host will provide a new application domain each time the 
        //engine processes a text template.
        //-------------------------------------------------------------
        return AppDomain.CreateDomain("Generation App Domain");
        //This could be changed to return the current appdomain, but new 
        //assemblies are loaded into this AppDomain on a regular basis.
        //If the AppDomain lasts too long, it will grow indefintely, 
        //which might be regarded as a leak.
        //This could be customized to cache the application domain for 
        //a certain number of text template generations (for example, 10).
        //This could be customized based on the contents of the text 
        //template, which are provided as a parameter for that purpose.
    }

    #region ITextTemplatingSessionHost implementation

public Microsoft.VisualStudio.TextTemplating.ITextTemplatingSession CreateSession()
{
    return Session = new Microsoft.VisualStudio.TextTemplating.TextTemplatingSession();
}

public Microsoft.VisualStudio.TextTemplating.ITextTemplatingSession Session {

// get
// {
// throw new NotImplementedException();
// }
// set
// {
// throw new NotImplementedException();
// }
get;
set;
}

#endregion
}

}
複製代碼

就這樣一個基於自定義宿主(通常狀況下是在VS中編輯模板並生成的)的代碼生成器的雛形就出來了。

(注:我們在VS中新建了一個tt文件在保存的時候VS會自動爲咱們編譯生成,你們不妨一試),

目前還不能根據參數動態的生成代碼,後續章節再介紹,這大熱天的晚上,筆記本都燙手了,哎!!

本文借鑑了很多http://www.cnblogs.com/artech/archive/2010/10/23/1859529.html,感謝:蔣金楠(Artech)的分享

相關文章
相關標籤/搜索