0
votes

I'm trying to achieve a recursive (tree) list in XSLT 1.0 starting from an XML that looks like this:

<list>
  <row>
    <icon>http://server/app/icon.gif</icon>
    <title>Document</title>
    <location>Root\Formulier</location> 
  </row>
  <row>
    <icon>http://server/app/icon.gif</icon>
    <title>Handleiding1</title>
    <location>Root\Handleidingen</location> 
  </row>
  <row>
    <icon>http://server/app/icon.gif</icon>
    <title>Form</title>
    <location>Root\Formulier\Informed consent (IC)</location> 
  </row>
  <row>
    <icon>http://server/app/icon.gif</icon>
    <title>Handleiding2</title>
    <location>Root\Handleidingen</location> 
  </row>
</list>

This has to use XSLT 1.0, because our SharePoint does not support 2.0 yet.

It should look like a tree in Windows Explorer.

The current XSLT code I have is:

    <?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" exclude-result-prefixes="msxsl">
  <xsl:output method="xml" indent="yes" />
  <xsl:key name="groups" match="/list/row" use="location" />
  <xsl:template match="/list">
    <div class="idocument-list">
      <xsl:apply-templates select="row[generate-id() = generate-id(key('groups', location)[1])]"/>
    </div>
  </xsl:template>
  <xsl:template match="row">
    <div style="margin-bottom:5px;">
      <ul>
        <li>
          <img border="0" style="align:left;" src="/_layouts/15/images/folder.gif?rev=23" alt="map" />
          <span class="ms-textLarge idocumentlist-title">
            <xsl:value-of select="substring-after(location,'Root\')"/>
          </span>
          <ul style="display:none;">
            <xsl:for-each select="key('groups', location)">
              <li>
                <img border="0" style="align:left;">
                  <xsl:attribute name="src">
                    <xsl:value-of select="icon"/>
                  </xsl:attribute>
                </img>
                <span>
                  <a>
                    <xsl:attribute name="href">
                      <xsl:value-of select="link"/>
                    </xsl:attribute>
                    <xsl:value-of select="title"/>
                  </a>
                </span>
              </li>
            </xsl:for-each>
          </ul>
        </li>
      </ul>
    </div>
  </xsl:template>
</xsl:stylesheet>

which shows the result like:

result

