Search code examples
xsltxpathxslt-grouping

Grouping by an attribute in xslt


Hi i am a little stuck with this problem my xml is as

<album>
     <title>album name</title>    
     <tracks>
           <track tracksequence="1" disksequence="1">track 1 - disc 1</track>
           <track tracksequence="2" disksequence="1">track 2 - disc 1</track>
           <track tracksequence="3" disksequence="1">track 3 - disc 1</track>
           <track tracksequence="1" disksequence="2">track 1 - disc 2</track>
           <track tracksequence="2" disksequence="2">track 2 - disc 2</track>
           <track tracksequence="3" disksequence="2">track 3 - disc 2</track>
           <track tracksequence="4" disksequence="2">track 4 - disc 2</track>
           <track tracksequence="1" disksequence="3">track 1 - disc 3</track>
           <track tracksequence="2" disksequence="3">track 2 - disc 3</track>
     </tracks>
</album>

i would like to be able to output as

<div>
<span>album name disc 1</span>
track 1 disc 1
track 2 disc 1
track 3 disc 1
</div>

<div>
<span>album name disc 2</span>
track 1 disc 2
track 2 disc 2
track 3 disc 2
track 4 disc 2 
</div>

ect...

im sure that i need to use some sort of Muenchian grouping but im having difficulty getting my head around it for my particular usage.


Solution

  • <?xml version="1.0" encoding="UTF-8"?>
    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="html" indent="yes"/>
    
    <xsl:key name="sortByDisk" match="track" use="@disksequence"/>
    
    <xsl:template match="/*">
        <body>
            <xsl:apply-templates select="tracks/track[
                count(. | key('sortByDisk', @disksequence)[1]) = 1
            ]"/>
        </body>
    </xsl:template>
    
    <xsl:template match="track">
        <div>
            <span>
                <xsl:value-of select="../../title"/>
                <xsl:text> disc</xsl:text>
                <xsl:value-of select="@disksequence"/>
            </span>
            <xsl:apply-templates select="key('sortByDisk', @disksequence)"
                mode="inner"/>
        </div>
    </xsl:template>
    
    <xsl:template match="track" mode="inner">
        <p><xsl:value-of select="."/></p>
    </xsl:template>
    
    </xsl:stylesheet>
    

    Output (using your sample):

    <body>
        <div>
            <span>album name disc1</span>
            <p>track 1 - disc 1</p>
            <p>track 2 - disc 1</p>
            <p>track 3 - disc 1</p>
        </div>
        <div>
            <span>album name disc2</span>
            <p>track 1 - disc 2</p>
            <p>track 2 - disc 2</p>
            <p>track 3 - disc 2</p>
            <p>track 4 - disc 2</p>
        </div>
        <div>
            <span>album name disc3</span>
            <p>track 1 - disc 3</p>
            <p>track 2 - disc 3</p>
        </div>
    </body>