Wix Heat - 使用XSLT用已知字符串替换自动生成的GUID

时间:2013-10-08 18:44:17

标签: xslt wix heat

我正在努力将Windows服务VDPROJ迁移到WiX。

我能够使用HEAT将Windows Service项目的输出收集到片段中。目前,为了使我的自定义操作正常工作,我手动将一些生成的GUID从Heat生成的文件更改为主Product.wxs中引用的已知字符串。

我需要在每次构建时以编程方式执行此操作,而不是依赖于手动干预,因为我需要将WiX项目集成到我们的连续构建服务器中。

根据我的研究,我可以在HEAT的输出上使用XSLT转换来实现我的需要,但是我很难让我的XSLT转换工作。

以下是未使用XSLT转换生成的片段的一部分

片段\ Windows.Service.Content.wxs

<?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来实现两件事:

  • 将所有出现的“ComponentIdINeedToReplace”替换为已知字符串(有两个)
  • 将“FileIdINeedToReplace”的单个事件替换为已知字符串

资源\ XsltTransform.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以实现我的需求?

1 个答案:

答案 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>