【问题标题】:Wix Heat - Replace an autogenerated GUID with known string using XSLTWix Heat - 使用 XSLT 将自动生成的 GUID 替换为已知字符串
【发布时间】:2013-10-15 20:26:39
【问题描述】:

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

我能够使用 HEAT 将我的 Windows 服务项目的输出收集到一个片段中。目前,为了让我的自定义操作正常工作,我手动将一些从 Heat 生成的文件生成的 GUID 更改为在主 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 预编译命令修改为:

"$(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 以达到我的需要?

【问题讨论】:

  • IMO,安装程序应该是自动化的,而不是安装程序创作。 30 秒内就能写完的东西浪费了太多时间。

标签: xslt wix heat


【解决方案1】:

恐怕您的 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">

然后,在您引用命名元素的地方,在它们前面加上这个声明的前缀。例如,您的模板匹配 File 看起来像这样

<xsl:template match="wi:ComponentGroup[@Id='CG.WinSvcContent']/wi:Component/wi:File[contains(@Source,'Windows.Service.exe') and not(contains(@Source,'config'))]">

现在,您在此模板中也遇到了问题,因为您要做的第一件事是输出一个属性,但此时您实际上并没有复制 File 元素,所以您可能会得到一个错误,因为它没有任何东西可以添加属性。因此,模板可能需要如下所示

<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,但在这种情况下可能没有必要,如果 File 元素没有子元素)

之前的模板也有问题

<xsl:template match="node[@Id=vIdToReplace]">

我猜你的意思是在这里使用“node()”而不是“node”,因为“node”本身会从字面上寻找一个名为“node”的元素,其中没有。但主要问题是您将 @IdvIdToReplace 进行比较。在这种情况下,它正在寻找一个名为 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 的 Component 元素:

<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是否存在Component元素

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

【讨论】:

  • 所以你是说我快到了,对吧?哈哈。开个玩笑,非常感谢您花时间解释为什么转换没有按照我的意思进行。我刚试了一下,效果很好。
猜你喜欢
  • 2011-08-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-03-05
相关资源
最近更新 更多