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

Subject: Re: [xsl] most efficient flat file listing to hierarchical
From: "Dimitre Novatchev" <dnovatchev@xxxxxxxxx>
Date: Thu, 11 Jan 2007 08:26:01 -0800
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).

Why bar FXSL -- are we masochistic? :oO)



Here's my take on this problem. Trying to adhere to the KISS principle, I'm using a two-step approach naturally expressed as functional composition.

Notice that not only is this a litlle easier to understand, but that
the main "powerhorse" (the function f:makeTree() ) is just 17 lines of
code:


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

<xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:template match="/*">
   <xsl:sequence select="f:makeTree(f:tokenizePaths(/*/*), 1)"/>
 </xsl:template>

 <xsl:function name="f:tokenizePaths">
   <xsl:param name="pfilePaths" as="element()+"/>
   <xsl:for-each select="$pfilePaths">
      <item>
         <xsl:for-each select="tokenize(.,'/')">
            <q><xsl:value-of select="."/></q>
         </xsl:for-each>
      </item>
   </xsl:for-each>
 </xsl:function>

 <xsl:function name="f:makeTree" as="element()*">
   <xsl:param name="pItems" as="element()*"/>
   <xsl:param name="pqLevel" as="xs:integer"/>

   <xsl:for-each-group select="$pItems" group-by="q[$pqLevel]">
     <xsl:choose>
       <xsl:when test="count(current-group()) = 1">
         <file name="{q[$pqLevel]}"/>
       </xsl:when>
       <xsl:otherwise>
         <dir name="{q[$pqLevel]}">
           <xsl:sequence select="f:makeTree(current-group(), $pqLevel+1)"/>
         </dir>
       </xsl:otherwise>
     </xsl:choose>
   </xsl:for-each-group>
 </xsl:function>
</xsl:stylesheet>


-- Cheers, Dimitre Novatchev --------------------------------------- Truly great madness cannot be achieved without significant intelligence. --------------------------------------- To invent, you need a good imagination and a pile of junk ------------------------------------- You've achieved success in your field when you don't know whether what you're doing is work or play





On 1/11/07, James Fuller <jim.fuller@xxxxxxxxxxxxxx> wrote:
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