0

I need to merge the two complex elements nodes through xslt, based on the attribute value i.e for eg when id=1 its respective name value elements need to be merged. so on for id=2, 3 ..etc

<?xml version="1.0" encoding="UTF-8"?>
<xrefStore>
<xrefData>
    <entityData>
        <entry id="1">
            <keyValue name="A" value=" "/>
            <keyValue name="B" value=" "/>
            <keyValue name="C" value=" "/>
        </entry>
        <entry id="2">
            <keyValue name="A" value=" "/>
            <keyValue name="B" value=" "/>
            <keyValue name="c" value=" "/>
            <keyValue name="D" value=" "/>
            <keyValue name="E" value=" "/>
        </entry>
        <entry id="1">
            <keyValue name="D" value=" "/>
            <keyValue name="E" value=" "/>
        </entry>
    </xrefStore>
</xrefData>

The final output should be as below,

<?xml version="1.0"?>
<root>
<set id="1">
    <nameValuePair>
        <name>A</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>B</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>C</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>D</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>E</name>
        <value> </value>
    </nameValuePair>
</set>
<set id="2">
    <nameValuePair>
        <name>A</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>B</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>C</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>D</name>
        <value> </value>
    </nameValuePair>
    <nameValuePair>
        <name>E</name>
        <value> </value>
    </nameValuePair>
</set>
</root>

i have tried the below code, but it does not merge based on id.

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
    <xsl:template match="/">
    <root>
        <xsl:for-each select="xrefStore/xrefData/entityData/entity/entry">
            <set >
                <xsl:attribute name="id">
                    <xsl:value-of select="current()/@id" />
                </xsl:attribute>
                <xsl:for-each select="current()/keyValue">
                    <nameValuePair>
                        <name>
                            <xsl:value-of select="current()/@name" />
                        </name>
                        <value>
                            <xsl:value-of select="current()/@value" />
                        </value>
                    </nameValuePair>
                </xsl:for-each>
            </set>
        </xsl:for-each>
    </root>
    </xsl:template>
</xsl:stylesheet>

Any kind of help is appreciated.

i found similar answer in here, Merge XML nodes using XSLT

but i did not understand the concept of their implementation.

Shruthi Bhaskar
  • 1,212
  • 6
  • 20
  • 32
  • The example is confusing: where does the `1` in `1` come from? Anyway, this looks like a grouping problem, so it's essential to know if you can use XSLT 2.0. – michael.hor257k Jun 18 '17 at 06:44
  • Its a typo Micheal, there is no value 1.. it is just an empty value. This grouping can be done using version 2 xslt is it? – Shruthi Bhaskar Jun 18 '17 at 06:52
  • It can be done in both XSLT 1.0 and XSLT 2.0 - but the methods are different. -- Please correct your example and clarify what should happen when merging two nodes with different values. – michael.hor257k Jun 18 '17 at 07:15
  • I am not sure on how to edit the content to correct it, i tried. Where as for expected output, for each of entry i would need every keyValue grouped and obtain a unique entry with id's with merged keyValue. – Shruthi Bhaskar Jun 18 '17 at 09:20
  • That's not clear. Please provide an example. And state your processor's version. – michael.hor257k Jun 18 '17 at 10:45

1 Answers1

1

Though this question is couple of months old, thought of posting an answer so that it could be useful for others who may happen to land on this question.

XSLT 1.0 Solution

A key needs to be declared which will be used for grouping of the identical nodes. In the sample, the grouping needs to be done on id attribute of the <entry> element.

<xsl:key name="kId" match="entry" use="@id" />

Looping needs to be done for all the <entry> nodes whose unique identifier (returned by the generate-id() function) matches to the unique identifier of the key.

<xsl:for-each select="entry[generate-id() = generate-id(key('kId', @id)[1])]">

Lastly looping through all the <keyValue> nodes within the grouped keys to get the desired result.

<xsl:for-each select="key('kId',@id)/keyValue">

The complete XSL using version 1.0 is as below.

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

    <!-- declare key to group nodes -->
    <xsl:key name="kId" match="entry" use="@id" />

    <xsl:template match="entityData">
        <root>
            <!-- loop through the grouped keys -->
            <xsl:for-each select="entry[generate-id() = generate-id(key('kId', @id)[1])]">
                <set>
                    <xsl:attribute name="id">
                        <xsl:value-of select="@id" />
                    </xsl:attribute>
                    <!-- loop through the elements of the key -->
                    <xsl:for-each select="key('kId',@id)/keyValue">
                        <nameValuePair>
                            <name>
                                <xsl:value-of select="@name" />
                            </name>
                            <value>
                                <xsl:value-of select="@value" />
                            </value>
                        </nameValuePair>
                    </xsl:for-each>
                </set>
            </xsl:for-each>
        </root>     
    </xsl:template>
</xsl:stylesheet>

XSLT 2.0 Solution

Version 2.0 provides an easier approach for grouping using the <xsl:for-each-group> feature. Below is the XSL.

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

    <xsl:template match="entityData">
        <root>
            <!-- loop through 'entry' groups grouped by 'id' attribute -->
            <xsl:for-each-group select="entry" group-by="@id">
                <set>
                    <xsl:attribute name="id">
                        <xsl:value-of select="current-grouping-key()" />
                    </xsl:attribute>
                    <!-- loop through current group for all 'keyValue' elements -->
                    <xsl:for-each select="current-group()/keyValue">
                        <nameValuePair>
                            <name>
                                <xsl:value-of select="@name" />
                            </name>
                            <value>
                                <xsl:value-of select="@value" />
                            </value>
                        </nameValuePair>
                    </xsl:for-each>
                </set>
            </xsl:for-each-group>
        </root> 
    </xsl:template>
</xsl:stylesheet>

Both the solutions transform the input XML into the desired output

<root>
   <set id="1">
      <nameValuePair>
         <name>A</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>B</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>C</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>D</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>E</name>
         <value> </value>
      </nameValuePair>
   </set>
   <set id="2">
      <nameValuePair>
         <name>A</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>B</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>c</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>D</name>
         <value> </value>
      </nameValuePair>
      <nameValuePair>
         <name>E</name>
         <value> </value>
      </nameValuePair>
   </set>
</root>
Aniket V
  • 3,183
  • 2
  • 15
  • 27