Re: [xsl] most efficient flat file listing to hierarchical

Subject: Re: [xsl] most efficient flat file listing to hierarchical
From: James Fuller <jim.fuller@xxxxxxxxxxxxxx>
Date: Thu, 11 Jan 2007 11:38:46 +0100
Andrew Welch wrote:

On 1/11/07, James Fuller <jim.fuller@xxxxxxxxxxxxxx> wrote:

Hello All,

Can anyone propose a pure xslt (1 or 2) solution to transforming the
following flat xml structure of directory paths into a hierarchical
(nested) xml.

<?xml version='1.0'?>
<listing>
    <item>cn/test.xml</item>
    <item>en</item>
    <item>en/test.html</item>
    <item>en/test1.html</item>
    <item>en/resource</item>
    <item>en/resource/style</item>
    <item>en/resource/style/test.css</item>
    <item>favicon.ico</item>
    <item>cn</item>
</listing>

to


<dir> <file name="favicon.ico"/> <dir name="cn"> <file name="test.xml"/> </dir> <dir name="en"> <file name="test.html"/> <file name="test1.html"/> <dir name="resource"> <dir name="style"> <file name="test.css"/> </dir> </dir> </dir> </dir>



...trickier than it first looks :)


This should do the job:

<xsl:stylesheet version="2.0"
 xmlns:xs="http://www.w3.org/2001/XMLSchema";
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
 exclude-result-prefixes="xs">

<xsl:template match="listing">
    <dir>
        <xsl:call-template name="process">
            <xsl:with-param name="depth" select="1" as="xs:integer"/>
            <xsl:with-param name="seq" select="item"/>
        </xsl:call-template>
    </dir>
</xsl:template>

<xsl:template name="process">
<xsl:param name="depth" as="xs:integer"/>
<xsl:param name="seq"/>
<xsl:for-each-group select="$seq" group-by="tokenize(., '/')[$depth]">
<xsl:variable name="part" select="tokenize(., '/')[$depth]"/>
<xsl:choose>
<xsl:when test="contains($part, '.')">
<file name="{$part}"/>
</xsl:when>
<xsl:otherwise>
<dir name="{$part}">
<xsl:call-template name="process">
<xsl:with-param name="depth" select="$depth + 1"/>
<xsl:with-param name="seq" select="$seq[tokenize(., '/')[$depth]
= $part]"/>
</xsl:call-template>
</dir>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:template>


</xsl:stylesheet>


The output is:


<dir>
    <dir name="cn">
        <file name="test.xml"/>
    </dir>
    <dir name="en">
        <file name="test.html"/>
        <file name="test1.html"/>
        <dir name="resource">
            <dir name="style">
                <file name="test.css"/>
            </dir>
        </dir>
    </dir>
    <file name="favicon.ico"/>
</dir>


nice....the recursion route seems to be the general approach, just about scratched it out myself (w/o the part bit)

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"; version="2.0">


<xsl:template match="data">

  <dir name="root">
          <xsl:call-template name="pdir">
              <xsl:with-param name="n" select="1"/>
              <xsl:with-param name="in" select="//item"/>
          </xsl:call-template>
  </dir>
        </xsl:template>
    <xsl:template name="pdir">
     <xsl:param name="n"/>
      <xsl:param name="in"/>
      <xsl:for-each-group select="$in" group-by="tokenize(.,'/')[$n]">
          <dir src="{.}">
              <xsl:call-template name="pdir">
                  <xsl:with-param name="n" select="$n+1"/>
                  <xsl:with-param name="in" select="current-group()"/>
              </xsl:call-template>             </dir>
       </xsl:for-each-group>
  </xsl:template>
 </xsl:stylesheet>

this is an elegant template, really shows the power and sweetspot hit by XSLT 2.0 (anyone doing this in XSLT 1.0, barring FXSL, this becomes a bit of a nightmare)....any alternate approaches which makes things more efficient (i dont think its possible to optimise the MKay recursive approach).

was thinking there might be an approach using analyze-string instruction...but still working on it.

(IMHO, DaveP should include this in XSLT FAQ, its a keeper and common processing scenario)

ta, J

Current Thread