Search code examples
xmlxsltrecursionapply-templates

xsl apply-templates recursively after transformation


I am having troubles trying to apply a template recursively. I would like to say that although I have worked a couple of times with simple XSL transformations, I don't have a deep knowledge of them.

I have the following XML that represents classes with methods and attributes

<classes>
    <class name="A" author="Mr.X" >
        <attribute name="i_" type="integer" visibility="protected" />
        <attribute name="f_" type="float" visibility="private" />
        <attribute name="c_" type="char" visibility="private" />
        <method name="foo" return="integer" visibility="public" >
            <param name="a" type="integer" />
            <param name="b" type="integer" />
        </method>
    </class>

    <class name="B" author="Mr.Y" >
        <attribute name="s_" type="string" visibility="protected" />
        <method name="bar" visibility="public" />
    </class>

    <class name="CA" author="Mr.Z" base="A" >
        <attribute name="d_" type="double" visibility="protected" />
    </class>

    <class name="CB" author="Mr.Z" base="B" />

    <class name="DCA" author="Mr.X" base="CA" >
        <attribute name="s_" type="string" visibility="protected" />
    </class>
</classes>

and I would like to obtain an XML that includes the classes with all the attributes and methods, both from itself and from it base class, in the same way as OO inheritance works.

I would like to obtain the following XML

<classes>
    <class name="A" author="Mr.X" >
        <attribute name="i_" type="integer" visibility="protected" />
        <attribute name="f_" type="float" visibility="private" />
        <attribute name="c_" type="char" visibility="private" />
        <method name="foo" return="integer" visibility="public" >
            <param name="a" type="integer" />
            <param name="b" type="integer" />
        </method>
    </class>

    <class name="B" author="Mr.Y" >
        <attribute name="s_" type="string" visibility="protected" />
        <method name="bar" visibility="public" />
    </class>

    <class name="CA" author="Mr.Z" >
        <attribute name="d_" type="double" visibility="protected" />
        <!--[begin] inherited from base class A by Mr.X-->
        <attribute name="i_" type="integer" visibility="protected" />
        <attribute name="f_" type="float" visibility="private" />
        <attribute name="c_" type="char" visibility="private" />
        <method name="foo" return="integer" visibility="public" >
            <param name="a" type="integer" />
            <param name="b" type="integer" />
        </method>
        <!--[end] inherited from base class A-->
    </class>

    <class name="CB" author="Mr.Z" >
        <!--[begin] inherited from base class B by Mr.Y-->
        <attribute name="s_" type="string" visibility="protected" />
        <method name="bar" visibility="public" />
        <!--[end] inherited from base class B-->
    </class>

    <class name="DCA" author="Mr.X" >
        <attribute name="s_" type="string" visibility="protected" />
        <!--[begin] inherited from base class CA by Mr.Z-->
        <attribute name="d_" type="double" visibility="protected" />
        <!--[begin] inherited from base class A by Mr.X-->
        <attribute name="i_" type="integer" visibility="protected" />
        <attribute name="f_" type="float" visibility="private" />
        <attribute name="c_" type="char" visibility="private" />
        <method name="foo" return="integer" visibility="public" >
            <param name="a" type="integer" />
            <param name="b" type="integer" />
        </method>
        <!--[end] inherited from base class A-->
        <!--[end] inherited from base class CA-->
    </class>
</classes>

I wrote the following XSL, but just works for one level of class inheritance.

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

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

    <xsl:template match="/classes/import">
        <xsl:comment>importing <xsl:value-of select="@file"/> file</xsl:comment>
        <xsl:apply-templates select="document(@file)/classes/node()" />
    </xsl:template>

    <xsl:template match="*[@base]">
        <xsl:variable name="bc" select="@base" />
        <xsl:copy>
            <xsl:apply-templates select="@*[name(.)!='base']"/>                      
            <xsl:apply-templates select="/classes/class[@name=$bc]/@*[name(.)!='name' and name(.)!='author']" />
            <xsl:apply-templates /> 
            <xsl:comment>[begin] inherited from base class <xsl:value-of select="$bc"/> by <xsl:value-of select="//class[@name=$bc]/@author"/></xsl:comment>
            <xsl:apply-templates select="/classes/class[@name=$bc]/node()" />
            <xsl:comment>[end] inherited from base class <xsl:value-of select="$bc"/></xsl:comment>
        </xsl:copy>
    </xsl:template>

