模版引擎之簡單實現


模版,大家肯定都比較熟悉的一個概念,剛學C#(Java)那會老師就告訴我們,類是對象的模版。今天寫這個模版其實是我用於生成js代碼的,當然不限於生成js,其實跟codesmith有着差不多的功能,只是沒那么強大,下面我寫一些思路

我這個模版目前用於生成Ext的Grid和添加,編輯 表單,所以主角的又(怎么會是又呢,呵呵)是數據庫表,不過這些信息被我存到前一篇博客里提到的Model里去了(通過特性的方式)。

        出於方便,我還是把Model的代碼貼一下: 

Model
// ------------------------------------------------------------------------------
//  <auto-generated>
//      This code generated by the tool, do not propose to amend
//        Generation time:2012/7/16 18:01:54
//  </auto-generated>
// ------------------------------------------------------------------------------
using System;
using System.Data;
using System.Runtime.Serialization;
using XDbFramework;
using System.Xml.Serialization;
using System.Diagnostics;
using System.CodeDom.Compiler;

namespace ExinSoft.Host.Model
{
    [Serializable]
    [Table(TableName =  " Agent " ,Descripton =  " 代理 ")]
      [GeneratedCodeAttribute( " System.Xml "" 2.0.50727.4927 ")]
    [DebuggerStepThroughAttribute()]
    [XmlRootAttribute(Namespace =  " http://www.scexin.com/ ", IsNullable =  true)]
    [DataContract(Namespace =  " http://www.scexin.com/ ")]
     public  partial  class Model_Agent
    {
        
        [Column(KeyType = KeyTypeEnum.PrimaryKey,ColumnName= " AgentID ",DbType=SqlDbType.BigInt, Index= 0,Description= " 編號 ")]
        [DataMember(Order =  0)]
         public   long? AgentID{ get; set;}
    
        
        [Column(ColumnName= " AgentLevelID ",ForeignKeyTableName= " AgentLevel ",ForeignKeyFiledName= " AgentLevelID ", DbType=SqlDbType.Int, Index= 1,Description= " 等級編號 ")]
        [DataMember(Order =  1)]
         public   int? AgentLevelID{ get; set;}
    
        
        [Column(ColumnName= " SuperiorAgentID ",ForeignKeyTableName= " Agent ",ForeignKeyFiledName= " AgentID ", DbType=SqlDbType.BigInt, Index= 2,Description= " 套餐編號 ")]
        [DataMember(Order =  2)]
         public   long? SuperiorAgentID{ get; set;}
    
        
        [Column(ColumnName= " CustomerID ",ForeignKeyTableName= " Customers ",ForeignKeyFiledName= " CustomerID ", DbType=SqlDbType.BigInt, Index= 3,Description= " 客戶編號 ")]
        [DataMember(Order =  3)]
         public   long? CustomerID{ get; set;}
    
        
        [Column(ColumnName= " AddTime ",DbType=SqlDbType.DateTime, Index= 4,Description= " 操作時間 ")]
        [DataMember(Order =  4)]
         public  DateTime? AddTime{ get; set;}
    
    }    
}

 

沒錯,這些信息被存放於儲如“[Column(ColumnName="AgentLevelID",ForeignKeyTableName="AgentLevel",ForeignKeyFiledName="AgentLevelID", DbType=SqlDbType.Int, Index=1,Description="等級編號")]”這行特性標記中,稍作解釋

ColumnName :列名,ForeignKeyTableName:外鍵表名,ForeignKeyFiledName:外鍵表的主鍵列名,DbType:字段類型,Description:描述

 有了這些基本信息,便夠了,現在我們就來解釋上一篇講到的數據庫表數據的瀏覽功能,主角(模版)登場 ,分步實現

1.定義模塊標簽,現在模版功能還不是很強大,沒有實現語法分析如if等,目前只是簡單的實現了for和標簽替換,上代碼 

