ddelizia
ddelizia

Reputation: 1571

Joining data of the same type with XSLT

I would like to join all data of the same type with XSLT. I have the following XML:

<ZE1MARAM>
        <ZE1KONDM SEGMENT="1">
            <VKORG>NL01</VKORG>
            <KONDART>VKP0</KONDART>
            <BEGINDATUM>99991231</BEGINDATUM>
            <ENDDATUM>20120605</ENDDATUM>
            <KONDWERT>NL01</KONDWERT>
            <MENGE> 70.00</MENGE>
            <CURRENCY>EUR</CURRENCY>
        </ZE1KONDM>
        <ZE1KONDM SEGMENT="1">
            <VKORG>NLWS</VKORG>
            <KONDART>VKP0</KONDART>
            <BEGINDATUM>99991231</BEGINDATUM>
            <ENDDATUM>20120605</ENDDATUM>
            <KONDWERT>NLWS</KONDWERT>
            <MENGE> 70.00</MENGE>
            <CURRENCY>EUR</CURRENCY>
        </ZE1KONDM>
        <ZE1KONDM SEGMENT="1">
            <VKORG>NLWS</VKORG>
            <KONDART>VKA0</KONDART>
            <BEGINDATUM>99991231</BEGINDATUM>
            <ENDDATUM>20120605</ENDDATUM>
            <KONDWERT>NLWS</KONDWERT>
            <MENGE> 33.00</MENGE>
            <CURRENCY>EUR</CURRENCY>
        </ZE1KONDM>
    </ZE1MARAM>

so each ZE1KONDM with the same VKORG value in the result xml has to be appended to the same element. So the result would be something like that:

<result>
<prices value="NL01">
    <price type="VKP0">
        70.00
    </price>
</prices>
<prices value="NLWS">
    <price type="VKP0">
        70.00
    </price>
    <price type="VKA0">
        55.00
    </price>
</prices>

I tried to work with keys, and do something like that:

                    <xsl:key name="myKey" match="ZE1KONDM" use="normalize-space(VKORG)" />

                <xsl:for-each select="ZE1KONDM">

                    <xsl:choose>

                        <xsl:when test="KONDART='VKP0'">
                            <xsl:element name="prices">

                                <xsl:element name="price">
                                    <xsl:value-of select="key('myKey', normalize-space(VKORG))/MENGE"/>
                                </xsl:element>

                            </xsl:element>
                        </xsl:when>

                    </xsl:choose>

                </xsl:for-each>

but it does not work because it takes just one key..

There is some way to solve this problem with xslt?

Upvotes: 1

Views: 66

Answers (2)

Dimitre Novatchev
Dimitre Novatchev

Reputation: 243579

I. XSLT 1.0 Solution:

Here is a classical application of the Muenchian grouping method:

<xsl:stylesheet version="1.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:key name="kZByM" match="ZE1KONDM" use="VKORG"/>

 <xsl:template match="/*">
     <result>
       <xsl:apply-templates select=
       "*[generate-id() = generate-id(key('kZByM', VKORG)[1])]"/>
     </result>
 </xsl:template>

 <xsl:template match="ZE1KONDM">
  <prices value="{VKORG}">
   <xsl:apply-templates select="key('kZByM', VKORG)" mode="inGroup"/>
  </prices>
 </xsl:template>

 <xsl:template match="ZE1KONDM" mode="inGroup">
   <price type="{KONDART}">
     <xsl:value-of select="MENGE"/>
   </price>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the provided XML document:

<ZE1MARAM>
    <ZE1KONDM SEGMENT="1">
        <VKORG>NL01</VKORG>
        <KONDART>VKP0</KONDART>
        <BEGINDATUM>99991231</BEGINDATUM>
        <ENDDATUM>20120605</ENDDATUM>
        <KONDWERT>NL01</KONDWERT>
        <MENGE>70.00</MENGE>
        <CURRENCY>EUR</CURRENCY>
    </ZE1KONDM>
    <ZE1KONDM SEGMENT="1">
        <VKORG>NLWS</VKORG>
        <KONDART>VKP0</KONDART>
        <BEGINDATUM>99991231</BEGINDATUM>
        <ENDDATUM>20120605</ENDDATUM>
        <KONDWERT>NLWS</KONDWERT>
        <MENGE>70.00</MENGE>
        <CURRENCY>EUR</CURRENCY>
    </ZE1KONDM>
    <ZE1KONDM SEGMENT="1">
        <VKORG>NLWS</VKORG>
        <KONDART>VKA0</KONDART>
        <BEGINDATUM>99991231</BEGINDATUM>
        <ENDDATUM>20120605</ENDDATUM>
        <KONDWERT>NLWS</KONDWERT>
        <MENGE>33.00</MENGE>
        <CURRENCY>EUR</CURRENCY>
    </ZE1KONDM>
</ZE1MARAM>

the wanted, correct result is produced:

<result>
   <prices value="NL01">
      <price type="VKP0">70.00</price>
   </prices>
   <prices value="NLWS">
      <price type="VKP0">70.00</price>
      <price type="VKA0">33.00</price>
   </prices>
</result>

Do note: The Muenchian grouping method is probably the fastest XSLT 1.0 grouping method, because it uses keys. Other methods (such as comparing siblings values) are way too slower (O(N^2)) which is prohibitive fro using them on large data sizes.


II. XSLT 2.0 solution:

<xsl:stylesheet version="2.0"
 xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 <xsl:output omit-xml-declaration="yes" indent="yes"/>
 <xsl:strip-space elements="*"/>

 <xsl:template match="/*">
     <result>
       <xsl:for-each-group select="*" group-by="VKORG">
          <prices value="{VKORG}">
           <xsl:apply-templates select="current-group()"/>
          </prices>
       </xsl:for-each-group>
     </result>
 </xsl:template>

 <xsl:template match="ZE1KONDM">
   <price type="{KONDART}">
     <xsl:value-of select="MENGE"/>
   </price>
 </xsl:template>
</xsl:stylesheet>

When this transformation is applied on the same XML document (above), the same correct result is produced:

<result>
   <prices value="NL01">
      <price type="VKP0">70.00</price>
   </prices>
   <prices value="NLWS">
      <price type="VKP0">70.00</price>
      <price type="VKA0">33.00</price>
   </prices>
</result>

Explanation:

Proper use of xsl:for-each-group with the group-by attribute, and the current-group() function.

Upvotes: 1

Mitya
Mitya

Reputation: 34596

There's probably a better way, but try this:

http://www.xmlplayground.com/2A3C7H

(see output source)

Upvotes: 1

Related Questions