Cascade deletions with XSLT

 

A few days ago this question popped up on comp.text.xml:

 

I have an xml document in which elements are hierarchically related to
each other conceptually.  Unfortunately, the hierarchical relationship
is not modelled in the schema (i.e., the elements are "flattened" in
the document".  I have a case in which I want to remove a high level
element using xslt and want all the related lower-level elements to be
removed as well. Is there an easy way to do this in a template?

For example, imagine a relationship of elements describing animals
starting with "animal" at the top of the hierarchy and ending in "pets"
at the bottom:

animal => class => beastie => breed => pet

Given the xml below, say I wanted to remove the class named "Aves" and
wanted  to cause a cascading removal of all other elements related to
"Aves".  Is there a simple way to do this using xsl that helps me avoid
writing specific templates for each child node of "animals"?

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

   <classes>
      <class name="Mammalia"/>
      <class name="Aves"/>
   </classes>

   <beasties>
      <beast type="cat" class="Mammalia"/>
      <beast type="dog" class="Mammalia"/>
      <beast type="bird" class="Aves"/>
   </beasties>

   <breeds>
      <breed name="collie" type="dog"/>
      <breed name="beagle" type="dog"/>
      <breed name="persian" type="cat"/>
      <breed name="siamese" type="cat"/>
      <breed name="parakeet" type="bird"/>
      <breed name="crow" type="bird"/>
   </breeds>

   <pets>
      <pet name="rover" breed="collie"/>
      <pet name="fluffy" breed="persian"/>
      <pet name="tweety" breed="parakeet"/>
   </pets>

</animals>

The desired xml output would be this:

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

   <classes>
      <class name="Mammalia"/>
   </classes>

   <beasties>
      <beast type="cat" class="Mammalia"/>
      <beast type="dog" class="Mammalia"/>
   </beasties>

   <breeds>
      <breed name="collie" type="dog"/>
      <breed name="beagle" type="dog"/>
      <breed name="persian" type="cat"/>
      <breed name="siamese" type="cat"/>
   </breeds>

   <pets>
      <pet name="rover" breed="collie"/>
      <pet name="fluffy" breed="persian"/>
   </pets>

</animals>

 

Here is  the first solution that came to my mind (one for XSLT 1.0 and for XSLT 2.0):

 Cascade Deletes (XSLT 1.0), 49 lines

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

 <xsl:param name="pClassToDel" select="‘Aves’"/>     

 <xsl:key name="kBeastByType" match="beast"
 
         use="@type"/>     

 <xsl:key name="kBreedByName" match="breed
          use="@name"/>     

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

 <xsl:template match="class">
   <xsl:if test="not(@name = $pClassToDel)
">
      <xsl:call-template name="identity
"/>
   </xsl:if
>
 </xsl:template>     

 <xsl:template match="beast">
   <xsl:if test="not(@class = $pClassToDel)
">
      <xsl:call-template name="identity
"/>
   </xsl:if
>
 </xsl:template>     

 <xsl:template match="breed">
   <xsl:if test

      "
not(key(‘kBeastByType’, @type)/@class 
          = 
           $pClassToDel)">

      <xsl:call-template name="identity"/>
   </xsl:if>

 </xsl:template>     

 <xsl:template match="pet">
   <xsl:if test

      "
not(key(‘kBeastByType’, 
                key(‘kBreedByName’, @breed)/@type 
                )/@class 
          = 
            $pClassToDel)">

      <xsl:call-template name="identity"/>
   </xsl:if
>
 </xsl:template>
</xsl:stylesheet>

 

        Cascade Deletes (XSLT 2.0), 40 lines

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

 <xsl:param name="pClassToDel"select="‘Aves’"/>     

 <xsl:key name="kDeleted" match="beast" use=
       "string(@class = $pClassToDel)
"/>      
 

 <xsl:key name="kDeleted" match="class"
    
use="string(@name=$pClassToDel)"/>       

<xsl:key name="kDeleted" match="breed" use=
  "string(key(‘kBeastByType’,@type)/@class
          =
          $pClassToDel)
"/>

 <xsl:key name="kDeleted" match="pet" use=
  "
string(key(‘kBeastByType’,
               key(‘kBreedByName’,@breed)/@type
              )
               
/@class
              =
              $pClassToDel)"

/> 

 <xsl:key name="kBeastByType" match="beast"
         
use="@type"/>     

 <xsl:key name="kBreedByName" match="breed
          use="@name"/>      

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

<xsl:template match=
       
"*[. intersect key('kDeleted', 'true')]"/>
</xsl:stylesheet>

 

Both transformations produce the wanted results:

<animals>
  <classes>
    <class name="Mammalia"/>
  </classes>

  <beasties>
    <beast type="cat" class="Mammalia"/>
    <beast type="dog" class="Mammalia"/>
  </beasties>

  <breeds>
    <breed name="collie" type="dog"/>
    <breed name="beagle" type="dog"/>
    <breed name="persian" type="cat"/>
    <breed name="siamese" type="cat"/>
  </breeds>

  <pets>
    <pet name="rover" breed="collie"/>
    <pet name="fluffy" breed="persian"/>
  </pets>
</animals>

 

The solutions above are the first that came into mind — probably the XSLT community will come with new, even better ones.

 

About these ads
This entry was posted in Uncategorized. Bookmark the permalink.

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s