模塊標簽定義
public  const  string ColumnEachTagName =  " columnsEach "// 循環列
         public  const  string DateColumnTagName =  " dateColumn ";   // 日期列
         public  const  string PkColumnTagName =  " pkColumn ";       // 主鍵列
         public  const  string TextColumnTagName =  " textColumn ";   // 文本列
         public  const  string BoolColumnTagName =  " boolColumn ";   // bool列
         public  const  string EveryColumnTagName =  " everyColumn "; // 所有列
         public  const  string NumberColumnTagName =  " numberColumn "; // 數字列
         public  const  string MoneyColumnTagName =  " moneyColumn ";   // money型列
         public  const  string ColumnNameTagName =  " [columnName] ";   // 列名
         public  const  string UnlessLastTagName =  " unlessLast ";     // 除非最后一個
         public  const  string TableTagName =  " [table] ";             // 表名
         public  const  string FkColumnTagName =  " fkColumn ";         // 外鍵列名
         public  const  string FkTableTagName =  " [fkTable] ";         // 外鍵表名
         public  const  string ColumnDescriptionTagName =  " [columnDescription] ";   // 列描述
         public  const  string PkColumnNameTagName =  " [pkColumnName] ";    // 主鍵列名

  

通過上面代碼,我們可以將標簽划分為三類

1.占位符類,如[table],[pkColumnName],[columnDescription]

2.語法類,如columnsEach,unlessLast

3.篩選類 如dateColumn,pkColumn,textColumn,boolColumn……

       2.寫模版文件 

模版文件
var [table]AddPostUrl =  ' /proc/[table]/?action=add ';
var [table]UpdatePostUrl =  ' /proc/[table]/?action=update ';
var [table]Form;
var [table]IdStr;
function initAdd[table]Form(containerid, idstr,rowObj) {
     if (!idstr)
        [table]IdStr = containerid;
     else
        [table]IdStr = idstr;
    [table]Form =  new Ext.FormPanel({
        labelWidth:  100//  label settings here cascade unless overridden
        url: [table]AddPostUrl,
        frame:  true,
        bodyStyle:  ' padding:5px 5px 0 ',
        buttonAlign:  ' left ',
        defaults: { width:  350 },
        defaultType:  ' textfield ',
        renderTo: containerid,
        items: [
@columnsEach:        
@pkColumn:
            {
                xtype:  ' hidden ',
                name:  ' [columnName] ',
                id:  ' [columnName] ' + idstr,
                value:  null == rowObj ?  null : rowObj. get( ' [columnName] '),
                readOnly:  true
            }@unlessLast:, @<unlessLast@<pkColumn
    @everyColumn:
            {
                xtype:  ' textfield ',
                fieldLabel:  ' [columnDescription] ',
                name:  ' [columnName] ',
                allowBlank:  false,
                value:  null == rowObj ?  null : rowObj. get( ' [columnName] '),
                id:  ' [columnName] ' + idstr
            }@unlessLast:, @<unlessLast@<everyColumn
    @dateColumn:
            {
                xtype:  ' datefield ',
                fieldLabel:  ' [columnDescription] ',
                name:  ' [columnName] ',
                allowBlank:  false,
                value:  null == rowObj || !Ext.isDate(rowObj. get( ' [columnName] ')) ?  null : rowObj. get( ' [columnName] ').dateFormat( ' Y-m-d H:i:s '),format: ' Y-m-d H:i:s ',
                id:  ' [columnName] ' + idstr
            }@unlessLast:, @<unlessLast@<dateColumn  
    @<columnsEach
            ],
            buttons: getAddOrEditButton( ' [table] ')
    });

@columnsEach:
    @fkColumn:
    $( ' #[columnName] ' + idstr).focus(function() {
        loadGrid( ' [fkTable] 'this, $( ' #hidden[columnName] ' + idstr)[ 0], ' [columnName] ');
    });@<fkColumn

@<columnsEach
            
            
if (Ext.getCmp(containerid +  ' _tab '))
    Ext.getCmp(containerid +  ' _tab ').add([table]Form);

}

 

看到這個模塊,也就清楚如果寫了,一個標記,我們得讓程序知道從哪里開始,又從哪里結束,所以我這里以@tagName:開始,@<tagName結束,所以我的TagIndex類里也依賴這一點

3.進行語法分析和替換動作

    這是很重要的一步了,要操作標簽,首先我們得找到標簽。很簡單,一個IndexOf就足夠了,但是我要稍微封裝一下,如下: 

找到標簽的類
using System;

