walko1234
walko1234

Reputation: 151

XSLT 1.0 group and count nodes

I have XML with modules where are some parts inside....

<?xml version="1.0"?>
<root>
 <modul id="1">
    <part id="01" part_number="AAA"/>
    <part id="02" part_number="AAA"/>
    <part id="03" part_number="AAA"/>
    <part id="04" part_number="BBB"/>
 </modul>
 <modul id="2">
    <part id="05" part_number="AAA"/>
    <part id="06" part_number="AAA"/>
    <part id="07" part_number="CCC"/>
    <part id="08" part_number="BBB"/>
 </modul>
</root>

I want to group by part_number inside each modul and count nodes occurancies:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="keyPartNumber" match="part" use="@part_number"/>
<xsl:template match="/">
<xsl:for-each select="root/modul">
<modul name="{@id}">
<xsl:for-each select="part[generate-id() = generate-id(key('keyPartNumber',@part_number)[1])]">
  <xsl:sort select="@part_number" order="ascending" data-type="text"/>                        
  <node title="{@part_number} (quantity: {count(key('keyPartNumber',@part_number))})" />
  </xsl:for-each>
</modul>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>

But result what I get is not what I want:

<?xml version="1.0" encoding="UTF-8"?>
<modul name="1">
  <node title="AAA (quantity: 5)"/>
  <node title="BBB (quantity: 2)"/>
</modul>
<modul name="2">
  <node title="CCC (quantity: 1)"/>
</modul>

Excepted result will be:

<?xml version="1.0" encoding="UTF-8"?>
<modul name="1">
  <node title="AAA (quantity: 3)"/>
  <node title="BBB (quantity: 1)"/>
</modul>
<modul name="2">
  <node title="AAA (quantity: 2)"/>
  <node title="BBB (quantity: 1)"/>
  <node title="CCC (quantity: 1)"/>
</modul>

How can I realize this ? Is it possible to create key for each modul dynamically ?

Upvotes: 0

Views: 1552

Answers (2)

Linga Murthy C S
Linga Murthy C S

Reputation: 5432

Or, you can use a variable inside "modul" to store it's @id and use it to filter only those "part" inside the current "root/modul":

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:key name="keyPartNumber" match="part" use="@part_number"/>
<xsl:template match="/">
    <root>
        <xsl:for-each select="root/modul">
            <xsl:variable name="id" select="@id"/>
            <modul name="{@id}">
                <xsl:for-each select="part[generate-id() = generate-id(key('keyPartNumber',@part_number)[parent::*[@id = $id]][1])]">
                    <xsl:sort select="@part_number" order="ascending" data-type="text"/>                        
                    <node title="{@part_number} (quantity: {count(key('keyPartNumber',@part_number)[parent::*[@id = $id]])})" />
                </xsl:for-each>
            </modul>
        </xsl:for-each>
    </root>
</xsl:template>
</xsl:stylesheet>

Upvotes: 0

Tim C
Tim C

Reputation: 70648

This is because you need to include the modul id as part of the key, so that parts are distinct by module

<xsl:key name="keyPartNumber" match="part" use="concat(../@id, '|', @part_number)"/>

Note the | here can be anything, just as long as it doesn't occur in either the id or part_number.

You would the use the same concat statement whenever you access the key too.

Try this XSLT:

<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:key name="keyPartNumber" match="part" use="concat(../@id, '|', @part_number)"/>

    <xsl:template match="/">
        <xsl:for-each select="root/modul">
            <modul name="{@id}">
                <xsl:for-each select="part[generate-id() = generate-id(key('keyPartNumber',concat(../@id, '|', @part_number))[1])]">
                    <xsl:sort select="@part_number" order="ascending" data-type="text"/>                        
                    <node title="{@part_number} (quantity: {count(key('keyPartNumber',concat(../@id, '|', @part_number)))})" />
                </xsl:for-each>
            </modul>
        </xsl:for-each>
    </xsl:template>
</xsl:stylesheet>

Upvotes: 5

Related Questions