模版,大家肯定都比較熟悉的一個概念,剛學C#(Java)那會老師就告訴我們,類是對象的模版。今天寫這個模版其實是我用於生成js代碼的,當然不限於生成js,其實跟codesmith有着差不多的功能,只是沒那么強大,下面我寫一些思路
我這個模版目前用於生成Ext的Grid和添加,編輯 表單,所以主角的又(怎么會是又呢,呵呵)是數據庫表,不過這些信息被我存到前一篇博客里提到的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 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]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就足夠了,但是我要稍微封裝一下,如下:

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 { get; set; }
public string TagName { get; set; }
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 < 0) throw new FormatException( string.Format( " 模版缺少@<{0}這一結束標記 ", tagName));
return new TagIndex { TagName = tagName, Source = str };
}
}
}
標簽找到之后,便是分析替換,像上面的[table](占位符類)標簽,我們當然直接替換就可以了,但是語法類的我們還得稍加分析才行,拿columnsEach舉例吧,上偽代碼
while (forTag.FindThisTag)
{
var forStr = forTag.RealContent;
string fields = string.Empty;
MathTag mathTag = new MathTag(forStr);
columns.ForEach(m =>
{
// 去替換里面的占位符類標簽
}
}
再來看看unlessLast標簽,定義這個標簽的用意在於,最后一行就不輸出標簽里面的內容,使用場景如:給表格加入列時,最后一列我們不需要加逗號,見下圖(當然不是只針對這種場景)
如上圖,第一處有“,” 第二處則沒有,要實現這一點,我們在模版文件里這樣寫
{
xtype: 'textfield',
fieldLabel: '[columnDescription]',
name: '[columnName]',
allowBlank: false,
value: null == rowObj ? null : rowObj.get('[columnName]'),
id: '[columnName]' + idstr
}@unlessLast:, @<unlessLast@<everyColumn
可能有人會說,這也太大材小用了吧,呵呵,希望你能提出意見,我現在只想到這種方式。下面把所有代碼粘過來,有需要的可以改改.

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;
}
}
}

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)