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

Ame*_*eto 2 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>
Run Code Online (Sandbox Code Playgroud)

我将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"
Run Code Online (Sandbox Code Playgroud)

并编写了以下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>
Run Code Online (Sandbox Code Playgroud)

如何修改我的XSLT以实现我的需求?

Tim*_*m C 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">
Run Code Online (Sandbox Code Playgroud)

然后,在引用命名元素的位置,使用此声明的前缀作为前缀.例如,与File匹配的模板将如下所示

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

现在,你在这个模板中也遇到了一个问题,因为你要做的第一件事是输出一个属性,但是你此时并没有真正复制过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>
Run Code Online (Sandbox Code Playgroud)

(我在这里添加了一个xsl:apply-templates,但在这种情况下可能没有必要,如果File元素没有子元素)

您也遇到了以前模板的问题

<xsl:template match="node[@Id=vIdToReplace]">
Run Code Online (Sandbox Code Playgroud)

我猜你的意思是在这里使用"node()"而不是"node",因为"node"本身会在字面上寻找一个名为"node"的元素,其中没有.但主要问题是你将@IdvIdToReplace进行比较.在这种情况下,当你真的想要将它与你的变量进行比较时,它正在寻找一个名为vIdToReplace的元素.正确的语法是$ vIdToReplace

<xsl:template match="node()[@Id=$vIdToReplace]">
Run Code Online (Sandbox Code Playgroud)

可是等等!如果您使用的是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]">
Run Code Online (Sandbox Code Playgroud)

但这看起来相当笨拙.或者,您可以定义一个键来查找包含您要替换的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"/>
Run Code Online (Sandbox Code Playgroud)

然后,您可以在模板匹配中使用此键,以检查当前@Id 的Component元素是否存在

<xsl:template match="node()[key('vIdToReplace', @Id)]">
Run Code Online (Sandbox Code Playgroud)

在这种情况下,这是完整的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>
Run Code Online (Sandbox Code Playgroud)

应用于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>
Run Code Online (Sandbox Code Playgroud)