使用 XSLT 进行数据库每日构建报告






4.71/5 (3投票s)
2006 年 12 月 4 日
18分钟阅读

35891

304
演示如何使用 XSLT 在每日构建过程中创建 SQL 错误报告。
目录
引言
不久前,我工作的公司建立了一个每日构建过程,其中包括对 SQL 部署脚本的自动化测试。该过程会恢复每个客户数据库的最近副本,在数据库上运行一系列更改脚本,并将结果保存到 SQL 输出文件中(每个数据库一个)。
本文讨论了这些原始输出文件如何转换为每个开发人员的定制 HTML 错误报告(仅包含其 SQL 脚本中发现的错误),并自动电子邮件发送给开发人员。
附加的代码演示了此过程的实际运行情况。它还演示了三个之前的 C# 实用程序(RegexToXml、TransformXml 和 SendSMTP)。
大量使用了 XSL 转换。我学到了很多关于 XSLT 的知识,我将在本文中分享我的一些发现和技巧。
背景
在所有客户数据库上测试 SQL 部署脚本的价值
不久前,一位同事使用 FinalBuilder 创建了一个每日构建过程。除了构建可执行文件外,该过程还会在每个客户数据库的最近副本上测试 SQL 部署脚本。
在所有客户数据库上测试部署脚本是一个好主意,因为它大大降低了现场部署错误的风险。当出现错误时,在现场安装升级的开发人员可能会倾向于忽略非关键脚本错误,或者自己创建临时修复程序。这样,部署脚本中的意外错误可能导致不必要的架构差异(与因自定义、国家/地区特定法规等引起的不可避免的架构差异相反)。因此,拥有可靠的部署脚本具有巨大的价值。
实现切实的好处
我同事最初的意图是粗略检查每个输出文件中的错误。所有包含错误的输出文件都将发送给所有开发人员。因此,所有开发人员都必须单独打开每个文件并搜索错误消息,以查看他们是否对任何错误负责。这显然会浪费开发人员的时间,并且有些令人恼火——特别是由于错误通常会出现在所有数据库中,导致需要检查大量文件。
我妻子要出差 10 天。所以,在她离开期间,我决定在晚上和周末解决这个问题。
我的解决方案是编写三个通用的命令行实用程序,然后使用它们来生成个性化的错误报告并将其发送给每个受影响的开发人员。
这三个实用程序是:
- RegexToXml:用于解析 SQL 输出文件中的错误和警告,并将结果输出为每个数据库单独的 XML 文件。
- TransformXml:.NET 的
XslCompiledTransform
类的包装器。编写了 XSL 转换来:- 生成各种批处理文件,将过程连接起来(从中央 Databases.xml 和 Developers.xml 文件生成带有每个客户或开发人员命令的批处理文件)。
- 连接各种客户数据库的 XML 文件。
- 按开发人员、然后按数据库、然后按更改脚本重新排序 XML 节点。
- 为每个开发人员生成一个 HTML 文件,其中包含该开发人员更改脚本中错误的详细信息(按数据库分组)。
- SendSMTP:用于将 HTML 文件(作为电子邮件正文)发送给每个受影响的开发人员。
我已经在单独的文章中介绍了每个实用程序(只需关注上面的链接)。
本文将整个过程整合在一起。您可以使用附加的演示代码来查看实际过程。
使用代码
运行演示代码
所有批处理文件都应从 \BatchFiles 子文件夹运行。
运行 GenerateChangeScriptErrorsToEMail.bat 来查看正在生成的 HTML 错误报告。
运行 GenerateAndSendChangeScriptErrorReports.bat 来查看整个过程的实际运行情况,包括生成报告的电子邮件发送。**但是请注意**……当系统尝试发送电子邮件时,您将在控制台窗口中看到 .NET 错误消息。这是因为演示中的虚构开发人员都拥有虚构的电子邮件地址,并且 SMTP 主机地址也同样是虚构的!稍后,您将找到有关如何为您的系统定制该过程的说明。这将消除这些错误消息。
导航文件夹结构
\Metadata
- MetaData\Databases.xml 列出了四个虚构的数据库:Alpha、Beta、Gamma 和 Delta。
- MetaData\Developers.xml 列出了五个虚构的开发人员:Andrew、Barbara、Chris、Diane 和 Ed。
请注意,开发人员的姓名必须是字母数字,不能有空格。\[这是因为正则表达式使用 \w+ 来匹配开发人员的姓名。]
Developers.xml 还包含每个开发人员的电子邮件地址。
\ChangeScriptResults
这里包含在每个数据库上运行 SQL 部署脚本所产生的输出文件(好吧,说实话,这些文件是我手工制作的……但它们大致基于实际输出文件的部分)。
- AlphaResults.txt
- BetaResults.txt
- GammaResults.txt
- DeltaResults.txt
这是其中一个文件的简短摘录……
Andrew\Feb06\Feb20_ChartMetaDataColumn.sql
(49 rows affected)
Msg 2714, Level 16, State 4, Server dbserver, Line 2
There is already an object named 'CK_ChartMetaDataColumn_Side'
in the database.
Msg 1750, Level 16, State 1, Server dbserver, Line 2
Could not create constraint. See previous errors.
Andrew\Feb06\Feb20a_AddChartMetaDataColumn.sql
Chris\Feb06\Feb20a_SampleNumber.sql
(5236 rows affected)
Diane\Feb06\Feb20a_OperationsMenuItems.sql
(6 rows affected)
(1 row affected)
(25 rows affected)
(1 row affected)
(0 rows affected)
(1 row affected)
(0 rows affected)
(1 row affected)
(0 rows affected)
(1 row affected)
(0 rows affected)
(1 row affected)
(0 rows affected)
(1 row affected)
(0 rows affected)
Ed\Mar06\Mar06_DelUpdateTrigger.sql
SQR Software 有一个内部实用程序,可以将开发人员的更改脚本按正确的顺序连接起来,形成 SQL 部署脚本。它还在每个更改脚本之前添加一个 PRINT
语句,其中包含脚本的子路径(这就是为什么您会在上面的文本文件中看到脚本名称)。子路径以开发人员的姓名开头,这使得可以知道将错误消息路由给谁。
\RegularExpressions
- ChangeScriptErrorsRegex.txt
此文件包含用于从输出文件中提取开发人员姓名、更改脚本文件名和错误消息的正则表达式。与 RegexToXml 单独文章一起提供的单元测试演示了如何构造此正则表达式。
\Tools
包含三个(用 C# 编写的)命令行实用程序及其支持文件。
- AndrewTweddle.Tools.RegexToXml.Core.dll
- RegexToXml.exe
- RegexToXml.exe.config
- TransformXml.exe
- SendSmtp.exe
请注意,您需要 .NET 2.0 框架才能安装它们。
\XslTransforms
- CreateBatchFileToGenerateChangeScriptErrorsPerDatabase.xsl
- MergeDatabaseChangeScriptErrors.xsl
- GenerateChangeScriptErrorsByDeveloper.xsl
- CreateBatchFileToGenerateChangeScriptErrorsForDevelopers.xsl
- HtmlMessageStyle.xsl
- GenerateChangeScriptErrorsForDeveloperAsHtml.xsl
- CreateBatchFileToSendChangeScriptErrorReports.xsl
\BatchFiles
主批处理文件是 GenerateAndSendChangeScriptErrorReports.bat,它按照正确的顺序调用其他批处理文件和 EXE。
此文件夹中有 13 个批处理文件。为什么这么多?
主要原因不是您可能怀疑的模块化。相反,是为了方便定制。过程的某些部分取决于开发人员和客户列表。这些步骤放在单独的批处理文件中,每次运行过程时都会重新生成(基于 Developers.xml 和 Databases.xml 文件的内容)。
GenerateChangeScriptErrorsToEMail.bat 是另一个重要的批处理文件。它生成 HTML 报告,但不尝试将其发送出去。
\WIP
中间 XML 文件生成到此文件夹中。
\Reports
每个开发人员的 HTML 文件以及构建管理员的主报告都生成到此文件夹中。
- ChangeScriptErrorsForAndrew.htm
- ...
- MasterChangeScriptErrorsReport.htm
\Notes
包含一些可能有助于更好地理解该过程的文本文件。
- BatchFileSequence.txt
- CustomizingTheProcess.txt
定制代码以适应您的系统
[**注意:**另请参阅 Notes\CustomizingTheProcess.txt 以获取更多信息]
如果您仅运行 BatchFiles\GenerateAndSendChangeScriptErrorReports.bat,您将在控制台窗口中看到 .NET 错误消息列表。当系统尝试将报告发送给开发人员时,就会发生这种情况。这是因为 SMTP 服务器地址不适用于您的系统。(此外,MetaData\Developers.xml 中的电子邮件地址都是虚构的。)
MetaData\Developers.xml 是更新开发人员列表及其电子邮件地址的中心位置。为了演示目的,您可能希望将这些地址更改为指向您自己的电子邮件地址。
但是,没有一个中央元数据文件可以用于更新 SMTP 服务器地址。相反,您需要打开 XslTransforms\CreateBatchFileToSendChangeScriptErrorReports.xsl 并编辑以下行以更改 SmtpServerAddress
参数的默认值……
<xsl:param name="SmtpServerAddress" select="'MySmtpHost'" />
或者,您也可以简单地在命令行上传递该参数的值。为此,只需附加以下文本……
-a SmtpServerAddress=<YourSmtpServerAddressHere>
……到 CreateBatchFileToSendChangeScriptErrorReports.bat 中的单行。
SMTP 主机地址也硬编码在 BatchFiles\SendMasterChangeScriptErrorReportToAdministrator.bat 中。请注意,此批处理文件包含构建管理员的硬编码姓名和电子邮件地址。通过修改以下文本,同时修复这两个问题:
if exist "..\Reports\MasterChangeScriptErrorsReport.htm"
..\Tools\SendSmtp
-h MySmtpHost
-S "Master change script errors report"
-w "..\Reports\MasterChangeScriptErrorsReport.htm"
-t "Andrew@NoPlaceOnEarth.com|Andrew Tweddle"
-f AutomatedBuild@NoPlaceOnEarth.com
[请注意,-h 设置 SMTP 主机/服务器,-S 主题行,-w HTML 格式的电子邮件正文,-t “收件人”地址,-f “发件人”地址。]
您的 SMTP 主机可能需要其他参数,例如用户名和密码。SendSmtp
可能不支持您所需的所有参数。如果它不支持某个特定参数,您可以创建一个配置文件(SendSmtp.exe.config)并添加一个类似的节:
<configuration>
<system.net>
<mailSettings>
<smtp ...>
<network>
...
</network>
</smtp>
</mailSettings>
</system.net>
</configuration>
有关更多信息,请在 MSDN 库中查找 mailSettings
元素、SmtpSection
和 SmtpNetworkElement
。
这些步骤应该可以使演示的电子邮件发送部分正常工作。但是,如果您希望将此代码改编为自己日常构建过程的一部分,则需要执行更多步骤。
首先,您需要在 MetaData 子文件夹中填充 Developers.xml 和 Databases.xml。
其次,HTML 报告包含指向源代码的链接,旁边是每个 SQL 脚本。您需要修改该过程,使这些链接指向正确的位置。或者,您可能需要删除一个或两个链接,如果它们不适合您的情况。
一个链接是指向源代码的基于 Web 的副本(目前是 WebSVN,一个流行的 Subversion 存储库的 Web 客户端前端)。
另一个链接是指向本地硬盘上的文件(假设所有开发人员都使用相同的文件夹结构——尽管如果他们不使用,也有办法解决)。
您可以在 Notes\CustomizingTheProcess.txt 中找到有关修改或删除这些链接的更多详细信息。
检查代码结构
有关批处理文件内容的综合细分,请参阅 Notes\BatchFileSequence.txt,其中按调用顺序排列。
以下两个图也显示了批处理文件的调用顺序,以及过程中每个步骤的输入和输出。实线箭头表示控制流,而虚线表示数据流。
[是的,是的,我知道我没有正确使用流程图符号!虽然我使用了标准的流程图符号,但这**不是**打算成为标准的流程图。借用 Humpty Dumpty 的话:“当我使用一个符号时,它意味着我选择的意思——不多也不少。” 如果这困扰您,请忽略图表!]
第一个图显示了 BatchFiles\GenerateAndSendChangeScriptErrorReports.bat 中的步骤。
第二个图深入分析了 GenerateChangeScriptErrorsToEMail.bat。
XSLT 技巧与窍门
这是我第一次接触 XSLT——所以我自己仍然是初学者。但我仍然想分享一些我学到的关于 XSLT 的知识……
推荐参考资料
我最喜欢的在线教程来自 ZVON。这是一个快速上手的好方法。最棒的是,您可以将**整个教程下载**到您的硬盘上,并将其用作持续的参考。
在 TopXml 上也有许多出色的代码示例。我广泛使用了这些来解决 XSLT 中一些更困难的方面。
MSDN 库也提供了关于 XPath 和 XSLT 语法的有用参考资料。\[提示:在 MSDN 库的索引选项卡中搜索“XPath”或“xsl:”]
推荐工具
Visual Studio 2005 对编辑 XSLT,特别是调试 XSLT,提供了很好的支持。
XML Notepad 是我最近才发现的 Microsoft 的免费工具。一个特别好的功能是它的查找对话框,它可以接受 XPath 表达式。这显然可用于测试您的 XPath。*XML Notepad* 还支持将 XSL 转换应用于 XML 文件,以及比较两个 XML 文件。非常有用!
在开发此代码时,我发现我可以舒适地使用**TransformXml**、批处理文件和记事本进行操作。但有时,我也会在 Visual Studio 中运行**TransformXml**,以便能够单步调试 XSLT 文件。单步调试该文件的技巧是:
- 在 IDE 中打开 TransformXml 解决方案。
- 打开 TransformXml 的属性编辑窗体。
- 转到“调试”选项卡。
- 编辑“命令行参数”设置,以使用“-g+”开关(该开关启用 XSLT 调试模式)调用 TransformXml。
- 在调用
Transform
方法的XslCompiledTransform
上设置断点。 - 在调试模式下运行 TransformXml。
- 单步进入 XSLT 转换(例如,使用 F11 键)。
我的 XPath 初学者最佳技巧
XPath 表达式中的条件放在方括号之间。如果您在条件开头加上斜杠,它将从 XML 文档的根目录搜索,而不是从当前上下文搜索。因此,作为一般规则,**请勿以斜杠开头您的条件!**
这是 WIP\MergedChangeScriptErrors.xml 的一小段摘录。
<?xml version="1.0" encoding="utf-8"?>
<ScriptErrors xmlns:file="urn:mscorlib">
<Alpha>
<ChangeScript>
<FilePath>
<Text>Chris\May05\May24b_GetVehiclecostingManagementData.sql
</Text>
<Developer>Chris</Developer>
</FilePath>
<ErrorOutput>
...
检索所有 Developer
节点的 XPath 表达式如下所示:
/ScriptErrors/*/ChangeScript/FilePath/Developer
但是,具有较低级别的 Developer
元素的数据库的路径如下所示:
/ScriptErrors/*[ChangeScript/FilePath/Developer]
请注意,ChangeScript 前面的斜杠 (/) 已消失。如果我一段时间没做 XPath,这总是会难倒我!
在 XslTransforms\GenerateChangeScriptErrorsByDeveloper.xsl 中有一个示例。
<xsl:template name="goToNextDatabase" >
<xsl:param name="developer" select="''" />
<xsl:param name="prevDatabase" select="''" />
<xsl:for-each
select="/ScriptErrors/*
[ms:string-compare(name(.), $prevDatabase, '', 'i') > 0]
[ChangeScript/FilePath/Developer=$developer]">
<xsl:sort order="ascending" select="name(.)"/>
<xsl:if test="position()=1">
这段代码查找(按字母顺序)在 $prevDatabase 之后、具有给定开发人员($developer)的更改脚本错误的下一个数据库。剥离所有复杂的字符串比较代码,它基本上与前面的示例相同 XPath。
反转 XML 层次结构
我面临的一个更难解决的问题是重新排序节点,以便不再按 Database
、然后按 ChangeScript
、然后按 Developer
排序,如下所示……
<?xml version="1.0" encoding="utf-8"?>
<ScriptErrors>
<Alpha>
<ChangeScript>
<FilePath>
<Text>Chris\May05\May24b_GetVehiclecostingManagementData.sql
</Text>
<Developer>Chris</Developer>
</FilePath>
<ErrorOutput>
...
……而是将节点放在一个层次结构中,以 Developer
为顶部,然后是 Database
,然后是 ChangeScript
。如下所示:
<?xml version="1.0" encoding="utf-8"?>
<ScriptErrors>
<Developer name="Andrew">
<Database name="Alpha">
<ChangeScript name="Andrew\Nov05\Nov01_Scenario.sql">
...
关键限制是每个开发人员必须只有一个 Developer
元素。这排除了某些更简单的、会导致重复的方法。
这一定是一个非常普遍的情况。然而,我在网上或我下载的任何样本中都找不到任何讨论。最后,我不得不找到自己的解决方案。您可以在 XslTransforms\GenerateChangeScriptErrorsByDeveloper.xsl 中找到它。工作原理如下:
XSLT 对 <xsl:template>
元素有两种不同的用途。
如果该元素具有 match="SomeXPathExpression"
属性,则模板的内容将应用于匹配 XPath 表达式的节点。
另一方面,如果它只有 name="SomeTemplateName"
属性,它就像函数调用一样。与常规函数调用一样,此类模板可以调用其他模板。模板也可以递归调用自身。
为了解决重新排序问题,我创建了两个递归调用自身的模板:
goToNextDeveloper
goToNextDatabase
这是 goToNextDeveloper
的定义:
<xsl:template name="goToNextDeveloper" >
<xsl:param name="prevDeveloper" select="''" />
<xsl:for-each
select="/ScriptErrors/*/ChangeScript/FilePath/Developer
[ms:string-compare(., $prevDeveloper, '', 'i') > 0]">
<xsl:sort order="ascending" select="."/>
<xsl:if test="position()=1">
<xsl:call-template name="developerSection">
<xsl:with-param name="developer" select="." />
</xsl:call-template>
<xsl:call-template name="goToNextDeveloper">
<xsl:with-param name="prevDeveloper" select="." />
</xsl:call-template>
</xsl:if>
</xsl:for-each>
</xsl:template>
这会查找所有开发人员姓名排在 $prevDeveloper 之后的 Developer
元素。这些元素按字母顺序排序。然后**只**处理第一个节点。这样,无论某个开发人员有多个节点,都不会受到影响。
处理第一个节点的代码如下:
- 它调用
developerSection
模板。该模板创建Developer
元素并调用goToNextDatabase
,后者使用与goToNextDeveloper
非常相似的模式创建Database
子元素。 - 然后它递归调用自身,将当前节点的开发人员姓名作为 $prevDeveloper 参数传递。
这解决了反转层次结构的问题……但如果您知道有更简单的方法,请告诉我!
用于生成 HTML 的有用 XSLT 代码片段
XslTransforms\GenerateChangeScriptErrorsForDeveloperAsHtml.xsl 生成 HTML 报告。此转换接受一个 $developer 参数。它可以是特定开发人员的姓名,也可以是空白,在这种情况下将包括所有开发人员(这用于生成发送给构建管理员的主报告)。
此转换文件包含一些有用且有趣的片段……
命名锚点是 HTML 文档中的一个点,可以通过(使用 # 符号和锚点名称)进行导航。这些对于创建可导航的目录非常有用。以下片段可用于生成命名锚点:
<!-- Convert the parameters into a named HTML anchor
i.e. <A Name="$AnchorName" />
-->
<xsl:template name="CreateNamedAnchor">
<xsl:param name="AnchorName" />
<xsl:text disable-output-escaping="yes"><a name="</xsl:text>
<xsl:value-of select="$AnchorName" />
<xsl:text disable-output-escaping="yes">" /></xsl:text>
</xsl:template>
[请注意 disable-output-escaping
属性。这对于防止 XSLT 处理器自动将符号(如“<”)替换为“<”(这显然会破坏 HTML)至关重要。]
此模板在各种地方被调用。例如,以下是用于生成特定开发人员标题的片段:
<!-- Template for "Developer" elements -->
<xsl:template match="Developer">
<xsl:if test="(@name=$developer)or($developer='')" >
<xsl:call-template name="CreateNamedAnchor">
<xsl:with-param name="AnchorName" select="@name" />
</xsl:call-template>
<h1>Change script errors for <xsl:value-of select="@name"/></h1>
<xsl:apply-templates/>
</xsl:if>
</xsl:template>
以下片段对于创建超链接很有用,无论是指向外部文档(如源代码文件的 WebSvn 网页),还是指向命名锚点:
<!-- Convert the parameters into an HTML link
i.e. <A HRef="$LinkRef">$LinkText</A>
-->
<xsl:template name="CreateLink">
<xsl:param name="LinkRef" />
<xsl:param name="LinkText" />
<xsl:text disable-output-escaping="yes"><a href="</xsl:text>
<xsl:value-of select="$LinkRef" />
<xsl:text disable-output-escaping="yes">"></xsl:text>
<xsl:value-of select="$LinkText" />
<xsl:text disable-output-escaping="yes"></a></xsl:text>
</xsl:template>
我遇到的一个主要挑战是将 SQL 输出结果复制到网页。输出中可能存在换行符。由于 HTML 会忽略这些字符,因此您会发现提取的文本如下所示……
(4 rows affected)
(1 row affected)
(10 rows affected)
(1 row affected)
……在 HTML 页面中显示如下……
(4 rows affected) (1 row affected) (10 rows affected) (1 row affected)
幸运的是,我在 Mike Brown 的 lf2br 模板中找到了一个非常优雅的解决方案。它将输入字符串中的换行符替换为 <br/>
标签。这解决了问题。
lf2br
也帮助我解决了另一个问题。首先,我改编了它以创建一个通用的 replaceText
模板……
<!-- Based on the lf2br example from www.topxml.com -->
<xsl:template name="replaceText">
<!-- import $StringToTransform -->
<xsl:param name="StringToTransform"/>
<xsl:param name="TextToReplace" />
<xsl:param name="ReplacementText" />
<xsl:choose>
<!-- ignore if TextToReplace is the empty string -->
<xsl:when test="$TextToReplace=''">
<xsl:value-of select="$StringToTransform"/>
</xsl:when>
<!-- ignore if TextToReplace and ReplacementText
are identical
-->
<xsl:when test="$TextToReplace=$ReplacementText">
<xsl:value-of select="$StringToTransform"/>
</xsl:when>
<!-- if string contains text to replace,
recursively replace it
-->
<xsl:when test="contains($StringToTransform,
$TextToReplace)">
<xsl:value-of
select="substring-before($StringToTransform,
$TextToReplace)"/>
<xsl:value-of select="$ReplacementText" />
<!-- repeat for the remainder of the original string -->
<xsl:call-template name="replaceText">
<xsl:with-param name="StringToTransform">
<xsl:value-of
select="substring-after($StringToTransform,
$TextToReplace)"/>
</xsl:with-param>
<xsl:with-param name="TextToReplace">
<xsl:value-of select="$TextToReplace" />
</xsl:with-param>
<xsl:with-param name="ReplacementText">
<xsl:value-of select="$ReplacementText" />
</xsl:with-param>
</xsl:call-template>
</xsl:when>
<!-- ignore if string does not contain text to replace -->
<xsl:otherwise>
<xsl:value-of select="$StringToTransform"/>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
……然后我使用 replaceText
来帮助生成指向 WebSVN 中源代码的链接(一个流行的基于 Web 的 Subversion 存储库前端)。
更改脚本文件的子路径中的文件夹以反斜杠(\)分隔,但 Web 文件夹以正斜杠(/)分隔。replaceText
用于将反斜杠替换为正斜杠。
它在替换 WebSVN 地址和本地工作副本文件路径格式中的实际文件名(而不是占位符文本 FILEPATH
)方面也很有用。
以下代码片段演示了调用 replaceText
(以及之前讨论过的 CreateLink
)……
<!-- Create an HTML link to the source code (e.g. in WebSvn) -->
<xsl:template name="CreateLinkToSourceCode">
<xsl:param name="ChangeScriptName" />
<xsl:text> </xsl:text>
<xsl:variable name="subPathToChangeScript">
<xsl:call-template name="replaceText">
<xsl:with-param name="StringToTransform"
select="$ChangeScriptName" />
<xsl:with-param name="TextToReplace" select="'\'" />
<xsl:with-param name="ReplacementText" select="'/'" />
</xsl:call-template>
</xsl:variable>
<xsl:call-template name="CreateLink">
<xsl:with-param name="LinkRef">
<xsl:call-template name="replaceText">
<xsl:with-param name="StringToTransform"
select="$ChangeScriptUri" />
<xsl:with-param name="TextToReplace"
select="'FILEPATH'" />
<xsl:with-param name="ReplacementText"
select="$subPathToChangeScript" />
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="LinkText" select="'WebSvn'" />
</xsl:call-template>
<xsl:text> </xsl:text>
<xsl:call-template name="CreateLink">
<xsl:with-param name="LinkRef">
<xsl:call-template name="replaceText">
<xsl:with-param name="StringToTransform"
select="$WorkingCopyChangeScriptUriTemplate" />
<xsl:with-param name="TextToReplace"
select="'FILEPATH'" />
<xsl:with-param name="ReplacementText"
select="$subPathToChangeScript" />
</xsl:call-template>
</xsl:with-param>
<xsl:with-param name="LinkText"
select="'Working copy'" />
</xsl:call-template>
</xsl:template>
使用 XSLT 生成批处理文件
<xsl:output method="text">
标签可用于生成文本文件(如批处理文件),而不是 XML 或 HTML 文件。
有三个转换文件以这种方式生成批处理文件。其中之一是 CreateBatchFileToGenerateChangeScriptErrorsPerDatabase.xsl。它用于生成调用每个 SQL 输出文件的 RegexToXml
的批处理文件(即,为每个数据库创建初始 XML 文件)。输入文件是 MetaData\Databases.xml。这是 XSL 文件的内容:
<?xml version='1.0' encoding='utf-8' ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="text" />
<xsl:template match="Databases">
<xsl:for-each select="Database">
<xsl:text>if exist "..\ChangeScriptResults\</xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>Results.txt" </xsl:text>
<xsl:text>..\Tools\regextoxml -d Matches=</xsl:text>
<xsl:value-of select="@name"/>
<xsl:text> Match=ChangeScript -p- -m+ -w- -c- -r </xsl:text>
<xsl:text> -r "..\RegularExpressions\ChangeScriptErrorsRegex.txt"
</xsl:text>
<xsl:text> -i "..\ChangeScriptResults\</xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>Results.txt" -o "..\WIP\</xsl:text>
<xsl:value-of select="@name"/>
<xsl:text>ChangeScriptErrors.xml"
</xsl:text>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
<xsl:text>
元素用于“按原样”输出文本。它对于开始新行和缩进特别有用,因为前导和尾随空格不会像其他元素那样被删除。
连接 XML 文件
GenerateChangeScriptErrorsPerDatabase.bat 为每个数据库创建单独的 XML 文件来更改脚本错误。在进一步处理之前,需要将这些单独的文件合并到一个 XML 文件中。
这很容易做到:
<?xml version='1.0' encoding='utf-8' ?>
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:file="urn:mscorlib"
>
<xsl:template match="Databases">
<xsl:text>
</xsl:text>
<ScriptErrors>
<xsl:for-each select="Database">
<xsl:variable
name="ChangeScriptErrorsFile"
select="concat('..\WIP\',
concat(@name, 'ChangeScriptErrors.xml'))"
/>
<xsl:if test="file:Exists($ChangeScriptErrorsFile)">
<xsl:text>
</xsl:text>
<xsl:copy-of select="document($ChangeScriptErrorsFile)" />
</xsl:if>
</xsl:for-each>
<xsl:text>
</xsl:text>
</ScriptErrors>
</xsl:template>
</xsl:stylesheet>
请注意,file:exists
映射到 System.IO.File.Exists()
。这使用了 Microsoft XSL 实现的一个特性,称为扩展对象。XslCompiledTransform
类可以将 .NET 对象注册为其自身 XML 命名空间中的扩展对象。然后可以从 XSL 文档中调用这些对象的合适方法。
TransformXml
包装了 XslCompiledTransform
,并增加了额外的功能,允许静态类也注册为扩展对象。请参阅我关于**TransformXml** 的文章,其中详细介绍了如何使用**CodeDOM** 来实现这一点。
调用 **TransformXml** 的地方在 MergeDatabaseChangeScriptErrors.bat 中。
..\Tools\TransformXml
-i "..\MetaData\Databases.xml"
-s "..\XslTransforms\MergeDatabaseChangeScriptErrors.xsl"
-o "..\WIP\MergedChangeScriptErrors.xml"
-x urn:mscorlib;mscorlib.dll;System.IO.File
-d+
-x 开关后跟 XML 命名空间;程序集名称;类名称。该类必须是静态的,具有静态方法,或者有一个默认构造函数。
-d+ 开关是允许调用 document()
函数所必需的。出于安全原因,默认不允许此操作。
[深入研究过 RegexToXml 的读者可能会想,为什么我没有使用它的 -a 和 -f 开关来在生成 XML 文档时将它们追加到一起。这本可以避免需要此 XSL 转换。我同意这会是一个更好的解决方案。我没有这样做的原因很简单……我是在编写了构建错误报告实用程序后才向 RegexToXml
添加这些开关的!]
可能的增强功能
我很乐意添加以下一些功能:
- 创建一个元数据文件来配置 SMTP 主机地址和管理员地址等内容。这将使该过程完全可定制,而无需编辑源代码。
- 重写生成 SQL 输出文件的构建过程的部分(目前在 FinalBuilder 中),从而提供一个免费端到端的数据库构建过程。
- 重写为单个 C# 应用程序,而不是使用批处理文件,以比较这两种方法的开发速度、可读性和可维护性。
- 重写为 SSIS(SQL Server 2005 Integration Services)包。SSIS 对 XML 和 XSL 转换具有内置支持,还有一个非常简洁的图形化设计器用于将整个过程连接起来。
- 重新排列文件夹结构,以便生成批处理文件的代码与报告代码分开。
- 在 Products.xml 文件中添加对多个产品的支持。
然而,我认为这些功能中的任何一个都不会为我在那里工作的人或我自己(例如,通过提供实验和学习新技术的机会)增加太多价值。因此,我不太可能添加任何这些功能。
历史
2006 年 12 月 2 日
- 提交初始版本。