Remove parent node when child node is empty XSLT

Viewed 39

I have an XML as below:

<Main>
    <rFs>
        <rF>
            <iT>T</iT>
            <ref>F1</ref>
            <recs>
                <rec>
                    <iT>T</iT>
                    <ref>T0</ref>
                    <cT Code="R" Des="Regular">R</cT>
                    <fT Code="N" Description="National">N</fT>
                    <st Code="D" Description="Dock">D</st>
                </rec>
                <rec>
                    <iT>T</iT>
                    <ref>T0</ref>
                    <cT Code="R" Description="Regular">R</cT>
                    <fT Code="Q" Description="Mad">Q</fT>
                </rec>
            </recs>
        </rF>
        <rF>
            <iT>T</iT>
            <ref>F2</ref>
            <recs>
                <rec>
                    <iT>T</iT>
                    <ref>T1</ref>
                    <cT Code="S" Des="Regular">R</cT>
                    <fT Code="N" Description="Inter">I</fT>
                </rec>
                <rec>
                    <iT>T</iT>
                    <ref>T1</ref>
                    <cT Code="S" Description="Time">R</cT>
                    <fT Code="Q" Description="Mad">Q</fT>
                </rec>
            </recs>
        </rF>
    </rFs>
</Main>

There is already an XSLT according to the requirements. The XSLT is as follows:

<xsl:template match="Main">
    <rFs>
        <xsl:for-each select="rFs/rF[iT = 'T']">
            <rF>
                <xsl:attribute name="id">
                    <xsl:value-of select="concat(ref,'_','fam')"/>
                </xsl:attribute>
                <recs>
                    <xsl:for-each-group select="recs/rec" group-by="ref">
                        <xsl:for-each select="current-group()">
                            <rec>
                                <xsl:attribute name="id">
                                    <xsl:value-of select="ref"/>
                                </xsl:attribute>
                                <st>
                                    <xsl:value-of select="st/@Code"/>
                                </st>
                            </rec>
                        </xsl:for-each>
                    </xsl:for-each-group>
                </recs>
            </rF>
        </xsl:for-each>
    </rFs>
</xsl:template>

The above XSLT gives the following XML as output -

<rFs>
    <rF id="F1_fam">
        <recs>
            <rec id="T0">
                <st>D</st>
            </rec>
            <rec id="T0">
                <st/>
            </rec>
        </recs>
    </rF>
    <rF id="F2_fam">
        <recs>
            <rec id="T1">
                <st/>
            </rec>
            <rec id="T1">
                <st/>
            </rec>
        </recs>
    </rF>
</rFs>

Now I have to make updations in the above XSLT. My requirements are:

    1. remove <rec> where <st> is null
    1. If all the <rec> under <rf> have<st> as null, then remove the entire <rf>

The implementation of the above requirements should generate the Final XML as:

<rFs>
    <rF id="F1_fam">
        <recs>
            <rec id="T0">
                <st>D</st>
            </rec>
        </recs>
    </rF>
</rFs>

I have gone through many post regarding the same but nothing seems to work.

2 Answers

Here is an XSLT 3 way using xsl:where-populated in a second phase processing of your initial result:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
  

  <xsl:template match="Main">
    <xsl:variable name="first-transformation-result">
        <rFs>
            <xsl:for-each select="rFs/rF[iT = 'T']">
            <rF>
                <xsl:attribute name="id"><xsl:value-of select="concat(ref,'_','fam')"/></xsl:attribute>
                <recs>
                    <xsl:for-each-group select="recs/rec" group-by="ref">
                    <xsl:for-each select="current-group()">
                        <rec>
                            <xsl:attribute name="id"><xsl:value-of select="ref"/></xsl:attribute>
                            <st><xsl:value-of select="st/@Code"/></st>
                        </rec>
                    </xsl:for-each>
                    </xsl:for-each-group>
                </recs>
            </rF>
            </xsl:for-each>
        </rFs>
      </xsl:variable>
      <xsl:apply-templates select="$first-transformation-result/node()"/>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:output indent="yes"/>

  <xsl:template match="*">
    <xsl:where-populated>
      <xsl:next-match/>
    </xsl:where-populated>
  </xsl:template>
  
</xsl:stylesheet>

Or perhaps even

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns:xs="http://www.w3.org/2001/XMLSchema"
    exclude-result-prefixes="#all"
    version="3.0">
  
  <xsl:template match="Main">
        <rFs>
            <xsl:for-each select="rFs/rF[iT = 'T']">
            <rF>
                <xsl:attribute name="id"><xsl:value-of select="concat(ref,'_','fam')"/></xsl:attribute>
                <recs>
                    <xsl:for-each-group select="recs/rec" group-by="ref">
                    <xsl:for-each select="current-group()">
                        <rec>
                            <xsl:attribute name="id"><xsl:value-of select="ref"/></xsl:attribute>
                            <st><xsl:value-of select="st/@Code"/></st>
                        </rec>
                    </xsl:for-each>
                    </xsl:for-each-group>
                </recs>
            </rF>
            </xsl:for-each>
        </rFs>
  </xsl:template>
  
  <xsl:template match="Main" priority="5">
    <xsl:variable name="first-transformation-result">
      <xsl:next-match/>
    </xsl:variable>
    <xsl:apply-templates select="$first-transformation-result/node()"/>
  </xsl:template>

  <xsl:mode on-no-match="shallow-copy"/>
  
  <xsl:output indent="yes"/>

  <xsl:template match="*">
    <xsl:where-populated>
      <xsl:next-match/>
    </xsl:where-populated>
  </xsl:template>
  
</xsl:stylesheet>

to reuse the existing template unchanged.

How about simply:

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

<xsl:template match="/Main">
    <rFs>
        <xsl:for-each select="rFs/rF[iT='T'][descendant::st/text()]">
            <rF id="{ref}_fam">
                <recs>
                    <xsl:for-each select="recs/rec[st/text()]">
                        <xsl:sort select="ref"/>
                        <rec id="{ref}">
                            <st>
                                <xsl:value-of select="st/@Code"/>
                            </st>
                        </rec>
                    </xsl:for-each>
                </recs>
            </rF>
        </xsl:for-each>
    </rFs>
</xsl:template>

</xsl:stylesheet>
Related