在本演示程序中,我們只是用程序目錄下的一個Access2000數據庫作為例子,因此也只調 用了LoadFromAccesss2000這個函數,其他的分析SQLSERVER和ORACLE的函數沒用到。在未來 當這個代碼生成器經過改善而投入實際應用時,它就能分析SQLSERVER和ORACLE等企業級數據 庫了。
在主頁面xslcreatecode.aspx中定義了一個GetXMLString函數,它能將一個對象 序列化成一個XML文檔。這裡的DataBaseInfo,TableInfo和FieldInfo都能XML序列化。在執 行XML序列化時,系統會分析對象類型,遍歷對象所有的公開字段和可讀寫屬性,然後將這些 屬性值輸出到XML文檔,若遇到對象樹狀結構,則會遞歸遍歷這個樹狀結構,對對象中的每一 個下屬對象都會建立一個XML子元素進行輸出。在這裡DataBaseInfo,TableInfo和FieldInfo 構成了三層的樹狀結構,因此生成的XML文檔也是多層次的。
一般來說,序列化生成 的XML文檔中,XML元素的名稱等於對象類型的名稱和公開字段屬性的名稱,但可以通過添加 特性來改變這種默認行為,在類型TableInfo的定義前面加上了特性XmlType,在這裡指明了 為類型TableInfo生成的XML元素名稱不是對象類型名稱TableInfo,而是Table。
[System.Xml.Serialization.XmlType("Table")]
public class TableInfo
同樣的方式,我們為類型FieldInfo指定了XML元素名稱為Field ,這裡展示了特性在C#中的應用。關於特性在未來的某節課程中將講到。
由於能執行 XML序列化的屬性必須是可讀寫的,因此在類型FieldInfo中的IsString,IsInteger等屬性為 了能執行XML序列化,因此定義了毫無作用的set方法。
XSLT模板說明
程序目錄下 放置了一些以下劃線開頭的擴展名為XSLT的文件,這就是代碼生成器使用的代碼生成模板。 在主界面中使用不同的模板就能生成不同的代碼。在這裡我們以_cshaprhashtable.xslt為例 子進行說明。
_cshaprhashtable.xslt
首先我們在界面中選擇數據表Customers, 可以生成它的XML代碼為.
<Table xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
<Name>Customers</Name>
<Fields>
<Field>
<Name>Address</Name>
<Remark>地址</Remark>
<FieldType>Char</FieldType>
<IsString>true</IsString>
<IsInteger>false</IsInteger>
<IsBoolean>false</IsBoolean>
<IsNumberic>false</IsNumberic>
<IsDateTime>false</IsDateTime>
<IsBinary>false</IsBinary>
<ValueTypeName>System.String</ValueTypeName>
<FieldWidth>60</FieldWidth>
<Nullable>true</Nullable>
<PrimaryKey>false</PrimaryKey>
<Indexed>false</Indexed>
</Field>
<Field>
<Name>City</Name>
<Remark>城市</Remark>
<FieldType>Char</FieldType>
<IsString>true</IsString>
<IsInteger>false</IsInteger>
<IsBoolean>false</IsBoolean>
<IsNumberic>false</IsNumberic>
<IsDateTime>false</IsDateTime>
<IsBinary>false</IsBinary>
<ValueTypeName>System.String</ValueTypeName>
<FieldWidth>15</FieldWidth>
<Nullable>true</Nullable>
<PrimaryKey>false</PrimaryKey>
<Indexed>false</Indexed>
</Field>
<Field>其他字段....</Field>
</Fields>
</Table>
而模板_cshaprhashtable.xslt的代碼為
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<!--
根據表結構XML文檔創建影射數 據庫字段的 C# 代碼,內部使用 Hashtable 來存儲字段數據
編制 袁永福 2008- 1-17
-->
<xsl:template match="/*">
<xsl:if test="name(.) != 'Table' ">
<font color="red">本模板只能用於單表</font>
<br />
</xsl:if>
<textarea wrap="off" readonly="1" style="
border:1 solid black;
overflow=visible;
background-color:#dddddd">
<xsl:variable name="classname">
<xsl:value-of select="concat('DB2_' , Name )" />
</xsl:variable>
//*****************************************************************************
// 文件名 <xsl:value-of select="Name" />.cs
//*****************************************************************************
/// <summary>
/// 數據庫表 <xsl:value-of select="Name" />
<xsl:if test="Remark! =''">
<xsl:value-of select="concat(' [',Remark,']')" />
</xsl:if> 操作對象
/// </summary>
/// <remark>
/// 該表有<xsl:value-of select="count(Fields/Field) " />個字段
/// 編制: 代碼生成器
/// 時間:
///</remark>
[System.Serializable()]
public class <xsl:value-of select="$classname" />
{
///<summary>返回數據表名稱 <xsl:value-of select="Name" /></summary>
public static string TableName
{
get{ return "<xsl:value-of select="Name" />" ; }
}
///<summary>返回所有字段的名稱</summary>
<xsl:text>
public static string[]FieldNames
{
get
{
return new string[]{ </xsl:text>
<xsl:for-each select="Fields/Field">
<xsl:if test="position()>1">
<xsl:text>
,</xsl:text>
</xsl:if>
<xsl:text>"</xsl:text>
<xsl:value-of select="Name" />
<xsl:text>"</xsl:text>
</xsl:for-each>
<xsl:text>
};
}
}
#region 定義數據庫字段變量及屬性 //////////////////////////////////////////
private System.Collections.Hashtable myValues = new System.Collections.Hashtable();
</xsl:text>
///<summary>包含所有字段值的列表 </summary>
<xsl:text>
public System.Collections.Hashtable Values
{
get{ return myValues ;}
}
</xsl:text>
<xsl:for-each select="Fields/Field"><!-- 開始循環遍歷所有的字段定義信息 -- >
<xsl:variable name="remark">
<xsl:if test="string-length( Remark ) > 0 ">
<xsl:value-of select="concat( '(' , Remark , ')') " />
</xsl:if>
</xsl:variable>
///<summary>字段值 <xsl:value-of select="Name" />
<xsl:value-of select="$remark" />
<xsl:if test="PrimaryKey='true'">[關鍵字段]</xsl:if>
</summary>
public <xsl:value-of select="concat( ValueTypeName , ' ' , Name )" />
{
get{ return <xsl:choose>
<xsl:when test="ValueTypeName='System.Byte[]'">
<xsl:text>( </xsl:text>
<xsl:value-of select="ValueTypeName" />
<xsl:text> ) myValues["</xsl:text>
<xsl:value-of select="Name" />
<xsl:text>"]</xsl:text>
</xsl:when>
<xsl:otherwise>
<xsl:text>Convert.To</xsl:text>
<xsl:value-of select="substring-after( ValueTypeName , '.' )" />
<xsl:text>( myValues["</xsl:text>
<xsl:value-of select="Name" />
<xsl:text>"] )</xsl:text>
</xsl:otherwise>
</xsl:choose> ;}
set{ myValues["<xsl:value-of select="Name" />"] = value;}
}
</xsl:for-each>
#endregion
}// 數 據庫操作類 <xsl:value-of select="$classname" /> 定義結束
</textarea>
</xsl:template>
</xsl:stylesheet>
則生成的代碼文本為
//****************************************************************** ***********
// 文件名 Customers.cs
//*****************************************************************************
/// <summary>
/// 數據庫表 Customers 操作對象
/// </summary>
/// <remark>
/// 該表有13個字段
/// 編制: 代碼生成器
/// 時間:
///</remark>
[System.Serializable()]
public class DB2_Customers
{
///<summary>返回數據表名 稱 Customers</summary>
public static string TableName
{
get{ return "Customers" ; }
}
///<summary>返回所有字段的名稱</summary>
public static string[] FieldNames
{
get
{
return new string[]{ "Address"
,"City"
,"CompanyName"
,"ContactName"
,"ContactTitle"
,"Country"
,"CustomerID"
,"Email"
,"Fax"
,"Phone"
,"PostalCode"
,"Region"
,"WebSite"
};
}
}
#region 定義數據庫字段變量及屬性 //////////////////////////////////////////
private System.Collections.Hashtable myValues = new System.Collections.Hashtable();
///<summary>包含所有字段值的列表</summary>
public System.Collections.Hashtable Values
{
get{ return myValues ;}
}
///<summary>字段值 Address(地址)</summary>
public System.String Address
{
get{ return Convert.ToString( myValues["Address"] ) ;}
set{ myValues ["Address"] = value;}
}
-------- 其他字段 ---------- -----------
#endregion
}// 數據庫操作類 DB2_Customers 定義結束
現在我們根據前因後果來說明其中的過程。在XSLT文件中,唯一的一個 xsl:template模板定義塊命中XML文檔的根節點,然後使用name函數來測試當前節點的名稱是 否為Table,若不是則輸出“本模板只能用於單表”的提示信息。XSLT沒有return 語句,只能按順序執行下去,因此不管XML文檔是否正確還是繼續輸出。
接著輸出 textarea標簽,由於使用HTML格式顯示代碼,而這裡使用textarea元素,我們就不用做尖括 號字符轉義了。
這裡有xsl:variable 元素,說明開始定義一個變量,該變量的名稱 是classname,也就是這個C#類的名稱。XSLT變量不是真的變量,應當算是常量,其值是不能 改變的。這裡還用到了concat函數,這是XSLT中字符串連接函數,用於將多個字符串拼湊起 來,這個函數的參數個數不固定,但必須等於或超過2個。函數裡面可以使用XPath路徑來引 用某個XML節點,用單引號來定義固定的字符串。
然後XSLT輸出代碼開頭的一些說明 性的注釋文本,這裡是用了count函數來累計所有的字段個數。接著開始輸出C#代碼了,在輸 出類型定義中使用了xsl:value-of標記來輸出類型名稱,它是用$classname來引用剛才定義 的名為classname的XSLT變量。然後我們輸出靜態屬性TableName返回數據表的名稱。
在輸出FieldNames屬性時,我們遍歷輸出了字段名稱,這裡有個判斷,若position函數的值 大於1則在字段名稱前輸出一個逗號,position函數用於返回當前處理的節點在整個要處理的 節點列表中的從1開始的序號。若position值大於1,則表示不是輸出第一個字段名稱,由於 這裡是定義一個字符串數組的,因此需要添加逗號。
在這裡我們使用xsl:text來輸出 純文本數據,這裡輸出逗號時可以不需要使用xsl:text元素,可以直接輸出,但使用 xsl:text元素可以改善XSLT代碼的可讀性。而且xsl:text元素可以輸出連續的空白字符。
接著我們輸出myValues 變量和Values屬性,然後又開始遍歷字段輸出各個字段的屬 性代碼了。
首先我們定義了一個名為remark的變量,用於保存字段說明文本。然後輸 出字段屬性的說明性注釋,並判斷PrimaryKey元素值,若該元素值為true則輸出文本“ 關鍵字段”。然後輸出public 屬性數據類型 屬性名稱,這裡是用concat函數來連接屬 性數據類型和字段名稱,中間加了一個空格。
這裡我們使用了xsl:choose結構, xsl:choose類似C#中的swith語句,下面的xsl:when 類似C#的case 語句,而xsl:otherwise 類似C#的default語句。Xsl:choose 可以包含若干個xsl:when,而且最多包含一個 xsl:oterwise元素。每一個xsl:when都可以進行各自的判斷,這點和switch不同,更像連續 的if else ifelse 語句。
在這裡,第一個xsl:when判斷字段類型名稱是否是 System.Byte[],也就是字節數組,若是字節數組則輸出字節數組強制轉化的C#代碼。剩余的 情況使用xsl:otherwise來輸出使用Convert.To函數進行類型轉換的C#代碼。
經過上 述的XSLT轉換處理,我們就能根據描述數據表和字段設計信息的XML文檔來自動生成C#代碼了 。把這個C#代碼復制到C#工程中就可以編譯了。這段代碼內部使用一個哈西表來保存字段的 值,並使用一個個屬性來影射數據庫表的字段。
在程序目錄下存在一些類似的模板, 比如_cshapr.xslt就是生成另外一種的C#代碼的模板,在生成的代碼中不是用哈西列表保存 數據,而是是用一個個變量來保存字段數據。_java.xslt是生成JAVA代碼的,_VB6.xslt是生 成VB6代碼的。
_HTML.xslt
程序目錄下還有其他的代碼生成器模板,例如 _HTML.xslt能生成HTML代碼,能是用表格的樣式來展示多個表結構信息。該 模板有個特點就 是既能生成單個表的信息,也能生成整個數據庫中所有表的信息。該模板的XSLT代碼為
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:output indent='yes' />
<xsl:template match="/*">
<xsl:for-each select="//Table">
<xsl:call-template name="table" />
</xsl:for-each>
</xsl:template>
<xsl:template name="table">
<div>
<span>
<span>數據表 </span>
<b>
<xsl:value-of select="Name" />
</b>
<span> 結構,共 </span>
<xsl:value-of select="count( Fields/Field )" />
<span> 個字段 </span>
<xsl:value -of select="concat(' ', Remark)" />
<xsl:if test="count( Fields/Field[PrimaryKey='true']) > 0 ">
<span>關鍵字段:</span>
<b>
<xsl:for-each select="Fields/Field[PrimaryKey='true']">
<xsl:if test="position()> 1 ">,</xsl:if>
<xsl:value-of select="Name" />
</xsl:for- each>
</b>
</xsl:if>
</span>
<table style="border: black 1 solid"
border="0" width="100%"
rules='all' cellspacing="0"
cellpadding="0" bordercolor="#000000">
<tr bgcolor="#eeee99">
<td width="150">字段名</td>
<td width="80">字段類型</td>
<td width="50">長度</td>
<td width="80">可否為空</td>
<td width="80">是否索引</td>
<td>說 明</td>
</tr>
<xsl:for- each select="Fields/Field">
<xsl:sort select="Name" />
<tr valign="top">
<xsl:attribute name="bgcolor">
<xsl:iftest="position() mod 2 = 0">#eeeeee</xsl:if>
</xsl:attribute>
<td>
<font>
<xsl:if test="PrimaryKey='true'">
<xsl:attribute name="color">red</xsl:attribute>
</xsl:if>
<xsl:value-of select="concat(' ' , Name)" />
</font>
</td>
<td>
<xsl:value-of select="FieldType" />
</td>
<td>
<xsl:value-of select="FieldWidth" />
</td>
<td>
<xsl:if test="Nullable='true'">是</xsl:if>
</td>
<td>
<xsl:if test="Indexed='true'">是</xsl:if>
</td>
<td>
<xsl:value-of select="Remark" />
<xsl:if test="Description !=''">
<br />
<xsl:value-of select="Description" />
</xsl:if>
</td>
</tr>
</xsl:for-each>
</table>
</div>
<p />
</xsl:template>
</xsl:stylesheet>
現對該模板進行詳細說 明。在這個模板中有兩個xsl:template,也就定義了兩個子模板,其中第一個直接使用match 命中XML文檔的根節點,而第二個xsl:template創建一個名為table的子模板。按照面向過程 的軟件開發思想,第一個模板就是主函數,而第二個模板就定義了一個名為table的函數,主 函數內部調用了這個子函數。
在主模板中,首先是用XPath路徑//Table來查找XML文 檔中所有的名為Table的節點。在XPath中使用兩個斜線表示查找XML文檔中的當前節點下的所 有子孫節點。對於查到的每個Table元素都是用xsl:call-template來調用名為table的子模板 。這很類似C語言中主函數調用子函數一樣。
XSLT轉換過程進入的了table的子模板中 ,首先輸出DIV,輸出一些數據表的說明文字。這裡還是用count( Fields/Field [PrimaryKey='true']) > 0 來判斷該表是否有關鍵字段,若有關鍵字段則輸出 關鍵字段的名稱。
然後輸出table標簽,輸出第一行表格,然後查找遍歷所有的字段 元素,對每一個字段XML元素,首先輸出tr標簽,這裡還是用了xsl:attribute元素來設置TR 元素的屬性值。這裡用了name屬性來指明輸出的是tr元素的名為bgcolor的屬性值,也就是表 格行的背景色。裡面是用 xsl:if 來輸出該屬性值的內容,這裡的判斷條件是 position() mod 2 = 0 ,position()函數返回從1開始的當前節點的序號,mod 表示數學取模運算,因此 若當前節點的序號是二的倍數則輸出其內容,也就是#eeeeee ,這是一個淺灰色的顏色值。 這樣我們就可以讓表格行每隔一行就設置bgcolor屬性為#eeeeee,實現了表格行的顏色交替 變化。
然後我們開始輸出單元格,第一個單元格輸出字段名,這裡我們又是用了 xsl:attribute,使得若字段為關鍵字段則使用紅色顯示字段名稱。剩下的欄目則原樣輸出。
這樣我們定義了兩個子模板來輸出HTML代碼,能顯示一個或全部的數據表的字段定義 信息。
其實我們可以將兩個子模板合並為一個模板,將子模板的XSLT代碼復制到主模 板的xsl:for-each裡面,也也能用一個模板來輸出一個或全部的數據表的代碼。
類似 的在模板_sql.語句.xslt也定義了兩個模板,使得能輸出一個或全部數據表的SQL語句。
從這些例子可以看出,XSLT代碼有點類似面向過程的編程語言。其中有邏輯判斷,循 環,條件開關列表,主函數,子函數,可以說是用XML來書寫的程序代碼。XSLT只能根據另外 一個XML文件輸出純文本文檔。
運行程序
大家獲得程序源代碼後,設置虛擬目錄 ,並設置安全權限,使得程序能讀取程序目錄下的文件,也不用作其它配置即可運行。部屬 完畢後我們就可以在浏覽器中查看這個代碼生成器的運行結果了。
用戶界面中列出了 演示數據庫中所有的數據表名稱,並列出了可用的模板名。大家可以選擇某個數據表名或整 個數據庫,然後再選擇某個模板,點擊“創建代碼”按鈕,即可在按鈕下面看到 生成的以HTML方式顯示的自動生成的文檔。
大家可以仿造已有的XSLT模板編制自己的 代碼生成模板,保存的文件名以下劃線開頭,XSLT為擴展名。放置到程序目錄下,點擊 “刷新系統”按鈕就可使用剛剛添加的代碼生成模板。這也算做是一種熱拔插系 統了。
本代碼生成器的特點是程序代碼是少量的,功能是強大的,配置是靈活的,移 植也是簡單的。由於核心是采用XSLT的,因此很容易翻譯到JAVA,VB.NET,PHP等其它B/S程 序。添加和刪除模板的操作也是很簡單的。大家可以根據各自需要,將這個代碼生成器進行 一些擴展即可投入實際運用了。
小結
在本課程中,我們使用了C#和XSLT技術開發 了一個可投入實際運用的代碼生成器了。該代碼生成器結構簡單,功能強大。使我們更深入 的發現了XSLT技術的威力。建議大家以後多多學習多多應用.