2

I have the following XML coming from a SharePoint control. I would like to transform using XSLT to produce a nested ul>li list. But I am having problem as I iterate each row, the primary folder repeats for each row, rather than making one primary Folder node and adding values of Menu_Display_name underneath that node to mimic a treeview... My XML is like:

<dsQueryResponse>
    <NewDataSet>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ CORP HR TIME SELF SERVICE" SECONDARY_FOLDER="Time" MENU_DISPLAY_NAME="Create Timecard"></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ CORP HR TIME SELF SERVICE" SECONDARY_FOLDER="Time" MENU_DISPLAY_NAME="Recent Timecards"></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ CORP HR TIME SELF SERVICE" SECONDARY_FOLDER="Time" MENU_DISPLAY_NAME="Templates"></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ CORP HR TIME SELF SERVICE" SECONDARY_FOLDER="Time" MENU_DISPLAY_NAME="Timecard Search"></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ CORP EXP ENTRY" SECONDARY_FOLDER="" MENU_DISPLAY_NAME="Expenses Home"></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ HR EMP SELF SERVICE" SECONDARY_FOLDER="" MENU_DISPLAY_NAME="Accommodation Request"></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ HR EMP SELF SERVICE" SECONDARY_FOLDER="" MENU_DISPLAY_NAME="Additional Personal Information" ></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ HR EMP SELF SERVICE" SECONDARY_FOLDER="" MENU_DISPLAY_NAME="All Actions Awaiting Your Attention"></Row>
        <Row DOMAIN_USERNAME="someUser" PRIMARY_FOLDER="XYZ HR EMP SELF SERVICE" SECONDARY_FOLDER="" MENU_DISPLAY_NAME="Appraisals"></Row>
    </NewDataSet>
</dsQueryResponse>

my "Poor man's attempt" at XSL is like this:

 <xsl:template name="dvt_1.body">
    <xsl:param name="Rows" />
    <xsl:param name="FirstRow" />
    <xsl:param name="LastRow" />
    <ul>
      <xsl:for-each select="$Rows">
        <xsl:if test="position() &gt;= $FirstRow and position() &lt;= $LastRow">
          <xsl:call-template name="dvt_1.rowview" />
        </xsl:if>
      </xsl:for-each>
    </ul>
  </xsl:template>


  <xsl:template name="dvt_1.rowview">

    <li class="isFolder isExpanded">
       <xsl:value-of select="@PRIMARY_FOLDER" />
       <xsl:choose>
         <xsl:when test="@SECONDARY_FOLDER != ''">
           <ul>
             <li class="isFolder isExpanded">
               <xsl:value-of select="@SECONDARY_FOLDER" />
               <ul>
                  <li><xsl:value-of select="@MENU_DISPLAY_NAME" /></li>
               </ul>
             </li>
           </ul>
         </xsl:when>
         <xsl:otherwise>
           <ul>
              <li><xsl:value-of select="@MENU_DISPLAY_NAME" /></li>
           </ul>
          </xsl:otherwise>
        </xsl:choose>
    </li>

  </xsl:template>

And my desired xsl output is like this:

<div id="navigator">
        <ul>
            <li class="isFolder isExpanded">
               XYZ CORP HR TIME SELF SERVICE    
                <ul>
                    <li class="isFolder isExpanded">
                        Time
                            <ul>
                                <li><a href="#" target="_tab">Create Timecard</a></li>
                                <li><a href="#" target="_tab">Recent Timecards</a></li>
                                <li><a href="#" target="_tab">Templates</a></li>
                                <li><a href="#" target="_tab">Timecard Search</a></li>                                   
                            </ul>
                    </li>
                </ul>        
            </li>
            <li class="isFolder isExpanded">
                XYZ CORP EXP ENTRY
                <ul>
                    <li><a href="#" target="_tab">Expense Home</a></li>      
                </ul>
            </li> 
            <li class="isFolder isExpanded">
                XYZ HR EMP SELF SERVICE
                <ul>
                    <li><a href="#" target="_tab">Accommodation Request</a></li>
                    <li><a href="#" target="_tab">Additional Personal Information</a></li>
                    <li><a href="#" target="_tab">All Actions Awaiting Your Attention</a></li>
                    <li><a href="#" target="_tab">Appraisals</a></li>
                </ul>
            </li>
        </ul>
    </div>

Can someone please help me achieve this using xslt?

Athapali
  • 1,091
  • 4
  • 25
  • 48

1 Answers1

4

Muenchian grouping is indeed what you need to be looking at in XSLT 1.0 (which is what Sharepoint uses I believe). First you grouping by the PRIMARY_VALUE attribute, so you have a key like this:

<xsl:key name="primary" match="Row" use="@PRIMARY_FOLDER" />

But I will assume you can possibly have multiple SECONDARY_FOLDER for a given PRIMARY_VALUE, so you would need a second key:

<xsl:key name="secondary" match="Row" use="concat(@PRIMARY_FOLDER, '|', @SECONDARY_FOLDER)" />

You start off by selecting the rows with the first occurrence of each PRIMARY_FOLDER value

<xsl:for-each select="$rows[generate-id() = generate-id(key('primary', @PRIMARY_FOLDER)[1])]">

And within this, you then select the rows with the distinct SECONDARY_VALUE to form the basis of your nested lists

<xsl:apply-templates select="key('primary', @PRIMARY_FOLDER)[generate-id() = generate-id(key('secondary', concat(@PRIMARY_FOLDER, '|', @SECONDARY_FOLDER))[1])]" mode="secondary" />

The only extra work is that you have slightly different behaviour based on whether the SECONDARY_FOLDER is populated or not. You can do this with two separate templates though.

Try this XSLT:

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
    <xsl:output method="html" indent="yes" />

    <xsl:key name="primary" match="Row" use="@PRIMARY_FOLDER" />
    <xsl:key name="secondary" match="Row" use="concat(@PRIMARY_FOLDER, '|', @SECONDARY_FOLDER)" />

    <xsl:variable name="rows" select="//Row" />

    <xsl:template match="/">
      <ul>
          <xsl:for-each select="$rows[generate-id() = generate-id(key('primary', @PRIMARY_FOLDER)[1])]">
              <li class="isFolder isExpanded">
                  <xsl:value-of select="@PRIMARY_FOLDER" />
                  <xsl:apply-templates select="key('primary', @PRIMARY_FOLDER)[generate-id() = generate-id(key('secondary', concat(@PRIMARY_FOLDER, '|', @SECONDARY_FOLDER))[1])]" mode="secondary" />
              </li>
          </xsl:for-each>
      </ul>
    </xsl:template>

    <xsl:template match="Row[@SECONDARY_FOLDER != '']" mode="secondary">
        <li class="isFolder isExpanded">
            <xsl:value-of select="@SECONDARY_FOLDER" />
            <ul>
                <xsl:apply-templates select="key('secondary', concat(@PRIMARY_FOLDER, '|', @SECONDARY_FOLDER))" />
            </ul>
        </li>
    </xsl:template>

    <xsl:template match="Row" mode="secondary">
        <xsl:apply-templates select="key('primary', @PRIMARY_FOLDER)" />
    </xsl:template>

    <xsl:template match="Row">
        <li>
            <a href="#" target="_tab">
                <xsl:value-of select="@MENU_DISPLAY_NAME" />
            </a>
        </li>
    </xsl:template>
</xsl:stylesheet>

See this in action at http://xsltransform.net/pPzifq9.

Note, if your actual XML has namespaces, you would need to modify the XSLT to take these into account.

Tim C
  • 70,053
  • 14
  • 74
  • 93