Dave
Dave

Reputation: 83

Treating sibling nodes as one for XSLT Muenchian grouping

I'm trying to use Muenchian grouping to display company policy documents in a hierarchical way. The policy documents are all added to SharePoint and tagged with a Language, a category and a document type. I'm then using the XML that this creates.

I need to take the data from 2 merged lists. The way SharePoint works, my XML looks like this (with a Rows element for each SharePoint list): I've had to simplify this to remove the hundreds of attributes that SP adds as it went over the stackoverflowsize limit, but the structure is accurate.

<dsQueryResponse>
  <Rows>
    <Row Title="Testitem" Category="Category 1" Language="English" DocumentType="Content" />
  </Rows>
  <Rows>
    <Row Title="Doc1" Category="Category 1" Language="English" DocumentType="Policy" />
    <Row Title="Policy2" Category="Category 2" Language="English" DocumentType="Policy" />
    <Row Title="Policy3" Category="Category 1" Language="Nederlands (Dutch)" DocumentType="Form" />
  </Rows>
</dsQueryResponse>

Current xsl looks like this:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:SharePoint="Microsoft.SharePoint.WebControls"
  xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer"
  xmlns:agg="http://schemas.microsoft.com/sharepoint/aggregatesource"
  xmlns:asp="http://schemas.microsoft.com/ASPNET/20"
  xmlns:d="http://schemas.microsoft.com/sharepoint/dsp"
  xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
  xmlns:ddwrt2="urn:frontpage:internal"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:x="http://www.w3.org/2001/XMLSchema"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="xsl msxsl ddwrt" 
  ddwrt:oob="true"
>
  <xsl:output indent="yes" omit-xml-declaration="yes" />
  <xsl:key name="byLANGUAGE" match="/dsQueryResponse/Rows/Row" use="@Language" />
  <xsl:key name="byCATEGORY" match="/dsQueryResponse/Rows/Row" use="concat(@Language, '+', @Category)" />

  <xsl:template match="/">
    <xsl:apply-templates select="/dsQueryResponse/Rows/Row[count(. | key('byLANGUAGE', @Language)[1]) = 1]/@Language">
      <xsl:sort select="." order="ascending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="@Language">
    <br /><b>Below you can see all policies in <xsl:value-of select="." /></b><br /><br />
    <xsl:variable name="thisLanguage" select="key('byLANGUAGE', .)" />
    <xsl:apply-templates select="$thisLanguage[count(. | key('byCATEGORY', concat(@Language, '+', @Category))[1])= 1]/@Category">
      <xsl:sort select="." order="ascending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="@Category">
    <br />Category: <xsl:value-of select="." />
    <xsl:apply-templates select="key('byCATEGORY', concat(../@Language, '+', .))" />
  </xsl:template>

  <xsl:template match="/dsQueryResponse/Rows/Row">
    <br />Title: <xsl:value-of select="@Title" />
  </xsl:template>
</xsl:stylesheet>

and current result looks like this (a mess):

Below you can see all policies in English 

Category: Category 1
Title: Testitem

Category: Category 2
Title: Policy2

Below you can see all policies in English

Category: Category 1
Title: Testitem

Category: Category 2
Title: Policy2

Below you can see all policies in Nederlands (Dutch)

Category: Category 1
Title: Policy3 

**My desired output is:**

Below you can see all policies in English 

Category: Category 1
Title: Testitem
Title: Doc1

Category: Category 2
Title: Policy2

Below you can see all policies in Nederlands (Dutch)

Category: Category 1
Title: Policy3 

So I seem to be getting lots of duplication, presumably because something is getting called twice, once for each of the Rows elements? I'm a novice with XSL so any help would be appreciated.

========== UPDATE ==========

The code below now works for me in SharePoint (the bulky looking Choose statements aren't technically necessary but I needed them to get rid of the non-English characters for the JQuery that I've put on top of this):

Full XSLT:

<xsl:stylesheet xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:x="http://www.w3.org/2001/XMLSchema" xmlns:d="http://schemas.microsoft.com/sharepoint/dsp" xmlns:agg="http://schemas.microsoft.com/sharepoint/aggregatesource" version="1.0" exclude-result-prefixes="xsl msxsl ddwrt" xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime" xmlns:asp="http://schemas.microsoft.com/ASPNET/20" xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:SharePoint="Microsoft.SharePoint.WebControls" xmlns:ddwrt2="urn:frontpage:internal" ddwrt:oob="true">

<xsl:output indent="yes" omit-xml-declaration="yes"/>
<xsl:key name="byLANGUAGE" match="/dsQueryResponse/Rows/Row" use="@Language" />
<xsl:key name="byCATEGORY" match="/dsQueryResponse/Rows/Row" use="concat(@Language, '+', @Category)" />

