xsl-list
[Top] [All Lists]

Ordering nodes by number of children

2004-02-22 06:31:10
I'm a newbie with XSL, and after long searching, and mainly thanks to the 
information in the list, I finally managed to solve the problem. Consider the 
following structure:
 
<?xml version="1.0"?>
 
<node label="aa" type="12">
 <node label="bb" type="21"/>
 <node label="cc" type="22">
  <node label="dd" type="44"/>
  <node label="ll" type="33">
   <node label="gg" type="22"/>
   <node label="hh" type="22"/>
  </node>
  <node label="ee" type="77"/>
  <node label="ff" type="55">
   <node label="gg" type="44"/>
   <node label="hh" type="44"/>
  </node>
  <node label="ww" type="44"/>
  <node label="xx" type="44"/>
 </node>
 <node label="yy" type="33"/>
 <node label="ll" type="33">
  <node label="gg" type="22"/>
  <node label="hh" type="22"/>
 </node>
 <node label="aa" type="22"/>
</node>
 
 
... etc
 
and suppose it was to be shown grafically on a tree display, in the same 
fashion as Windows Explorer shows them (folder icon and + expanding sign if the 
node has children, file icon and no sign if it has no children), and suppose 
that the engine that does the graphical display goes node by node in the 
hierarchy of the original xml. The problem is that for a long list, "files" 
(meaning a node without children) and "folders" (meaning a node with children) 
are scattered all over the place, and it don't look nice, basically... :) I was 
looking for a xsl script that would arrange the original xml in the same 
fashion that Windows explorer displays lists, that is, folders first, files 
later, for every level - that is, I was looking for the following output from 
the original xml:
 
<?xml version="1.0"?>
<node label="aa" type="12">
 <node label="cc" type="22">
  <node label="ll" type="33">
   <node label="gg" type="22"/>
   <node label="hh" type="22"/>
  </node>
  <node label="ff" type="55">
   <node label="gg" type="44"/>
   <node label="hh" type="44"/>
  </node>
  <node label="dd" type="44"/>
  <node label="ee" type="77"/>
  <node label="ww" type="44"/>
  <node label="xx" type="44"/>
 </node>
 <node label="ll" type="33">
  <node label="gg" type="22"/>
  <node label="hh" type="22"/>
 </node>
 <node label="bb" type="21"/>
 <node label="yy" type="33"/>
 <node label="aa" type="22"/>
</node>

Well, the solution that worked for me is below, and here is a short : First, 
template match="/node" instead of the root template match="/" since I do not 
want to access the document root, only my root node, which (should be) the only 
"node" child of the document root, so effectively it should be ran only once ?? 
(I hope :) )
 
Then a copy tag is opened, so that the root node correctly encompasses 
everything, and so that the actual attribute content of the root node can be 
copied, so that the script runs regardless of the node's attribute contents. 
The copy of all the attributes is output first, and then we iterate through all 
the children of the root node (which are again all nodes) with a for each. This 
is, metaphorically speaking, iteration in level 1 if we consider the root node 
(not the document root) as level 0. With using select="./*" in the for each, a 
direct reference to "node" is avoided, hopefully allowing that the script runs 
no matter what the names are named, that is it runs only according to number of 
element-type children (?)..
 
The children in level 1 are then arranged according to the number of children, 
with the elements with least children (0) set to the back (descending). This 
will ensure that the script afterwards, when examining in the next levels of 
iteration, will go through the "folders" first, and end with the "files"
 
We then call the ArrangeDirectory template for each node in the first level, 
using the "current node" itself as a parameter.
 
ArrangeDirectory accepts the current node, and examines it using choose. If it 
is has children (that is, <xsl:when test="$CurrentNode/child::*">), then it is 
a folder, so again a copy tag is opened, so that the node correctly encompasses 
everything, with the actual attribute content of the root node copied. After 
this, a for each iteration through the level defined by the current node is 
performed, first so that sorting is used to sort the files and directories in 
it, and second, so that ArrangeDirectory can be recursively called for each of 
the child nodes. After the for each loop is over, then the copy tag is closed, 
which closes the "parenting" folder, so that the inheritance is proper (this 
trick with the possibillity for separating the opening and closing tags of the 
copied element if it is a "folder" was the hardest for me, I kept on copying 
the the folders directly and the hierarc
 hy kept on being messed up).. The other part of the having-children condition, 
is defined w!
ith the  <xsl:otherwise>, and it means that we came across a file, in which 
case, we just directly copy the element.
 
And here is the xsl:
 
 
<?xml version="1.0"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="1.0">
 <xsl:output method="xml" indent="yes"/>

 <xsl:template match="/node">
 
  <xsl:copy>
   <xsl:copy-of select="@*"/>
   
   <xsl:for-each select="./*">
   
    <xsl:sort select="count(child::*)" data-type="number" order="descending"/>
    
    <xsl:call-template name="ArrangeDirectory">
     <xsl:with-param name="CurrentNode" select="."/>
    </xsl:call-template>
    
   </xsl:for-each>
   
  </xsl:copy>
 </xsl:template>
 
 
 <xsl:template name="ArrangeDirectory">
  <xsl:param name="CurrentNode"/>
  
  <xsl:choose>
   <xsl:when test="$CurrentNode/child::*">
   
    <xsl:copy>
     <xsl:copy-of select="@*"/>
     
     <xsl:for-each select="$CurrentNode/child::*">
      <xsl:sort select="count(child::*)" data-type="number" order="descending"/>
      
      <xsl:call-template name="ArrangeDirectory">
       <xsl:with-param name="CurrentNode" select="."/>
      </xsl:call-template>
     </xsl:for-each>
     
    </xsl:copy>
   </xsl:when>
   
   <xsl:otherwise>
    <xsl:copy-of select="."/>
   </xsl:otherwise>
  </xsl:choose>
  
 </xsl:template>
 
</xsl:stylesheet>

I hope this can help someone - again, havent had much experience with XSL, so 
maybe the debate is a little amateurish (I have a bit of a programmer bckg so I 
still think in counters and loops), however you are more then welcome to set it 
in the right frame.
 
Best
smilen 
 

 XSL-List info and archive:  http://www.mulberrytech.com/xsl/xsl-list



<Prev in Thread] Current Thread [Next in Thread>