Search code examples
xsltxslt-groupingmuenchian-grouping

Sorting fails when using XSLT 1.0 Muenchian Grouping for creating HTML output


I've got the following XML:

<?xml version="1.0" encoding="utf-8" standalone="no"?>
<?xml-stylesheet type="text/xsl" href="Test.xslt"?>
<test-results>
    <test-case name="TestCase1" description="Descriptiontext">
        <categories>
            <category name="Dimension linked to measure group" />
        </categories>
    </test-case>
    <test-case name="TestCase2" description="DescriptionText">
        <categories>
            <category name="Dimension linked to measure group" />
        </categories>
    </test-case>
    <test-case name="TestCase3" description="DescriptionText">
        <categories>
            <category name="Default parameters" />
        </categories>
    </test-case>
    <test-case name="TestCase4" description="DescriptionText">
        <categories>
            <category name="Default parameters" />
        </categories>
    </test-case>
    <test-case name="TestCase5" description="DescriptionText">
        <categories>
            <category name="Referential Integrity" />
        </categories>
        <reason>
            <message><![CDATA[Not testable, yet (v1.6.1)]]></message>
        </reason>
    </test-case>
    <test-case name="TestCase6" description="DescriptionText">
        <categories>
            <category name="Referential Integrity" />
        </categories>
        <reason>
            <message><![CDATA[Not testable, yet (v1.6.1)]]></message>
        </reason>
    </test-case>
</test-results>

With the following XSLT I try to use Muenchian grouping to order by category name (ascending) and within each category by test-case name (ascending).

<?xml version="1.0"?>
<xsl:stylesheet version="1.0"
    xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
    xmlns="http://www.w3.org/1999/xhtml">
    <xsl:key name="cases-by-category" match="categories" use="category/@name" />
    <xsl:template match="test-case">
        <xsl:for-each select="categories[count(. | key('cases-by-category', category/@name)[1]) = 1]">
            <xsl:sort select="category/@name" />
            <xsl:value-of select="category/@name" /><br/>
            <xsl:for-each select="key('cases-by-category', category/@name)">
                <xsl:sort select="//test-case/@name" />
                <xsl:value-of select="//test-case/@name"/><br/>
            </xsl:for-each>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

However, what I get is this:

Dimension linked to measure group
TestCase1
TestCase1
Default parameters
TestCase1
TestCase1
Referential Integrity
TestCase1
TestCase1

The number of test cases for each category is correct, but the sorting doesn't get applied and the first test-case name is always used. How can I fix this?


Solution

  • Given <xsl:key name="cases-by-category" match="categories" use="category/@name" /> the expression key('cases-by-category', category/@name) gives you a node-set of categories elements, if you want to sort them by the parent then I think you want to use <xsl:sort select="../@name" />.

    I also think having

    <xsl:template match="test-case">
        <xsl:for-each select="categories[count(. | key('cases-by-category', category/@name)[1]) = 1]">
    

    looks odd as you would process the categories of every matched test-case element, it seems more likely you want

    <xsl:template match="test-results">
        <xsl:for-each select="test-case/categories[count(. | key('cases-by-category', category/@name)[1]) = 1]">
    

    instead.

    Here is a complete sample:

    <?xml version="1.0"?>
    <xsl:stylesheet version="1.0"
        xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
        xmlns="http://www.w3.org/1999/xhtml">
    
        <xsl:output indent="yes"/>
    
        <xsl:key name="cases-by-category" match="categories" use="category/@name" />
    
        <xsl:template match="/">
          <html>
            <body>
              <xsl:apply-templates/>
            </body>
          </html>
        </xsl:template>
    
        <xsl:template match="test-results">
          <xsl:for-each select="test-case/categories[count(. | key('cases-by-category', category/@name)[1]) = 1]">
                <xsl:sort select="category/@name" />
                <xsl:value-of select="category/@name" /><br/>
                <xsl:for-each select="key('cases-by-category', category/@name)">
                    <xsl:sort select="../@name" />
                    <xsl:value-of select="../@name"/><br/>
                </xsl:for-each>
            </xsl:for-each>
        </xsl:template>
    
    </xsl:stylesheet>
    

    When I run that with Saxon 6.5 against your input I get the following result:

    <html xmlns="http://www.w3.org/1999/xhtml">
       <body>Default parameters<br/>TestCase3<br/>TestCase4<br/>Dimension linked to measure group<br/>TestCase1<br/>TestCase2<br/>Referential Integrity<br/>TestCase5<br/>TestCase6<br/>
       </body>
    </html>