<xsl:template match="/">
<div id="tabs" style="display:none;">
<!--Create tabs-->
<ul>
<xsl:apply-templates select="/dsQueryResponse/Rows/Row[count(. | key('byLANGUAGE', @Language)[2]) = 1]/@Language">
<xsl:sort select="." order="ascending" />
</xsl:apply-templates>
</ul>
<!--Create content-->
<xsl:apply-templates select="/dsQueryResponse/Rows/Row[count(. | key('byLANGUAGE', @Language)[2]) = 1]/@Language" mode="pass2">
<xsl:sort select="." order="ascending" />
</xsl:apply-templates>
</div>
</xsl:template>

<!--Do a first pass to create the tabs -->
<xsl:template match="@Language">
<li>
<xsl:choose>
<xsl:when test=". = 'English'"><xsl:text disable-output-escaping="yes">&lt;a href="#English"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Bahasa'"><xsl:text disable-output-escaping="yes">&lt;a href="#Bahasa"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = '简体中文 (Chinese)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Chinese"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Nederlands (Dutch)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Dutch"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Français (French)'"><xsl:text disable-output-escaping="yes">&lt;a href="#French"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Deutsch (German)'"><xsl:text disable-output-escaping="yes">&lt;a href="#German"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Italiano (Italian)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Italian"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = '日本語 (Japanese)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Japanese"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = '한국의 (Korean)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Korean"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Polski (Polish)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Polish"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Português (Portuguese)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Portuguese"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'PyccĸИЙ (Russian)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Russian"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Castellano (Spanish)'"><xsl:text disable-output-escaping="yes">&lt;a href="#Spanish"&gt;</xsl:text><xsl:value-of select="." /><xsl:text disable-output-escaping="yes">&lt;/a&gt;</xsl:text></xsl:when>
</xsl:choose>
</li>
</xsl:template>

<!--and a second pass to do everything else-->
<xsl:template match="@Language" mode="pass2">
<xsl:choose>
<xsl:when test=". = 'English'"><xsl:text disable-output-escaping="yes">&lt;div id="English"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Bahasa'"><xsl:text disable-output-escaping="yes">&lt;div id="Bahasa"&gt;</xsl:text></xsl:when>
<xsl:when test=". = '简体中文 (Chinese)'"><xsl:text disable-output-escaping="yes">&lt;div id="Chinese"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Nederlands (Dutch)'"><xsl:text disable-output-escaping="yes">&lt;div id="Dutch"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Français (French)'"><xsl:text disable-output-escaping="yes">&lt;div id="French"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Deutsch (German)'"><xsl:text disable-output-escaping="yes">&lt;div id="German"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Italiano (Italian)'"><xsl:text disable-output-escaping="yes">&lt;div id="Italian"&gt;</xsl:text></xsl:when>
<xsl:when test=". = '日本語 (Japanese)'"><xsl:text disable-output-escaping="yes">&lt;div id="Japanese"&gt;</xsl:text></xsl:when>
<xsl:when test=". = '한국의 (Korean)'"><xsl:text disable-output-escaping="yes">&lt;div id="Korean"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Polski (Polish)'"><xsl:text disable-output-escaping="yes">&lt;div id="Polish"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Português (Portuguese)'"><xsl:text disable-output-escaping="yes">&lt;div id="Portuguese"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'PyccĸИЙ (Russian)'"><xsl:text disable-output-escaping="yes">&lt;div id="Russian"&gt;</xsl:text></xsl:when>
<xsl:when test=". = 'Castellano (Spanish)'"><xsl:text disable-output-escaping="yes">&lt;div id="Spanish"&gt;</xsl:text></xsl:when>
</xsl:choose>
<b>Below you can see all policies in <xsl:value-of select="." /></b><br /><br/>

<div class="accordion">
<xsl:variable name="thisLanguage" select="key('byLANGUAGE', .)" />
<xsl:apply-templates select="$thisLanguage[count(. | key('byCATEGORY', concat(@Language, '+', @Category))[2])= 1]/@Category">
<xsl:sort select="." order="ascending" />
</xsl:apply-templates>
</div>

<xsl:text disable-output-escaping="yes">&lt;/div&gt;</xsl:text>
</xsl:template>

<xsl:template match="@Category">
<h3>
<xsl:value-of select="." />
</h3>

<div class="accordionContent">
<xsl:apply-templates select="key('byCATEGORY', concat(../@Language, '+', .))"/>
</div>
</xsl:template>

