0

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

Appsum Solutions
  • 999
  • 2
  • 10
  • 30
  • Please show the expected result **as code**. – michael.hor257k Oct 28 '15 at 07:56
  • My bad, I've added the example! – Appsum Solutions Oct 28 '15 at 08:11
  • This is pretty much the same question as this one: http://stackoverflow.com/questions/872067/creating-a-nested-tree-structure-from-a-path-in-xslt – Tomalak Oct 28 '15 at 08:28
  • @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 Oct 28 '15 at 09:01
  • @Devil Please show the expected result as code (2). – michael.hor257k Oct 28 '15 at 09:02
  • @michael.hor257k Added the expected result code – Appsum Solutions Oct 28 '15 at 09:11
  • @Devil The result you show does not match your input. I am afraid I have no more time for this: I have posted a generic solution which you should be able to adapt to your specific requirements. – michael.hor257k Oct 28 '15 at 09:51
  • @michael.hor257k How do you mean, it does not match my input? You asked the expected output, meaning the input that I want it to be. Of course it doesn't match the structure of the XML that is being inputted, that is the question all along. I dont have the expected result yet, that why I ask for a solution to get to it? Thanks anyway for the generic solution. I'm trying to get the expected HTML out of it. If I got the solution, I'll post it here. – Appsum Solutions Oct 28 '15 at 10:37
  • @michael.hor257k This question asks how to turn `rootfolder\folder2\folder2.1\folder2.1.1` into a node hierarchy, the other question asks the exactly same thing. – Tomalak Oct 28 '15 at 10:51
  • @Devil Your input has "Doc1", "Doc2", etc. your output has "Formulier", "Informed consent (IC)" and other things which do not appear in the input. The reason why we ask to see the input alongside the expected output is to better understand the nature of the required transformation. Comparing input to unrelated output is not helpful at all. – michael.hor257k Oct 28 '15 at 12:38
  • @michael.hor257k Ah ok, didn't notice it, changed now – Appsum Solutions Oct 28 '15 at 12:50

1 Answers1

0

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.

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51