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