2

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>
Dave
  • 83
  • 6
  • 1
    Is that definitely how your XML looks? I tried your XSLT at http://xsltransform.net/jyH9rMz and the output looks OK to me. You are definitely off to a good start with your XSLT if you are a novice though! – Tim C Apr 01 '15 at 12:11
  • One sort has a wrong context but other than that I think the approach is fine, see http://xsltransform.net/gWmuiJx/1. You will need to edit your question with the real XSLT (`` will never work) and the real XML, I don't see a combination English `Category: Category 2 Title: Policy2` in your sample input – Martin Honnen Apr 01 '15 at 12:12
  • Thanks - I've corrected the code, sorry the xml was wrong. So if the XSLT and XML are both correct and working on xsltransform.net, then I can only assume that SharePoint isn't processing the transform correctly? I'm using a DataView webpart and applying the XSLT to that. Tim, I have done quite a bit of XSLT before but I've never had a chance to actually learn it, so there's a lot of trial and error and probably bad habits involved! – Dave Apr 01 '15 at 12:25

1 Answers1

1

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.

Tomalak
  • 332,285
  • 67
  • 532
  • 628
  • Thanks - that works perfectly when I test it outside of SharePoint, but still gives the weird results when I try it in SharePoint. It seems like this is just down to a SharePoint bug rather than any XSLT issues and there's nothing I can do about it. For reference it seems that xsltlistviewwebpart deals with XSLT correctly, but dataformwebpart doesn't. Unfortunately, only the latter will let me combine 2 sets of data. Should I still accept this answer? I'm new to stackoverflow so don't know the etiquette! – Dave Apr 02 '15 at 09:38
  • I cannot imagine a Sharepoint bug being responsible. All components within Sharepoint use the .NET-integrated XSLT processor, which is mature enough to rule out any bugs. That's a very strong indication that it's connected to your source XML instead. It might help if you posted the *exact* source XML and the "weird" results. – Tomalak Apr 02 '15 at 09:58
  • I figured it out, but it still makes no sense, so I'd love to hear if anyone can explain this to me...To get the Muenchian working in SharePoint I had to change `[count(. | key('byLANGUAGE', @Language)[1]) = 1]` to `[count(. | key('byLANGUAGE', @Language)[2]) = 1]`, and the same for the Category one. It all works perfectly now. – Dave Apr 02 '15 at 10:56
  • That's unusual, please post the actual XML you use, no simplifications. – Tomalak Apr 02 '15 at 11:07