Search code examples
xsltxslt-grouping

Grouping elements and deleting duplicate nodes - XSLT 1.0


I have looked at Muenchian Grouping - group within a node, not within the entire document but it is not quite working for me. The Muenchian method alone does not do it either for me.

I have also looked at XSLT 1.0: grouping and removing duplicate but cannot follow it completely.

I have the following XML:

<?xml version="1.0" encoding="UTF-8"?>
<MT_MATERIALDATA>
<items item="475053">
    <Recordset>
        <CodeBusinessUnit>99</CodeBusinessUnit>
        <PriceValue>250</PriceValue>
    </Recordset>
    <Recordset>
        <CodeBusinessUnit>1</CodeBusinessUnit>
        <PriceValue>250</PriceValue>
    </Recordset>
</items>
<items item="475054">
    <Recordset>
        <CodeBusinessUnit>1</CodeBusinessUnit>
        <PriceValue>255.34</PriceValue>
    </Recordset>
    <Recordset>
        <CodeBusinessUnit>10</CodeBusinessUnit>
        <PriceValue>299</PriceValue>
    </Recordset>
</items>
</MT_MATERIALDATA>

The outcome should look like this:

<?xml version="1.0" encoding="UTF-8"?>
<MT_MATERIALDATA>
<Mi item="475053">
    <PriceList>
        <Prices>
            <Price Value="250"/>
            <PriceConfig>
                <Stores>99,1</Stores>
            </PriceConfig>
        </Prices>
    </PriceList>
</Mi>
<Mi item="475054">
    <PriceList>
        <Prices>
            <Price Value="255.34"/>
            <PriceConfig>
                <Stores>1</Stores>
            </PriceConfig>
        </Prices>
        <Prices>
            <Price Value="299"/>
            <PriceConfig>
                <Stores>10</Stores>
            </PriceConfig>
        </Prices>
    </PriceList>
</Mi>
</MT_MATERIALDATA>

So for matching <PriceValue> elements in <Recordset>, all respective <CodeBusinessUnits> need to be listed in <Stores>. If not, an extra <Prices> node needs to be created.

I have been trying for hours but either the Store-numbers are always duplicate or they are not aggregated even if the PriceValue is the same.


Solution

  • This transformation:

    <xsl:stylesheet version="1.0"
     xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
     <xsl:output omit-xml-declaration="yes" indent="yes"/>
    
     <xsl:key name="kPriceByValAndItem" match="PriceValue"
      use="concat(../../@item, '|', .)"/>
    
     <xsl:template match="/*">
      <MT_MATERIALDATA>
       <xsl:apply-templates/>
      </MT_MATERIALDATA>
     </xsl:template>
    
     <xsl:template match="items">
      <MI item="{@item}">
       <PriceList>
         <xsl:for-each select=
          "*/PriceValue
              [generate-id()
              =
               generate-id(key('kPriceByValAndItem',
                               concat(../../@item, '|', .)
                               )[1]
                           )
               ]
          ">
           <Prices>
            <Price Value="{.}"/>
            <PriceConfig>
              <Stores>
                <xsl:for-each select=
                "key('kPriceByValAndItem',
                               concat(../../@item, '|', .)
                               )">
                 <xsl:value-of select="../CodeBusinessUnit"/>
                 <xsl:if test="not(position()=last())">,</xsl:if>
                </xsl:for-each>
              </Stores>
            </PriceConfig>
           </Prices>
         </xsl:for-each>
       </PriceList>
      </MI>
     </xsl:template>
    </xsl:stylesheet>
    

    when applied on the provided XML document:

    <MT_MATERIALDATA>
        <items item="475053">
            <Recordset>
                <CodeBusinessUnit>99</CodeBusinessUnit>
                <PriceValue>250</PriceValue>
            </Recordset>
            <Recordset>
                <CodeBusinessUnit>1</CodeBusinessUnit>
                <PriceValue>250</PriceValue>
            </Recordset>
        </items>
        <items item="475054">
            <Recordset>
                <CodeBusinessUnit>1</CodeBusinessUnit>
                <PriceValue>255.34</PriceValue>
            </Recordset>
            <Recordset>
                <CodeBusinessUnit>10</CodeBusinessUnit>
                <PriceValue>299</PriceValue>
            </Recordset>
        </items>
    </MT_MATERIALDATA>
    

    produces the wanted, correct result:

    <MT_MATERIALDATA>
        <MI item="475053">
            <PriceList>
                <Prices>
                    <Price Value="250"/>
                    <PriceConfig>
                        <Stores>99,1</Stores>
                    </PriceConfig>
                </Prices>
            </PriceList>
        </MI>
        <MI item="475054">
            <PriceList>
                <Prices>
                    <Price Value="255.34"/>
                    <PriceConfig>
                        <Stores>1</Stores>
                    </PriceConfig>
                </Prices>
                <Prices>
                    <Price Value="299"/>
                    <PriceConfig>
                        <Stores>10</Stores>
                    </PriceConfig>
                </Prices>
            </PriceList>
        </MI>
    </MT_MATERIALDATA>