我正在努力将Windows服务VDPROJ迁移到WiX。
我能够使用HEAT将Windows Service项目的输出收集到片段中。目前,为了使我的自定义操作正常工作,我手动将一些生成的GUID从Heat生成的文件更改为主Product.wxs中引用的已知字符串。
我需要在每次构建时以编程方式执行此操作,而不是依赖于手动干预,因为我需要将WiX项目集成到我们的连续构建服务器中。
根据我的研究,我可以在HEAT的输出上使用XSLT转换来实现我的需要,但是我很难让我的XSLT转换工作。
以下是未使用XSLT转换生成的片段的一部分
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
[...]
<Fragment>
<ComponentGroup Id="Windows.Service.Binaries">
<ComponentRef Id="ComponentIdINeedToReplace" />
[...]
</ComponentGroup>
</Fragment>
[...]
<Fragment>
<ComponentGroup Id="CG.WinSvcContent">
<Component Id="ComponentIdINeedToReplace" Directory="TARGETDIR" Guid="{SOMEGUID}">
<File Id="FileIdINeedToReplace" Source="$(var.Windows.Service.TargetDir)\Windows.Service.exe" />
</Component>
[...]
</ComponentGroup>
</Fragment>
[...]
</Wix>
我将HEAT prebuild命令修改为:
"$(WIX)bin\heat.exe" project "$(ProjectDir)\..\Windows.Service\Windows.Service.csproj" -gg -pog Binaries -pog Symbols -pog Content -cg CG.WinSvcContent -directoryid "TARGETDIR" -t "$(ProjectDir)Resources\XsltTransform.xslt" -out "$(ProjectDir)Fragments\Windows.Service.Content.wxs"
并编写了以下XSLT来实现两件事:
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:variable
name="vIdToReplace"
select="//ComponentGroup[@Id='CG.WinSvcContent']/Component/File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]/../@Id" />
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="node[@Id=vIdToReplace]">
<xsl:copy-of select="@*[name()!='Id']"/>
<xsl:attribute name="Id">C_Windows_Service_exe</xsl:attribute>
</xsl:template>
<xsl:template
match="//ComponentGroup[@Id='CG.WinSvcContent']/Component/File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]">
<xsl:copy-of select="@*[name()!='Id']"/>
<xsl:attribute name="Id">Windows_Service_exe</xsl:attribute>
</xsl:template>
</xsl:stylesheet>
如何修改我的XSLT以实现我的需求?
答案 0 :(得分:10)
我担心你的XSLT会遇到很多问题。第一个是名称空间。在你的wix XML文件中,所有元素都在命名空间“http://schemas.microsoft.com/wix/2006/wi”中,但是在XSLT中没有提到该命名空间,这意味着在尝试匹配命名元素的地方,它只会匹配那些命名元素。在NO命名空间中,这与你的wix文件不同。
您需要为XSLT添加相应的命名空间声明,如此
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wi="http://schemas.microsoft.com/wix/2006/wi">
然后,在您引用命名元素的位置,使用此声明的前缀作为前缀。例如,与文件匹配的模板将如下所示
<xsl:template match="wi:ComponentGroup[@Id='CG.WinSvcContent']/wi:Component/wi:File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]">
现在,你在这个模板中也遇到了问题,因为你要做的第一件事就是输出一个属性,但此时你还没有真正复制文件元素,所以你可能会收到一个错误,因为它没有任何东西可以添加属性。因此,模板可能需要看起来像这样
<xsl:template match="wi:ComponentGroup[@Id='CG.WinSvcContent']/wi:Component/wi:File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]">
<xsl:copy>
<xsl:attribute name="Id">Windows_Service_exe</xsl:attribute>
<xsl:copy-of select="@*[name()!='Id']"/>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
(我在这里添加了 xsl:apply-templates ,但在这种情况下可能没有必要,如果文件元素没有子元素)
你也遇到过上一个模板的问题
<xsl:template match="node[@Id=vIdToReplace]">
我猜你的意思是在这里使用“node()”而不是“node”,因为“node”本身会在字面上寻找一个名为“node”的元素,其中没有。但主要问题是您将 @Id 与 vIdToReplace 进行比较。在这种情况下,当您真的想要将它与变量进行比较时,它正在寻找一个名为 vIdToReplace 的元素。正确的语法是 $ vIdToReplace
<xsl:template match="node()[@Id=$vIdToReplace]">
但是等等!如果您使用的是XSLT 1.0,则会出现错误。您不能在XSLT 1.0中使用模板匹配中的变量。它只适用于XSLT 2.0。你可以做的只是将你的长表达式粘贴到模板匹配中:
<xsl:template match="node()[@Id=//wi:ComponentGroup[@Id='CG.WinSvcContent']/wi:Component[wi:File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]]/@Id]">
但这看起来相当笨拙。或者,您可以定义一个键来查找包含您要替换的ID的组件元素:
<xsl:key name="vIdToReplace"
match="wi:ComponentGroup[@Id='CG.WinSvcContent']/wi:Component[wi:File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]]"
use="@Id"/>
然后您可以在模板匹配中使用此键,以检查当前@Id
是否存在组件元素<xsl:template match="node()[key('vIdToReplace', @Id)]">
在这种情况下,这是完整的XSLT
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:wi="http://schemas.microsoft.com/wix/2006/wi">
<xsl:key name="vIdToReplace"
match="wi:ComponentGroup[@Id='CG.WinSvcContent']/wi:Component[wi:File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]]"
use="@Id"/>
<xsl:template match="@*|node()">
<xsl:copy>
<xsl:apply-templates select="@*|node()" />
</xsl:copy>
</xsl:template>
<xsl:template match="node()[key('vIdToReplace', @Id)]">
<xsl:copy>
<xsl:attribute name="Id">C_Windows_Service_exe</xsl:attribute>
<xsl:copy-of select="@*[name()!='Id']"/>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
<xsl:template match="wi:ComponentGroup[@Id='CG.WinSvcContent']/wi:Component/wi:File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]">
<xsl:copy>
<xsl:attribute name="Id">Windows_Service_exe</xsl:attribute>
<xsl:copy-of select="@*[name()!='Id']"/>
<xsl:apply-templates />
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
应用于XML时,输出以下内容
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
[...]
<Fragment>
<ComponentGroup Id="Windows.Service.Binaries">
<ComponentRef Id="C_Windows_Service_exe"/>
[...]
</ComponentGroup>
</Fragment>
[...]
<Fragment>
<ComponentGroup Id="CG.WinSvcContent">
<Component Id="C_Windows_Service_exe" Directory="TARGETDIR" Guid="{SOMEGUID}">
<File Id="Windows_Service_exe" Source="$(var.Windows.Service.TargetDir)\Windows.Service.exe"/>
</Component>
[...]
</ComponentGroup>
</Fragment>
[...]
</Wix>