(sorry this may be a bit long)
I have a WXS file that was generated by WiX's heat utility. I'm trying to modify it with an (existing) exclusions.xslt file to automatically exclude certain components based on the contents of another XML file (I'll call this parts.xml). The xslt file is currently used to remove some components/files/directories from the installer, but for a relatively static list.
The WXS file I'm transforming has File elements, all of which have a "Source" attribute, which is what the criteria is applied to for removing elements.
My goal is to read in the parts.xml file from my XSLT stylesheet and use that to exclude some elements from my Wix installer. The way we currently exclude elements is:
We first copy every element (the entire source xml) over. Like so:
<!-- Copy all attributes and elements to the output. -->
<xsl:template match="@*|*">
<xsl:copy>
<xsl:apply-templates select="@*" />
<xsl:apply-templates select="*" />
</xsl:copy>
</xsl:template>
then we use xsl:key instructions to mark certain items, like so:
<xsl:key name="removals" match="wix:Component[wix:File[contains(@Source, ')\fileToBeRemoved1.txt')]]" use="@Id" />
<xsl:key name="removals" match="wix:Component[wix:File[contains(@Source, ')\fileToBeRemoved2.exe')]]" use="@Id" />
Then we remove them with:
<xsl:template match="wix:Component[key('removals', @Id)]" />
<xsl:template match="wix:Directory[key('removals', @Id)]" />
<xsl:template match="wix:ComponentRef[key('removals', @Id)]" />
I think I've gotten to the point where I was able to read the parts.xml file into the stylesheet with:
<!-- Adapted from https://stackoverflow.com/a/30153713/5605122 and http://geekswithblogs.net/Erik/archive/2008/04/01/120915.aspx-->
<xsl:param name="srcroot" />
<xsl:variable name="filename">
<xsl:call-template name="string-replace-all">
<xsl:with-param name="text" select="concat($srcroot, 'foo/parts.xml')" />
<xsl:with-param name="replace" select="'\'" />
<xsl:with-param name="by" select="'/'" />
</xsl:call-template>
</xsl:variable>
<xsl:variable name="partsfile" select="document(concat('file://', $filename))" />
<xsl:variable name="myOutputFiles">
<xsl:call-template name="str:tokenize">
<xsl:with-param name="string" select="normalize-space($partsfile/xsi:Apple/xsi:Banana[@Name = 'my_sdk']/xsi:Carrot/xsi:Fig[@Directory = 'OutputFiles'])"/>
</xsl:call-template>
</xsl:variable>
I got the str:tokenize from here. I imagine there are issues with the way I set myOutputFiles, especially with namespace issues, but I'm not entirely sure how to properly do this. I've added the xsi namespace to my top-level stylesheet node, but this didn't seem to produce any results when I printed out the value with:
<xsl:message terminate="yes">
<xsl:text>INFO: </xsl:text>
<xsl:text>
</xsl:text>
<xsl:copy-of select="$myOutputFiles"/>
<xsl:text>
</xsl:text>
</xsl:message>
My next step would be to somehow call an <xsl:key /> instruction over each of the tokens returned, so that the three template instructions I wrote above would remove the necessary items. But since <xsl:key/> has to be a top-level element in the stylesheet, I'm not sure how I could go about doing that.
Ultimately, once I get the tokens, I'd want to iterate over them and exclude them by marking them in a way similar to above:
<xsl:key name="removals" match="wix:Component[wix:File[contains(@Source, ')\{PUT STRING FROM TOKEN HERE}')]]" use="@Id" />
and then they'd be removed by the template instructions above. The ')\' prefix is important to prepend to the token string. This is how it knows to only remove components from the INSTALLDIRECTORY node, and not any subdirectories.
How can I go about accomplishing this task? Any help is much appreciated!
Edit:
I'm using MSXSL as my processor. I don't believe it can natively do str:tokenize, but I copied the template from here and included it at the bottom of my stylesheet to use. Other than it eliminating the <token> tags, it seems to work okay. If I call it with string="'file1 file2 file3'" and print it out the same way I printed above, it outputs "file1file2file3" to the console.
Here is some sample input XML before transformation:
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLDIRECTORY">
<Component Id="cmp53284C0E6C6EC93D8D9DE8E3703596E4" Guid="*">
<File Id="filBD973F0EAED6A0B34BE693B053368769" KeyPath="yes" Source="$(env.srcDir)\file1.txt" />
</Component>
<Component Id="cmp81302C0E6C6EC93D877778E270358FFE" Guid="*">
<File Id="filAA273F0EAED6A0B34BE693B053A129EA" KeyPath="yes" Source="$(env.srcDir)\file2.exe" />
</Component>
<Component Id="cmp2A1630B8E0E70C310FC91CD5DADB5A43" Guid="*">
<File Id="filF5C36F42ADA8B3DD927354B5AB666898" KeyPath="yes" Source="$(env.srcDir)\thisWillStay.txt" />
</Component>
<Component Id="cmpABC123AE6C6EC93D8D9DE8E370BEEF39" Guid="*">
<File Id="fil72EA34F0EAED6A0B34BE693B05334421" KeyPath="yes" Source="$(env.srcDir)\Some.File.dll" />
</Component>
<Directory Id="dir2A411B40F7C80649B57155F53DD7D136" Name="ThisWillStay">
<Component Id="cmpE7DF6F3C9BE17355EA10D49649C4957A" Guid="*">
<File Id="fil908CF12B8DD2E95A77D7611874EC3892" KeyPath="yes" Source="$(env.srcDir)\ThisWillStay\file1.txt" />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
<Fragment>
<ComponentGroup Id="DeliveredFiles">
<ComponentRef Id="cmp53284C0E6C6EC93D8D9DE8E3703596E4" />
<ComponentRef Id="cmp81302C0E6C6EC93D877778E270358FFE" />
<ComponentRef Id="cmp2A1630B8E0E70C310FC91CD5DADB5A43" />
<ComponentRef Id="cmpABC123AE6C6EC93D8D9DE8E370BEEF39" />
<ComponentRef Id="cmpE7DF6F3C9BE17355EA10D49649C4957A" />
</ComponentGroup>
</Fragment>
</Wix>
The referenced xml file (parts.xml) looks something like (the node-tree leading up to Fig is what's important, but there are other elements around it as well):
<?xml version="1.0" encoding="utf-8"?>
<Apple xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:noNamespaceSchemaLocation="../foo/schema.xsd" >
<!-- ... -->
<Banana Name="my_sdk" SomeAttribute="${srcroot}some/path/to/file.txt">
<Carrot>
<Dill SomeOtherAttribute="SomeIdentifier"/>
<Fig Directory="OutputFiles">
foo/bar/file1.txt
foo/bar/file2.exe
foo/bar/Some.File.dll
<!-- ... -->
</Fig>
</Carrot>
</Banana>
<!-- ... -->
</Apple>
The files specified in Fig relate to the components to be removed from the WXS file (their paths here are not important though).This reference file is based off of an XML Schema instance (the schema.xsd file), which looks something like:
<?xml version="1.0" encoding="utf-8"?>
<xs:schema attributeFormDefault="unqualified" elementFormDefault="qualified" xmlns:xs="http://www.w3.org/2001/XMLSchema">
<xs:element name="Apple">
<!-- (various xs:elements defined for elements used in other parts file) -->
</xs:element>
</xs:schema>
And the expected output XML:
<?xml version="1.0" encoding="utf-8"?>
<Wix xmlns="http://schemas.microsoft.com/wix/2006/wi">
<Fragment>
<DirectoryRef Id="INSTALLDIRECTORY">
<Component Id="cmp2A1630B8E0E70C310FC91CD5DADB5A43" Guid="*">
<File Id="filF5C36F42ADA8B3DD927354B5AB666898" KeyPath="yes" Source="$(env.srcDir)\thisWillStay.txt" />
</Component>
<Directory Id="dir2A411B40F7C80649B57155F53DD7D136" Name="ThisWillStay">
<Component Id="cmpE7DF6F3C9BE17355EA10D49649C4957A" Guid="*">
<File Id="fil908CF12B8DD2E95A77D7611874EC3892" KeyPath="yes" Source="$(env.srcDir)\ThisWillStay\file1.txt" />
</Component>
</Directory>
</DirectoryRef>
</Fragment>
<Fragment>
<ComponentGroup Id="DeliveredFiles">
<ComponentRef Id="cmp2A1630B8E0E70C310FC91CD5DADB5A43" />
<ComponentRef Id="cmpE7DF6F3C9BE17355EA10D49649C4957A" />
</ComponentGroup>
</Fragment>
</Wix>
The file1.txt component is now gone from the first fragment, and the corresponding ComponentRef is gone in the second fragment. (the paths don't matter for the reference file. It should use only the file names to exclude files only in the INSTALLDIRECTORY node, not any subdirectories of the source file. I figured I could just use a "substring-after-last" like this).
Edit2:
If it helps, here are some templates I've grabbed from various sources I planned to use:
<!--
Adapted from: https://stackoverflow.com/a/9079154/5605122
-->
<xsl:template name="substring-after-last">
<xsl:param name="haystack" />
<xsl:param name="needle" />
<xsl:choose>
<xsl:when test="contains($haystack, $needle)">
<xsl:call-template name="substring-after-last">
<xsl:with-param name="haystack"
select="substring-after($haystack, $needle)" />
<xsl:with-param name="needle" select="$needle" />
</xsl:call-template>
</xsl:when>
<xsl:otherwise><xsl:value-of select="$haystack" /></xsl:otherwise>
</xsl:choose>
</xsl:template>
<!-- Tokenizing: http://exslt.org/str/functions/tokenize/index.html -->
<!-- Code from: http://exslt.org/str/functions/tokenize/str.tokenize.template.xsl -->
<xsl:template name="str:tokenize">
<xsl:param name="string" select="''"/>
<xsl:param name="delimiters" select="' '"/>
<xsl:choose>
<xsl:when test="not($string)"/>
<xsl:when test="not($delimiters)">
<xsl:call-template name="str:_tokenize-characters">
<xsl:with-param name="string" select="$string"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="delimiters" select="$delimiters"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>
<xsl:template name="str:_tokenize-characters">
<xsl:param name="string"/>
<xsl:if test="$string">
<token>
<xsl:value-of select="substring($string, 1, 1)"/>
</token>
<xsl:call-template name="str:_tokenize-characters">
<xsl:with-param name="string" select="substring($string, 2)"/>
</xsl:call-template>
</xsl:if>
</xsl:template>
<xsl:template name="str:_tokenize-delimiters">
<xsl:param name="string"/>
<xsl:param name="delimiters"/>
<xsl:variable name="delimiter" select="substring($delimiters, 1, 1)"/>
<xsl:choose>
<xsl:when test="not($delimiter)">
<token>
<xsl:value-of select="$string"/>
</token>
</xsl:when>
<xsl:when test="contains($string, $delimiter)">
<xsl:if test="not(starts-with($string, $delimiter))">
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="substring-before($string, $delimiter)"/>
<xsl:with-param name="delimiters" select="substring($delimiters, 2)"/>
</xsl:call-template>
</xsl:if>
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="substring-after($string, $delimiter)"/>
<xsl:with-param name="delimiters" select="$delimiters"/>
</xsl:call-template>
</xsl:when>
<xsl:otherwise>
<xsl:call-template name="str:_tokenize-delimiters">
<xsl:with-param name="string" select="$string"/>
<xsl:with-param name="delimiters" select="substring($delimiters, 2)"/>
</xsl:call-template>
</xsl:otherwise>
</xsl:choose>
</xsl:template>