Shysoks
Shysoks

Reputation: 11

Recursively remove nodes when children are empty in XSLT

I am new to using xsl and I am not sure how to remove specific parent nodes and all their children, if all are empty. I think the answer shown in this link is what I need, but I am not sure how to apply this to a specific node instead of the whole stylesheet.

Using what is in that question, I have expanded it ...

<farm>
    <foo>
    <bar>
        <baz/>
    </bar>
    <quux>  </quux>
    <quuux>Actual content</quuux>
    </foo>
    <sounds>
        <moo>
            <meow>
                <buzz></buzz>
            </meow>
        </moo>
        <birds>
            <cluck>  </cluck>
            <quack></quack>
        </birds>
    </sounds>
</farm>

How can I just eliminate <sounds> and all of its children (if they are all empty) while leaving empty nodes like foo, bar, and baz. The requirements for the xml I am producing needs to have certain tags present even if they are blank, but some need to be removed if they are blank.

To go even further, if what I had is ...

<farm>
    <foo>
    <bar>
        <baz/>
    </bar>
    <quux>  </quux>
    <quuux>Actual content</quuux>
    </foo>
    <sounds>
        <moo>
            <meow>
                <buzz>"zzzz"</buzz>
            </meow>
        </moo>
        <birds>
            <cluck>  </cluck>
            <quack></quack>
        </birds>     <!-- Fixed by edit -->
    </sounds>
</farm>

In this example,<sounds>, <moo>, <meow>, and <buzz> would need to stay since <buzz> has a value, but <birds> and its children would need to be removed.

This seems so complicated to me, I am not sure what the simplest way to do this would be.

Thank you so much for your guidance, as I feel dizzy from going in so many circles!

Upvotes: 1

Views: 479

Answers (2)

Daniel Haley
Daniel Haley

Reputation: 52858

If your processor doesn't support XSLT 3.0, here's an XSLT 1.0 option...

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

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

  <xsl:template match="*[ancestor-or-self::sounds][not(string())]"/>

</xsl:stylesheet>

Fiddle for example #1: http://xsltfiddle.liberty-development.net/jyH9rNp

Fiddle for example #2: http://xsltfiddle.liberty-development.net/jyH9rNp/1

Upvotes: 2

Martin Honnen
Martin Honnen

Reputation: 167446

Assuming XSLT 3 (supported for the Java platform with Saxon 9.8 and 9.9 and for the .NET platform with Saxon 9.8, also by Altova 2017/2018/2019) you could use xsl:where-populated together with xsl:next-match for sounds and any descendant elements and the identity transformation for the rest; additionally you would need to strip white space, otherwise <cluck> </cluck> would not be removed:

<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:mode on-no-match="shallow-copy"/>

  <xsl:strip-space elements="*"/>
  <xsl:output indent="yes"/>

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

</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/gWmuiKn/0 has your first input and https://xsltfiddle.liberty-development.net/gWmuiKn/1 your second.

Upvotes: 0

Related Questions