</xsl:stylesheet>

Of course, if I apply the above transformation as many times as the maximum level of inheritance that the classes have, I (almost) obtain the desired result, but my goal is to get it in just one transformation.

Any guide would be greatly appreciated. Thanks in advance.


Solution

  • Here's how to approach your first problem:

    XSLT 1.0

    <xsl:stylesheet version="1.0" 
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
    <xsl:strip-space elements="*"/>
    
    <xsl:key name="parent" match="class" use="@name" />
    
    <!-- identity transform -->
    <xsl:template match="@*|node()">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="class">
        <xsl:copy>
            <xsl:apply-templates select="@*|node()"/>
            <xsl:apply-templates select="key('parent', @base)" mode="inherit"/>
        </xsl:copy>
    </xsl:template>
    
    <xsl:template match="class" mode="inherit">
        <xsl:comment>
            <xsl:text>[begin] inherited from class </xsl:text>
            <xsl:value-of select="@name"/>
        </xsl:comment>
        <xsl:copy-of select="attribute | method"/>
        <xsl:apply-templates select="key('parent', @base)" mode="inherit"/>
        <xsl:comment>
            <xsl:text>[end] inherited from class </xsl:text>
            <xsl:value-of select="@name"/>
        </xsl:comment>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Applied to your input example, the result will be:

    <?xml version="1.0" encoding="UTF-8"?>
    <classes>
      <class name="A" author="Mr.X">
        <attribute name="i_" type="integer" visibility="protected"/>
        <attribute name="f_" type="float" visibility="private"/>
        <attribute name="c_" type="char" visibility="private"/>
        <method name="foo" return="integer" visibility="public">
          <param name="a" type="integer"/>
          <param name="b" type="integer"/>
        </method>
      </class>
      <class name="B" author="Mr.Y">
        <attribute name="s_" type="string" visibility="protected"/>
        <method name="bar" visibility="public"/>
      </class>
      <class name="CA" author="Mr.Z" base="A">
        <attribute name="d_" type="double" visibility="protected"/>
        <!--[begin] inherited from class A-->
        <attribute name="i_" type="integer" visibility="protected"/>
        <attribute name="f_" type="float" visibility="private"/>
        <attribute name="c_" type="char" visibility="private"/>
        <method name="foo" return="integer" visibility="public">
          <param name="a" type="integer"/>
          <param name="b" type="integer"/>
        </method>
        <!--[end] inherited from class A-->
      </class>
      <class name="CB" author="Mr.Z" base="B">
        <!--[begin] inherited from class B-->
        <attribute name="s_" type="string" visibility="protected"/>
        <method name="bar" visibility="public"/>
        <!--[end] inherited from class B-->
      </class>
      <class name="DCA" author="Mr.X" base="CA">
        <attribute name="s_" type="string" visibility="protected"/>
        <!--[begin] inherited from class CA-->
        <attribute name="d_" type="double" visibility="protected"/>
        <!--[begin] inherited from class A-->
        <attribute name="i_" type="integer" visibility="protected"/>
        <attribute name="f_" type="float" visibility="private"/>
        <attribute name="c_" type="char" visibility="private"/>
        <method name="foo" return="integer" visibility="public">
          <param name="a" type="integer"/>
          <param name="b" type="integer"/>
        </method>
        <!--[end] inherited from class A-->
        <!--[end] inherited from class CA-->
      </class>
    </classes>
    

    I did not read your second question.