Search code examples
xsltxslt-2.0

Remove parent node if child node is empty


I am trying to transform a given XML using xslt. The caveat is that I would have to delete a parent node if a given child node is not present. I did do some template matching, but I am stuck. Any help would be appreciated.

The input xml :

<?xml version="1.0" encoding="UTF-8"?>
<main>
     <item>
        <value>
           <item>
              <value>ABC</value>
              <key>test1</key>
           </item>
           <item>
              <value>XYZ</value>
              <key>test2</key>
           </item>
               <item>
              <value></value>
              <key>test3</key>
           </item>
        </value>
     </item>
     <item>
        <value />
        <key>test4</key>
     </item>
     <item>
        <value>PQR</value>
        <key>test5</key>
     </item>
</main>

Expected Output:

<?xml version="1.0" encoding="UTF-8"?>
<main>
     <item>
        <value>
           <item>
              <value>ABC</value>
              <key>test1</key>
           </item>
           <item>
              <value>XYZ</value>
              <key>test2</key>
           </item>
        </value>
     </item>
     <item>
        <value>PQR</value>
        <key>test5</key>
     </item>
</main>

The issue is if I use template matching e.g.

<xsl:template match="item[not(value)]"/> as mentioned in deleting the parent node if child node is not present in xml using xslt, then it completely removes everything as main/item/value is also empty.

What I need is remove if element is empty but only do if element has no child element.


Solution

  • You should first start with the XSLT identity template

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

    Then, all you need is a template that matches the item element where all descendent leaf value elements are empty.

     <xsl:template match="item[not(descendant::value[not(*)][normalize-space()])]" />
    

    So, the template matches it, but doesn't output it.

    Try this XSLT

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
        <xsl:output method="xml" indent="yes" />
    
        <xsl:template match="item[not(descendant::value[not(*)][normalize-space()])]" />
    
        <xsl:template match="@*|node()">
            <xsl:copy>
                <xsl:apply-templates select="@*|node()"/>
            </xsl:copy>
        </xsl:template>
    </xsl:stylesheet>