Search code examples
xmlxsltxslt-1.0xslt-2.0

Grouping inside for each loop : xsl 1.0


I have this XML grouped by country. With sequence preserved, the node with a @country value is the parent and the children are the elements with blank country under it.

<catalog> 
<cd> 
    <country>USA</country> 
    <artist>Bob Dylan</artist> 
</cd> 
<cd> 
    <country></country> 
    <artist>Percy Sledge</artist> 
</cd> 
<cd> 
    <country></country> 
    <artist>Joe Cocker</artist> 
</cd> 
<cd> 
    <country>UK</country> 
    <artist>Kenny Rogers</artist> 
</cd> 
<cd> 
    <country></country> 
    <artist>Bonnie Tyler</artist> 
</cd> 
</catalog>

using xsl 1.0. what I want to do is to group them so that the node with blank country will be grouped under the node with country above it, something like below.

Country Artist
USA Bob Dylan, Percy Sledge, Joe Cocker
UK Kenny Rogers, Bonnie Tyler

This is what I had in mind. The first foreach loop filters all the node with country, and the 2nd for each loop is to find all the node with the same country and all the blank country under that node. I'm having issues with the 2nd for each loop. couldn't figure out how to do the loop.

I appreciate all the help. thank you.

<table> 
<tr > <th>country</th> <th>Artist</th> </tr> 
<xsl:for-each select="catalog/cd[not(country='')]"> 
    <xsl:variable name="cntry" select="country"/> 
    <tr> 
        <td><xsl:value-of select="$cntry" /></td> 
        <xsl:for-each select="catalog/cd[country='$cntry' or catalog/cd[country='']]"> 
            <td><xsl:value-of select="artist" /></td> 
        </xsl:for-each> 
    </tr> 
</xsl:for-each> 
</table>

Solution

  • Adapting the answer (for XSLT 1.0) from https://stackoverflow.com/a/74612904/3016153:

    <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output method="xml" indent="yes"/>
    
    <xsl:key name="k1" match="cd[not(country/text())]" use="preceding-sibling::cd[country/text()][1]/country" />
    
    <xsl:template match="/catalog">
        <table border="1">
            <tr>
                <th>Country</th>
                <th>Artist</th> 
            </tr>
            <xsl:for-each select="cd[country/text()]">
                <tr> 
                    <td>
                        <xsl:value-of select="country"/>
                    </td> 
                    <td>
                        <xsl:for-each select=". | key('k1', country)">
                            <xsl:value-of select="artist"/>
                            <xsl:if test="position() != last()">, </xsl:if>
                        </xsl:for-each> 
                    </td>
                </tr>
            </xsl:for-each>
        </table>
    </xsl:template>
    
    </xsl:stylesheet>