namespace TemplateEngine
{
     public  class TagIndex
    {
         public  bool FindThisTag {  get {  return Start >=  0; } }
         public  int Start
        {
             get
            {
                 return Source.IndexOf( " @ " + TagName, System.StringComparison.Ordinal);
            }
        }
         public  int End
        {
             get
            {
                 return Source.IndexOf( " @< " + TagName, System.StringComparison.Ordinal); ;
            }
        }
         public  string Source {  getset; }
         public  string TagName {  getset; }
         public  string Content
        {
             get
            {
                 return Source.Substring(Start +  string.Format( " @{0}: ", TagName).Length, End - Start -  string.Format( " @<{0} ", TagName).Length);
            }
        }

         public  string RealContent
        {
             get
            {
                 return Source.Substring(Start, End - Start +  string.Format( " @<{0} ", TagName).Length);
            }
        }


         public  static TagIndex GetTag( string str,  string tagName)
        {
             var startOfAt = str.IndexOf( " @ " + tagName, System.StringComparison.Ordinal);
             if (startOfAt <  0)
                 return  new TagIndex { TagName = tagName, Source = str };
             var endOfAt = str.IndexOf( " @< " + tagName, System.StringComparison.Ordinal);
             if (endOfAt <  0throw  new FormatException( string.Format( " 模版缺少@<{0}這一結束標記 ", tagName));
             return  new TagIndex { TagName = tagName, Source = str };
        }
    }
}

 

 標簽找到之后,便是分析替換,像上面的[table](占位符類)標簽,我們當然直接替換就可以了,但是語法類的我們還得稍加分析才行,拿columnsEach舉例吧,上偽代碼

