XSLT Change attribute value of specific elements with a condition

Viewed 21

I have the following XML content:

<fragment>
  <directory Id="dirABC" Name="ABC">
    <component Id="cmpA" Guid="*">
      <file Id="filA" KeyPath="yes" Source="SourceRootDir\AAA.exe" />
    </component>
    <component Id="cmpB" Guid="*">
      <file Id="filB" KeyPath="yes" Source="SourceRootDir\BBB.exe" />
    </component>
    <component Id="cmpC" Guid="*">
      <file Id="filC" KeyPath="yes" Source="SourceRootDir\CCC.exe" />
    </component>
  </directory>
</fragment>

I am trying to find the file elements that have the Source attribute ending with 'BBB.exe' and replace its Id attribute with another value, e.g. with filNEW. In my example the Id attribute value 'filB' should be changed to 'filNEW'. So, my XSLT is defined the following way:

<?xml version="1.0" encoding="utf-8"?>

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" >
  <xsl:output omit-xml-declaration="no" indent="yes" />
  <xsl:strip-space elements="*"/>

  <xsl:template match="@*|node()">
    <xsl:copy>
      <xsl:apply-templates select="@*|node()" />
    </xsl:copy>
  </xsl:template>

  <xsl:template match="/fragment/directory/component/file[substring(@Source, string-length(@Source) - string-length('BBB.exe') + 1)  = 'BBB.exe']">
      <xsl:attribute name="Id">filNEW</xsl:attribute>
  </xsl:template>
</xsl:stylesheet>

However, instead of updating only the Id attribute of the file element of the second component element, the result has the whole file element removed for the second component and has the component element's Id attribute value replaced with the new filNEW value. The current result.

<?xml version="1.0" encoding="UTF-8"?>
<fragment>
   <directory Id="dirABC" Name="ABC">
      <component Guid="*" Id="cmpA">
         <file Id="filA" KeyPath="yes" Source="SourceRootDir\AAA.exe"/>
      </component>
      <component Guid="*" Id="filNEW"/>
      <component Guid="*" Id="cmpC">
         <file Id="filC" KeyPath="yes" Source="SourceRootDir\CCC.exe"/>
      </component>
   </directory>
</fragment>

Whereas, I am trying to get this result:

<?xml version="1.0" encoding="UTF-8"?>
<fragment>
   <directory Id="dirABC" Name="ABC">
      <component Guid="*" Id="cmpA">
         <file Id="filA" KeyPath="yes" Source="SourceRootDir\AAA.exe"/>
      </component>
      <component Id="cmpB" Guid="*">
        <file Id="filNEW" KeyPath="yes" Source="SourceRootDir\BBB.exe" />
      </component>
      <component Guid="*" Id="cmpC">
         <file Id="filC" KeyPath="yes" Source="SourceRootDir\CCC.exe"/>
      </component>
   </directory>
</fragment>

What am I doing wrong in the XSLT?

1 Answers

You are matching on the file element and then generating an attribute. If you just want to replace the value of the @Id attribute, then change your match expression to match on that @Id by adding /@Id after the predicate on the file:

<xsl:template match="/fragment/directory/component/file[substring(@Source, string-length(@Source) - string-length('BBB.exe') + 1)  = 'BBB.exe']/@Id">
  <xsl:attribute name="Id">filNEW</xsl:attribute>
</xsl:template>
Related