Where for example 'Formulier\Informed consent (IC)' shows all the folders after each other, while it should be split on the \ and show 'Formulier' as the parent of 'Informed consent (IC)'. (I substringed the 'Root\' location out, but it should show on top as root node)

Example result code:

<div class="idocument-list">
  <ul>
    <li>
      <img style="align: left;" alt="map" src="..." border="0">
      <span class="ms-textLarge idocumentlist-title">Root</span>
      <ul>
        <li>
          <img style="align: left;" alt="map" src="..." border="0">
          <span class="ms-textLarge idocumentlist-title">Formulier</span>
          <ul>
            <li>
              <img style="align: left;" alt="map" src="..." border="0">
              <span class="ms-textLarge idocumentlist-title">Informed consent (IC)</span>
              <ul>
                <li>
                  <img style="align: left;" src="..." border="0">
                  <span>
                    <a href="...">Form</a>
                  </span>
                </li>
              </ul>
            </li>
            <li>
              <img style="align: left;" alt="map" src="..." border="0">
              <span>
                <a href="...">Document</a>
              </span>
            </li>
          </ul>
        </li>
        <li>
          <img style="align: left;" alt="map" src="..." border="0">
          <span class="ms-textLarge idocumentlist-title">Handleidingen</span>
          <ul>
            <li>
              <img style="align: left;" src="..." border="0">
              <span>
                <a href="...">Handleiding1</a>
              </span>
            </li>
            <li>
              <img style="align: left;" src="..." border="0">
              <span>
                <a href="...">Handleiding2</a>
              </span>
            </li>
          </ul>
        </li>
      </ul>
    </li>
  </ul>
</div>

Can anyone give me a source of information or code to play with to achieve something like this with just XSLT 1.0?

Thanks in advance.

Nils

1
Please show the expected result as code. - michael.hor257k
My bad, I've added the example! - Appsum Solutions
This is pretty much the same question as this one: stackoverflow.com/questions/872067/… - Tomalak
@Tomalak This is different, because here you need to create a node for each distinct location step - not just for each document, as the other answer does. - michael.hor257k
@Devil Please show the expected result as code (2). - michael.hor257k

1 Answers

0
votes

I am afraid this may be much more complex than it seems.

  • If you want to display a node for each folder (whether it contains a document or not), you must start by tokenizing the locations into individual folders.
  • Next, you must eliminate the duplicates from the result.*
  • The third step is arrange the folders into a hierarchical structure and also re-attach the documents to their folders.

Here's a sketch you could use as the basis for your stylesheet:

XSLT 1.0

<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:key name="location-by-path" match="location" use="@path" />
<xsl:key name="location-by-parent" match="location" use="@parent-path" />
<xsl:key name="document-by-location" match="row" use="location" />

<xsl:variable name="xml" select="/" />

<!-- 1st pass: tokenize paths to individual locations -->
<xsl:variable name="locations">
    <xsl:for-each select="/list/row">
        <xsl:call-template name="tokenize">
            <xsl:with-param name="text" select="location"/>
        </xsl:call-template>
    </xsl:for-each>
</xsl:variable>

<!-- 2nd pass: distinct locations only -->
<xsl:variable name="distinct-locations">
    <xsl:copy-of select="exsl:node-set($locations)/location[count(. | key('location-by-path', @path)[1]) = 1]"/>
</xsl:variable> 
<xsl:variable name="distinct-locations-set" select="exsl:node-set($distinct-locations)" />

<!-- output -->
<xsl:template match="/list">
    <root>
        <!-- start with progenitor locations  -->
        <xsl:apply-templates select="$distinct-locations-set/location[@parent-path='']"/>
    </root>
</xsl:template>

<xsl:template match="location">
    <xsl:variable name="path" select="@path" />
    <xsl:element name="{@name}">
        <!-- set context to XML input  -->
        <xsl:for-each select="$xml">
            <!-- get documents  -->
            <xsl:apply-templates select="key('document-by-location', $path)"/>
        </xsl:for-each>
        <!-- set context to distinct locations  -->
        <xsl:for-each select="$distinct-locations-set">
            <!-- get subdirectories  -->
            <xsl:apply-templates select="key('location-by-parent', concat($path, '\'))"/>
        </xsl:for-each>
    </xsl:element>
</xsl:template>

<xsl:template match="row">
    <document name="{title}"/>
</xsl:template>

<xsl:template name="tokenize">
    <xsl:param name="text"/>
    <xsl:param name="parent-path"/>
    <xsl:param name="delimiter" select="'\'"/>
    <xsl:variable name="token" select="substring-before(concat($text, $delimiter), $delimiter)" />
    <xsl:if test="$token">
        <location name="{$token}" path="{concat($parent-path, $token)}" parent-path="{$parent-path}"/>
    </xsl:if>
    <xsl:if test="contains($text, $delimiter)">
        <!-- recursive call -->
        <xsl:call-template name="tokenize">
            <xsl:with-param name="text" select="substring-after($text, $delimiter)"/>
            <xsl:with-param name="parent-path" select="concat($parent-path, $token, $delimiter)"/>
        </xsl:call-template>
    </xsl:if>
</xsl:template>


</xsl:stylesheet> 

The result, when applied to your example input, will be:

<?xml version="1.0" encoding="UTF-8"?>
<root>
   <rootfolder>
      <document name="Doc2"/>
      <folder1>
         <document name="Doc3"/>
         <folder1.1>
            <folder1.1.1>
               <document name="Doc1"/>
            </folder1.1.1>
         </folder1.1>
      </folder1>
      <folder2>
         <folder2.1>
            <folder2.1.1>
               <document name="Doc4"/>
            </folder2.1.1>
         </folder2.1>
      </folder2>
   </rootfolder>
</root>

(*)You need to be familiar with Muenchian grouping in order to understand this solution.