Warm tip: This article is reproduced from stackoverflow.com, please click
xml xslt xslt-1.0 xslt-grouping

XSLT beginner: Group XML based on unique element value XSLT 1.0

发布于 2020-04-11 22:24:42

I am a beginner and I am trying to group an XML input based on similar category, using XSLT 1.0 Here is the input xml which contains category and location. The out put must group all elements with the same category and list unique locations:

<?xml version="1.0" ?>
<Data>
    <Row>
       <id>123</id>
       <location>/example/games/data.php</location>
       <category>gamedata</category>
    </Row>
    <Row>
        <id>456</id>
       <location>/example/games/data.php</location>
       <category>gamedata</category>
    </Row>
<Row>
        <id>789</id>
       <location>/example/games/score.php</location>
       <category>gamedata</category>
    </Row>
<Row>
       <id>888</id>
       <location>/example/games/title.php</location>
       <category>gametitle</category>
    </Row>
<Row>
        <id>777</id>
       <location>/example/games/title.php</location>
       <category>gametitle</category>
    </Row>
<Row>
        <id>999</id>
       <location>/example/score/title.php</location>
       <category>gametitle</category>
    </Row>
</Data>

Looking for output as(list only unique location grouped by category):

<project>
     <item>
        <data>
<category>gamedata</category>
           <id>456</id>
            <id>789</id>
             <id>123</id>
       <location>/example/games/data.php</location>   
       <location>/example/games/score.php</location>
       </data>
  <data> <category>gametitle</category>
       <id>888</id>
       <id>777</id>
        <id>999</id>
       <location>/example/games/title.php</location>
       <location>/example/score/title.php</location> 
    </data>
</item></project>

What I have tried so far:

 <?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="keyCategory" match="Row" use="category"/>
    <xsl:template match="/">
        <project xmlns="xyz.com">
            <item >
                <name lang="en">Example</name>
                <xsl:for-each select="//Row[generate-id(.) = generate-id(key('keyCategory', category)[1])]">

                    <xsl:for-each select="key('keyCategory', category)">
                          <data>
                            <category><xsl:value-of select="category"/></category>
                            <id><xsl:value-of select="id"/></id>
                             <location><xsl:value-of select="location"/></location></data>
                    </xsl:for-each>
             </xsl:for-each>
</item>
</project>

What I am actually getting:

<project>
     <item>
        <data>
<category>gamedata</category>
           <id>456</id>
       <location>/example/games/data.php</location>

         </data>
         <data>
<category>gamedata</category>
            <id>789</id>
       <location>/example/games/score.php</location>

         </data>
        <data>
<category>gamedata</category>
            <id>789</id>
       <location>/example/games/score.php</location>

         </data>
        <data>
<category>gamedata</category>
       <id>123</id>
       <location>/example/games/data.php</location>

       </data>
  <data>
<category>gametitle</category>
       <id>888</id>
       <location>/example/games/title.php</location>

    </data>
   <data>
<category>gametitle</category>
        <id>777</id>
       <location>/example/games/title.php</location>

    </data>
   <data>
<category>gametitle</category>
        <id>999</id>
       <location>/example/score/title.php</location>

    </data>
</item></project>
Questioner
Aben
Viewed
36
Martin Honnen 2020-02-02 20:16

For your nested grouping problem I think you want to use a second key:

<xsl:stylesheet version="1.0"  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:output indent="yes"/>

    <xsl:key name="keyCategory" match="Row" use="category"/>

    <xsl:key name="location" match="Row" use="concat(category, '|', location)"/>

    <xsl:template match="/">
        <project>
            <item>
                <name lang="en">Example</name>
                <xsl:for-each select="//Row[generate-id(.) = generate-id(key('keyCategory', category)[1])]">
                    <data>
                        <xsl:copy-of
                          select="category"/>
                        <xsl:copy-of select="key('keyCategory', category)/id"/>
                        <xsl:copy-of 
                          select="key('keyCategory', category)[generate-id() = generate-id(key('location', concat(category, '|', location))[1])]/location"/>
                    </data>
                </xsl:for-each>
            </item>
        </project>
    </xsl:template>
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/94Acsm4/0

This only shows the grouping and ignores that your output uses a different namespace, to create the selected elements in the new namespace I would transform them:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
  xmlns="http://example.com/">
    <xsl:output indent="yes"/>

    <xsl:key name="keyCategory" match="Row" use="category"/>

    <xsl:key name="location" match="Row" use="concat(category, '|', location)"/>

    <xsl:template match="/">
        <project>
            <item>
                <name lang="en">Example</name>
                <xsl:for-each select="//Row[generate-id(.) = generate-id(key('keyCategory', category)[1])]">
                    <data>
                        <xsl:apply-templates
                          select="category"/>
                        <xsl:apply-templates select="key('keyCategory', category)/id"/>
                        <xsl:apply-templates 
                          select="key('keyCategory', category)[generate-id() = generate-id(key('location', concat(category, '|', location))[1])]/location"/>
                    </data>
                </xsl:for-each>
            </item>
        </project>
    </xsl:template>

    <xsl:template match="*">
        <xsl:element name="{local-name()}">
            <xsl:apply-templates/>
        </xsl:element>
    </xsl:template>
</xsl:stylesheet>

https://xsltfiddle.liberty-development.net/94Acsm4/1