views:

73

answers:

2

I'm attempting to remove Component elements from the XML below that have File children with the extension "config." I've managed to do this part, but I also need to remove the matching ComponentRef elements that have the same "Id" values as these Components.

<Fragment>
  <DirectoryRef Id="MyWebsite">
    <Component Id="Comp1">
      <File Source="Web.config" />
    </Component>
    <Component Id="Comp2">
      <File Source="Default.aspx" />
    </Component>
  </DirectoryRef>
</Fragment>
<Fragment>
  <ComponentGroup Id="MyWebsite">
    <ComponentRef Id="Comp1" />
    <ComponentRef Id="Comp2" />
  </ComponentGroup>
</Fragment>

Based on other SO answers, I've come up with the following XSLT to remove these Component elements:

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="xml" indent="yes" />
    <xsl:template match="Component[File[substring(@Source, string-length(@Source)- string-length('config') + 1) = 'config']]" />
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
</xsl:stylesheet>

Unfortunately, this doesn't remove the matching ComponentRef elements (i.e. those that have the same "Id" values). The XSLT will remove the component with the Id "Comp1" but not the ComponentRef with Id "Comp1". How do I achieve this using XSLT 1.0?

A: 

How about this? I've made a small change to your original to simplify things as well (it's simpler to check if the @source attribute ends with 'config').

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
    <xsl:output method="xml" indent="yes" />
    <xsl:template match="Component[substring(@Source, string-length(@Source) - 5) = 'config']" />
    <xsl:template match="ComponentRef[//Component[substring(@Source, string-length(@Source) - 5) = 'config']/@Id = @Id]"/>
        <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>

This has a template that matches any ComponentRef that has the same Id attribute as a Component matched by the preceding template. One thing - the '//Component' is not efficient. You should be able to replace that with something more efficient - I don't know your XML structure

Nic Gibson
Thanks for the quick response! The only problem with this is that I'm limited to XPath/XSLT 1.0 which apparently doesn't have `fn:ends-with()` which is why I'm using substring. I replaced your calls to `ends-with()` with `substring()` and it works perfectly. One question: you say `//Component` is inefficient. Would selecting from the root (e.g. `/Wix/Fragment/DirectoryRef/`) avoid this inefficiency?
pmdarrow
Apologies - I completely forgot about the lack of ends-with in XSLT 1.0. I'll fix the example. As @markusk mentions below, xsl:key would be an efficient approach, more so that actually using the absolute path (which is better than my approach). If you are looking up values more than once, xsl:key is the right approach
Nic Gibson
Thanks for your answer nonetheless. This is my first question on SO and I'm amazed at the quality and speed of responses on here.
pmdarrow
+4  A: 

A fairly efficient approach is to use xsl:key to identify the IDs of config components:

<?xml version="1.0" encoding="utf-8"?> 
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt; 
    <xsl:output method="xml" indent="yes" />

    <xsl:key name="configComponent" 
      match="Component[File/@Source[substring(., 
               string-length() - string-length('config') + 1) = 'config']]" 
      use="@Id" />

    <xsl:template match="Component[key('configComponent', @Id)]" /> 

    <xsl:template match="ComponentRef[key('configComponent', @Id)]" /> 

    <xsl:template match="@*|node()"> 
        <xsl:copy> 
            <xsl:apply-templates select="@*|node()"/> 
        </xsl:copy> 
    </xsl:template> 
</xsl:stylesheet>
markusk
Yes, that's the approach I would have chosen but didn't want to complicate the example.
Nic Gibson
I like this approach, very elegant :) Since this appears to be the more efficient approach and works properly on XPath/XSLT 1.0, I'll accept this answer.
pmdarrow
Good Answer! +1.
Dimitre Novatchev
@pmdarrow - glad you switched that - I really didn't give the answer enough time, @markusk's is a much better answer
Nic Gibson