   var forTag = TagIndex.GetTag(str,  " columnEach ");
             while (forTag.FindThisTag)
            {
                 var forStr = forTag.RealContent;
                 string fields =  string.Empty;

                MathTag mathTag =  new MathTag(forStr);

                columns.ForEach(m =>
                {
                      // 去替換里面的占位符類標簽
                }

             }      

 再來看看unlessLast標簽,定義這個標簽的用意在於,最后一行就不輸出標簽里面的內容,使用場景如:給表格加入列時,最后一列我們不需要加逗號,見下圖(當然不是只針對這種場景)

 

 

如上圖,第一處有“,” 第二處則沒有,要實現這一點,我們在模版文件里這樣寫

@everyColumn:
            {
                xtype: 'textfield',
                fieldLabel: '[columnDescription]',
                name: '[columnName]',
                allowBlank:  false,
                value:  null == rowObj ?  null : rowObj.get('[columnName]'),
                id: '[columnName]' + idstr

                    }@unlessLast:, @<unlessLast@<everyColumn  

            

可能有人會說,這也太大材小用了吧,呵呵,希望你能提出意見,我現在只想到這種方式。下面把所有代碼粘過來,有需要的可以改改.

Generator
using System.IO;
using System.Text;
using XDbFramework;

namespace TemplateEngine
{
     public  class Generator
    {
         public  const  string ColumnEachTagName =  " columnsEach "// 循環列
         public  const  string DateColumnTagName =  " dateColumn ";   // 日期列
         public  const  string PkColumnTagName =  " pkColumn ";       // 主鍵列
         public  const  string TextColumnTagName =  " textColumn ";   // 文本列
         public  const  string BoolColumnTagName =  " boolColumn ";   // bool列
         public  const  string EveryColumnTagName =  " everyColumn "; // 所有列
         public  const  string NumberColumnTagName =  " numberColumn "; // 數字列
         public  const  string MoneyColumnTagName =  " moneyColumn ";   // money型列
         public  const  string ColumnNameTagName =  " [columnName] ";   // 列名
         public  const  string UnlessLastTagName =  " unlessLast ";     // 除非最后一個
         public  const  string TableTagName =  " [table] ";             // 表名
         public  const  string FkColumnTagName =  " fkColumn ";         // 外鍵列名
         public  const  string FkTableTagName =  " [fkTable] ";         // 外鍵表名
         public  const  string ColumnDescriptionTagName =  " [columnDescription] ";   // 列描述
         public  const  string PkColumnNameTagName =  " [pkColumnName] ";    // 主鍵列名

         private  readonly  string _template;

         public Generator( string templatePath)
        {
             using ( var reader =  new StreamReader(templatePath, encoding: Encoding.UTF8))
            {
                _template = reader.ReadToEnd();
            }
        }

         public  string Generate<T>()  where T :  class
        {
             var table = DalHelper.GetTableInfo( typeof(T));
             var columns = DalHelper.GetTypeColumns<T>();
             var str = _template.Replace(TableTagName, table.TableName);
             var pkColumn = columns.Find(m => m.KeyType == KeyTypeEnum.PrimaryKey);
             if (pkColumn !=  null)
                str = str.Replace(PkColumnNameTagName, pkColumn.ColumnName);

             var forTag = TagIndex.GetTag(str, ColumnEachTagName);
             while (forTag.FindThisTag)
            {
                 var forStr = forTag.RealContent;
                 string fields =  string.Empty;

                MathTag mathTag =  new MathTag(forStr);

                columns.ForEach(m =>
                {
                     string tmpStr =  string.Empty;
                    TagIndex tag = mathTag.GetTagIndexByColumn(m);
                     if (tag !=  null)
                    {
                        tmpStr = tag.Content
                            .Replace(ColumnNameTagName, m.ColumnName)
                            .Replace(ColumnDescriptionTagName, m.Description)
                            .Replace(FkTableTagName, m.ForeignKeyTableName);
                    }
                     var unlessLastTag = TagIndex.GetTag(tmpStr, UnlessLastTagName);
                     if (unlessLastTag.FindThisTag)
                    {
                         if (m == columns[columns.Count -  1])
                        {
                            tmpStr = tmpStr.Replace(unlessLastTag.RealContent,  string.Empty);
                        }
                         else
                        {
                            tmpStr = tmpStr.Replace(unlessLastTag.RealContent, unlessLastTag.Content);
                        }
                    }
                    fields += tmpStr;
                });

                str = str.Replace(forStr, fields);
                forTag = TagIndex.GetTag(str, ColumnEachTagName);
            }
             return str;
        }
    }
}

 

MathTag
using System;
using XDbFramework;

namespace TemplateEngine
{
     public  class MathTag
    {
         private  string _sourceString;
         private TagIndex pkTag;
         private TagIndex fkTag;
         private TagIndex textTag;
         private TagIndex dateTag;
         private TagIndex numberTag;
         private TagIndex moneyTag;
         private TagIndex boolTag;
         private TagIndex everyTag;


         public MathTag( string sourceString)
        {
            _sourceString = sourceString;
            pkTag = TagIndex.GetTag(sourceString, Generator.PkColumnTagName);
            fkTag = TagIndex.GetTag(sourceString, Generator.FkColumnTagName);
            textTag = TagIndex.GetTag(sourceString, Generator.TextColumnTagName);
            dateTag = TagIndex.GetTag(sourceString, Generator.DateColumnTagName);
            numberTag = TagIndex.GetTag(sourceString, Generator.NumberColumnTagName);
            moneyTag = TagIndex.GetTag(sourceString, Generator.MoneyColumnTagName);
            boolTag = TagIndex.GetTag(sourceString, Generator.BoolColumnTagName);
            everyTag = TagIndex.GetTag(sourceString, Generator.EveryColumnTagName);
        }

         public TagIndex GetTagIndexByColumn(ColumnAttribute m)
        {
             if (m.KeyType == KeyTypeEnum.PrimaryKey && pkTag.FindThisTag)
            {
                 return pkTag;
            }
             if ((m.CType ==  typeof(DateTime) || m.CType ==  typeof(DateTime?)) && dateTag.FindThisTag)
            {
                 return dateTag;
            }
             if ((m.CType ==  typeof( bool) || m.CType ==  typeof( bool?)) && boolTag.FindThisTag)
            {
                 return boolTag;
            }
             if ((m.CType ==  typeof( float) || m.CType ==  typeof( decimal) || m.CType ==  typeof( double) || m.CType ==  typeof( float?) || m.CType ==  typeof( decimal?) || m.CType ==  typeof( double?)) && moneyTag.FindThisTag)
            {
                 return moneyTag;
            }
             if ((m.CType ==  typeof( int) || m.CType ==  typeof( int?) || m.CType ==  typeof( long) || m.CType ==  typeof( long?)) && numberTag.FindThisTag)
            {
                 return numberTag;
            }
             if (m.CType ==  typeof( string) && textTag.FindThisTag)
            {
                 return textTag;
            }
             if (m.KeyType == KeyTypeEnum.ForeignKey && fkTag.FindThisTag)
            {
                 return fkTag;
            }  if (everyTag.FindThisTag)
            {
                 return everyTag;
            }
             return  null;
        }
    }
}

 好,這里整個過程也就差不多結束了(不知道怎么替換?當然是Replace)

 

 


免責聲明!

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



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