Search code examples
xmlxsltxslt-2.0

Chaining templates in XSLT 2.0


I have a very simple input-file:

<?xml version="1.0" encoding="UTF-8"?>
<STEP-ProductInformation>
   <Products>
      <Product>
          <Values>
            <ValueGroup AttributeID="tec_att_ignore_basic_data_text">
               <Value ID="Y" QualifierID="en-US">Yes</Value>
               <Value ID="Y" QualifierID="de-DE">Yes</Value>
            </ValueGroup>
            <ValueGroup AttributeID="prd_att_description">
               <Value QualifierID="en-US">
                   english text
               </Value>
               <Value QualifierID="de-DE">
                      german text
               </Value>
            </ValueGroup>
         </Values>
      </Product>
   </Products>
</STEP-ProductInformation>

In my XSL transformation, I want to delete every Value in the prd_att_description-Value group, if there is a N in the attribute: ignore_basic_data_text. This works fine so far, but if everything is deleted (so that there are no childs), I want to remove the whole ValueGroup as well.

My approach:

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


<xsl:key name="lov" match="ValueGroup[@AttributeID='tec_att_ignore_basic_data_text']/Value" use="@QualifierID" />
<!-- identity transform -->
<xsl:template match="@*|node()">
    <xsl:copy>
        <xsl:apply-templates select="@*|node()"/>
    </xsl:copy>
</xsl:template>

<xsl:template match="ValueGroup[@AttributeID='prd_att_description']/Value[key('lov', @QualifierID)/@ID='Y']"/>
<xsl:template match="ValueGroup[@AttributeID='prd_att_description' and not(Value)]"/>
    
</xsl:stylesheet>

It simply isn't removing the empty <ValueGroup>. I tried it with counting elements, with normalize-space(), with different templates, with different modes, but without success. I would assume the 2nd template isn't running over the result of the first template.

Any hints?


Solution

  • Well, the current version of XSLT is 3.0, there you could use xsl:where-populated as follows:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
      version="3.0"
      xmlns:xs="http://www.w3.org/2001/XMLSchema"
      exclude-result-prefixes="#all"
      expand-text="yes">
      
      <xsl:strip-space elements="*"/>
      <xsl:output indent="yes"/>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:key name="lov" match="ValueGroup[@AttributeID='tec_att_ignore_basic_data_text']/Value" use="@QualifierID" />
    
      <xsl:template match="ValueGroup[@AttributeID='prd_att_description']/Value[key('lov', @QualifierID)/@ID='Y']"/>
    
      <xsl:template match="ValueGroup[@AttributeID='prd_att_description']">
        <xsl:where-populated>
          <xsl:next-match/>
        </xsl:where-populated>
      </xsl:template>
    
    </xsl:stylesheet>
    

    I don't think there is a direct equivalent in XSLT 1/2 but for your use case I think you basically want the empty template for ValueGroup as

    <xsl:template match="ValueGroup[@AttributeID='prd_att_description'][not(Value[not(key('lov', @QualifierID)/@ID='Y')])]"/>
    

    and of course keep your

    <xsl:template match="ValueGroup[@AttributeID='prd_att_description']/Value[key('lov', @QualifierID)/@ID='Y']"/>