Search code examples
xmlxsltxslt-1.0xslt-2.0xslt-3.0

XSLT to find all related elements


I am trying to find out if a XSLT can be used to transform the following XML (snippet as it contains more elements) so that the transformed XML only contains the content elements that are specified in the child element.

<?xml version="1.0" encoding="UTF-8"?>
<library>
    <content content-id="a">
        <content-link content-id="a1"/>
        <content-link content-id="a2"/>
    </content>
    <content content-id="b">
        <content-link content-id="b1"/>
        <content-link content-id="b2"/>
    </content>
    <content content-id="a1">
        <content-link content-id="a11"/>
        <content-link content-id="a12"/>
    </content>
    <content content-id="a2">
        <content-link content-id="a21"/>
        <content-link content-id="a22"/>
    </content>
    <content content-id="a11"/>
    <content content-id="a12"/>
    <content content-id="a21"/>
    <content content-id="a22"/>    
</library>

to

<?xml version="1.0" encoding="UTF-8"?>
<library>
    <content content-id="a">
        <content-link content-id="a1"/>
        <content-link content-id="a2"/>
    </content>
    <content content-id="a1">
        <content-link content-id="a11"/>
        <content-link content-id="a12"/>
    </content>
    <content content-id="a2">
        <content-link content-id="a21"/>
        <content-link content-id="a22"/>
    </content>
    <content content-id="a11"/>
    <content content-id="a12"/>
    <content content-id="a21"/>
    <content content-id="a22"/>    
</library>

Below is my attempt but I am rather weak in XSLT so appreciate further inputs here

<?xml version="1.0"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">   
    <xsl:output method="xml" indent="yes"/>
    <xsl:variable name="cid" select="'a'" />
    <xsl:template match="/">
        <library>        
        <xsl:apply-templates select="//content[@content-id=$cid]"/>        
        </library>
    </xsl:template>
    
    <xsl:template match="content">        
        <xsl:copy-of select="." />        
        <xsl:for-each select="*">
            <xsl:apply-templates select="content-link"/>
        </xsl:for-each>
    </xsl:template>
    
    <xsl:template match="content-link">
         <!-- Don't know how to call the ancestor content elements referenced by content-link -->
    </xsl:template>
</xsl:stylesheet>

Solution

  • Perhaps like this:

    <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns:xs="http://www.w3.org/2001/XMLSchema"
        xmlns:mf="http://example.com/mf"
        exclude-result-prefixes="#all"
        version="3.0">
        
      <xsl:function name="mf:get-related-elements" as="element()*">
          <xsl:param name="element" as="element()"/>
          <xsl:sequence
            select="$element ! (. | content-link | key('ref', content-link/@content-id)/mf:get-related-elements(.))"/>
      </xsl:function>
      
      <xsl:key name="ref" match="/*//*" use="@content-id"/>
        
      <xsl:param name="cid" select="'a'" />
      
      <xsl:variable name="start" select="key('ref', $cid)"/>
      <xsl:variable name="related-elements" select="mf:get-related-elements($start)"/>
    
      <xsl:mode on-no-match="shallow-copy"/>
    
      <xsl:template match="/*//*[not(. intersect $related-elements)]"/>
      
    </xsl:stylesheet>
    

    https://xsltfiddle.liberty-development.net/6q1SDjU