Re: [xsl] Tree from directory listing

Subject: Re: [xsl] Tree from directory listing
From: Geert Josten <Geert.Josten@xxxxxxxxxxx>
Date: Thu, 16 Dec 2004 10:23:28 +0100
Hi,

Hi Geert,
  Thanks a lot for pointing the bug.. I was infact
testing my stylesheet again, and was getting
StackOverFlow error. And your mail pointed the bug..
Thanks for saving my time!

I was actually considering whether the node-set could be eliminated or not. My first impression was: easily, but then I noticed you used the keys on the document fragment. Which is actually quite nice.


Can anyone tell me how that fits into the XSLT 1 recommendation?

By the way, I realize that it opens an alternative to the node-set function! Wrap your fragment in some obscure element name (or just use some namespace) and add a key that only matches that element. Should work, I guess..

I suggest, please post your XSL. It'll help the
original poster.

Very true, here it comes:


<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
    xmlns:exslt="http://exslt.org/common";
    exclude-result-prefixes="exslt">

<xsl:output method="xml" indent="yes" encoding="utf-8" />

<xsl:key name="folders" match="folder" use="@path" />

  <xsl:template match="root">
    <xsl:variable name="folders">
      <!-- create a temporary tree with a folder structure -->
      <xsl:copy>
        <xsl:copy-of select="@*" />

        <xsl:for-each select="file">
          <!-- sort the files for nicer output -->
          <xsl:sort select="@path" />
          <xsl:sort select="@name" />

          <!-- build a folder structure for each file (separately) -->
          <xsl:apply-templates select="." mode="build-folders">
            <xsl:with-param name="path" select="concat(@path, @name)" />
          </xsl:apply-templates>
        </xsl:for-each>
      </xsl:copy>
    </xsl:variable>

    <!-- we have files in separate folder elements, now merge them -->
    <xsl:apply-templates select="exslt:node-set($folders)/node()" mode="merge" />
  </xsl:template>

  <xsl:template match="file" mode="build-folders">
    <xsl:param name="path-base" />
    <xsl:param name="path" /> <!-- assumption: path always starts with '/' -->

<!-- folder-count equals the number of slashes in the path, minus one for the file name -->
<xsl:param name="folder-count" select="string-length($path) - string-length(translate($path, '/', '')) - 1"/>


    <xsl:choose>
      <!-- should not occur, actually -->
      <xsl:when test="string-length($path) = 0" />

      <!-- path contains slashes, output a folder -->
      <xsl:when test="$folder-count > 0">
        <!-- Note: folder-count = 1 means at least two slashes -->
        <xsl:variable name="folder-name" select="substring-before(substring-after($path, '/'), '/')" />
        <xsl:variable name="folder-path" select="concat($path-base, '/', $folder-name)" />
        <xsl:variable name="remainder" select="substring-after(substring-after($path, '/'), '/')" />

        <folder name="{$folder-name}" path="{$folder-path}">
          <!-- and recurse to add sub folders -->
          <xsl:apply-templates select="." mode="build-folders">
            <xsl:with-param name="path" select="concat('/', $remainder)" />
            <xsl:with-param name="path-base" select="$folder-path" />
          </xsl:apply-templates>
        </folder>
      </xsl:when>

      <!-- no more slashes, output the file -->
      <xsl:otherwise>
        <!-- note: could do a xsl:copy-of select="." as well -->
        <file name="{@name}" />
      </xsl:otherwise>
    </xsl:choose>
  </xsl:template>

  <xsl:template match="root" mode="merge">
    <xsl:copy>
      <xsl:copy-of select="@*" />

      <!-- insert top-level files -->
      <xsl:copy-of select="file" />

<!-- apply those folders that are the first ones with a specific path -->
<xsl:apply-templates select="folder[generate-id(.) = generate-id(key('folders', @path)[1])]" mode="merge"/>
</xsl:copy>
</xsl:template>


  <xsl:template match="folder" mode="merge">
    <xsl:copy>
      <!-- note: one might want to suppress the path attribute -->
      <xsl:copy-of select="@*" />

<xsl:copy-of select="key('folders', @path)/file" />

<xsl:apply-templates select="key('folders', @path)/folder[generate-id(.) = generate-id(key('folders', @path)[1])]" mode="merge"/>
</xsl:copy>
</xsl:template>


</xsl:stylesheet>

Current Thread