-
c#发现之旅第三讲—c#开发基于XSLT的代码生成器 (2)
本文示例源代码或素材下载
在本演示程序中,我们只是用程序目录下的一个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技术的威力。建议大家以后多多学习多多应用.
_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的代码为