Curunir
Curunir

Reputation: 1288

XSLT Family Tree

At the moment I'm struggeling to get my XSLT working properly. I trying to transform a XML into a unordered list with the children inside a sub unordered list. The problem is that XSL creates empty list elements if a person doesn't have a child.

XML:

    <?xml version="1.0" encoding="UTF-8"?>
<?xml-stylesheet type="text/xsl" href="Trees.xsl"?>
<Tree>
    <Person ID="1"  FirstName="test" LastName="" ChildOf="0" />
    <Person ID="2" FirstName="test2" LastName="" ChildOf="1" />
    <Person ID="3" FirstName="test3" LastName="" ChildOf="1" />
    <Person ID="4" FirstName="test4" LastName="" ChildOf="1" />
    <Person ID="6" FirstName="test6" LastName="" ChildOf="3" />
    <Person ID="5" FirstName="test5" LastName="" ChildOf="0" />
</Tree>

XSL:

<?xml version="1.0" encoding="UTF-8"?>

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


    <xsl:template match="Person">


           <ul><li><xsl:value-of select="@FirstName" /></li>
           <li><xsl:apply-templates select="../Person[@ChildOf=current()/@ID]" />
           </li></ul>

    </xsl:template>

</xsl:stylesheet>

I already tried checking with an if if the FirstName is empty or not, but it doesn't work. Where is the error.

Upvotes: 1

Views: 242

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243539

A simpler, shorter and efficient solution (no variables and no conditional instructions):

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:key name="kPersById" match="Person" use="@ID"/>
 <xsl:key name="kChildrenOf" match="Person" use="@ChildOf"/>

  <xsl:template match="/*">
    <xsl:apply-templates select="Person[not(key('kPersById', @ChildOf))][1]" mode="start"/>
  </xsl:template>
  <xsl:template match="Person" mode="start">
    <ul>
      <li><xsl:value-of select="@FirstName" /></li>
      <xsl:apply-templates select="key('kChildrenOf', @ID)[1]" mode="start"/>
      <xsl:apply-templates select="key('kChildrenOf', @ChildOf)[position() > 1]"/>
    </ul>
  </xsl:template>

  <xsl:template match="Person">
      <li><xsl:value-of select="@FirstName" /></li>  
      <xsl:apply-templates select="key('kChildrenOf', @ID)[1]" mode="start"/>
  </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<Tree>
    <Person ID="1"  FirstName="test" LastName="" ChildOf="0" />
    <Person ID="2" FirstName="test2" LastName="" ChildOf="1" />
    <Person ID="3" FirstName="test3" LastName="" ChildOf="1" />
    <Person ID="4" FirstName="test4" LastName="" ChildOf="1" />
    <Person ID="6" FirstName="test6" LastName="" ChildOf="3" />
    <Person ID="5" FirstName="test5" LastName="" ChildOf="0" />
</Tree>

the wanted, correct result is produced:

<ul>
   <li>test</li>
   <ul>
      <li>test2</li>
      <li>test3</li>
      <ul>
         <li>test6</li>
      </ul>
      <li>test4</li>
   </ul>
   <li>test5</li>
</ul>

Do note:

This solution doesn't depend on the top-level Person elements having ChildOf="0". In fact, for the top-level elements this attribute can be completely omitted or different top elements could have ChildOf attributes with different values -- such as when these have been "cut" from another, bigger hierarchy.

Upvotes: 1

michael.hor257k
michael.hor257k

Reputation: 117103

I would suggest you try it this way:

<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="children" match="Person" use="@ChildOf" />

<xsl:template match="/Tree">
    <ul>
        <xsl:apply-templates select="Person[@ChildOf=0]"/>
    </ul>
</xsl:template>

<xsl:template match="Person">
    <li>
        <xsl:value-of select="@FirstName" />
    </li>
    <xsl:variable name="children" select="key('children', @ID)" />
    <xsl:if test="$children">
        <ul>
            <xsl:apply-templates select="$children" />
        </ul>
    </xsl:if>
</xsl:template>   

</xsl:stylesheet>

Upvotes: 3

Related Questions