<xsl:template match="/dsQueryResponse/Rows/Row">
<div class="policy-item">
<div class="policy-item-title">
<xsl:value-of select="@Title" />
</div>
<xsl:if test="@ItemHtml != ''">
<div class="policy-item-content">
<xsl:value-of select="@ItemHtml" disable-output-escaping="yes" />
</div>
</xsl:if>
</div>
</xsl:template>

</xsl:stylesheet>

Upvotes: 2

Views: 177

Answers (1)

Tomalak
Tomalak

Reputation: 338208

Here is what I would do:

<xsl:stylesheet 
  version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns:SharePoint="Microsoft.SharePoint.WebControls"
  xmlns:__designer="http://schemas.microsoft.com/WebParts/v2/DataView/designer"
  xmlns:agg="http://schemas.microsoft.com/sharepoint/aggregatesource"
  xmlns:asp="http://schemas.microsoft.com/ASPNET/20"
  xmlns:d="http://schemas.microsoft.com/sharepoint/dsp"
  xmlns:ddwrt="http://schemas.microsoft.com/WebParts/v2/DataView/runtime"
  xmlns:ddwrt2="urn:frontpage:internal"
  xmlns:msxsl="urn:schemas-microsoft-com:xslt"
  xmlns:x="http://www.w3.org/2001/XMLSchema"
  xmlns:xs="http://www.w3.org/2001/XMLSchema"
  exclude-result-prefixes="msxsl ddwrt SharePoint __designer agg asp d ddwrt2 x" 
  ddwrt:oob="true"
>
  <xsl:output method="html" indent="yes" omit-xml-declaration="yes" />
  <xsl:key name="kRowByLanguage" match="Row" use="@Language" />
  <xsl:key name="kRowByCategory" match="Row" use="concat(@Language, '+', @Category)" />

  <xsl:template match="/">
    <html>
      <body>
        <xsl:apply-templates select="dsQueryResponse/Rows" />
      </body>
    </html>
  </xsl:template>

  <xsl:template match="Rows">
    <xsl:apply-templates mode="language" select="Row[
      count(. | key('kRowByLanguage', @Language)[1]) = 1
    ]">
      <xsl:sort select="@Language" order="ascending" />
    </xsl:apply-templates>
  </xsl:template>

  <xsl:template match="Row" mode="language">
    <xsl:variable name="myCategory" select="concat(@Language, '+', @Category)" />
    <div class="language">
      <div>
        <xsl:text>Below you can see all policies in </xsl:text>
        <xsl:value-of select="@Language" />
      </div>
      <div>
        <xsl:apply-templates mode="category" select="key('kRowByLanguage', @Language)[
            count(. | key('kRowByCategory', $myCategory)[1]) = 1
        ]">
          <xsl:sort select="@Category" order="ascending" />
        </xsl:apply-templates>
      </div>
    </div>
  </xsl:template>

  <xsl:template match="Row" mode="category">
    <xsl:variable name="myCategory" select="concat(@Language, '+', @Category)" />
    <div class="catecory">
      <div>
        <xsl:text>Category: </xsl:text>
        <xsl:value-of select="@Category" />
      </div>
      <div>
        <xsl:apply-templates mode="title" select="key('kRowByCategory', $myCategory)" />
      </div>
    </div>
  </xsl:template>

  <xsl:template match="Row" mode="title">
    <div>
      <xsl:text>Title: </xsl:text>
      <xsl:value-of select="@Title" />
    </div>
  </xsl:template>
</xsl:stylesheet>

output (I generally recommend using CSS for formatting your output, instead of using <br>):

<html>
   <body>
      <div class="language">
         <div>Below you can see all policies in English</div>
         <div>
            <div class="catecory">
               <div>Category: Category 1</div>
               <div>
                  <div>Title: Testitem</div>
                  <div>Title: Doc1</div>
               </div>
            </div>
         </div>
      </div>
      <div class="language">
         <div>Below you can see all policies in Nederlands (Dutch)</div>
         <div>
            <div class="catecory">
               <div>Category: Category 1</div>
               <div>
                  <div>Title: Policy3</div>
               </div>
            </div>
         </div>
      </div>
   </body>
</html>

The primary change is the move of the subgroup key (language + category) into the variable $myCategory, this enables selecting the right nodes in your double grouping.

Your original expression:

$thisLanguage[count(. | key('byCATEGORY', concat(@Language, '+', @Category))[1])= 1]

refers to the wrong context inside the predicate (it refers to $thisLanguage, which does not have @Language or @Category). Moving the concat(...) out of this expression generates the correct results.

The secondary change is adding some structure to the output so it can be targeted more easily via CSS.

Also note the output method and the use of different template modes.

General hint: You have many namespace declarations in your stylesheet. Remove any you don't actively use in the stylesheet.

Upvotes: 1

Related Questions