程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> .NET網頁編程 >> C# >> 關於C# >> C#發現之旅第三講 使用C#開發基於XSLT的代碼生成器(2)

C#發現之旅第三講 使用C#開發基於XSLT的代碼生成器(2)

編輯:關於C#

在本演示程序中,我們只是用程序目錄下的一個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技術的威力。建議大家以後多多學習多多應用.

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved