Title: XSLT 1.0 conditional merge after grouping
Tags: xml;xslt;merge;muenchian-grouping;apply-templates
Question: My input xml looks as shown below:
<items&gt;
<item&gt;
<geoDetails&gt;
<street&gt;xxx</street&gt;
<city&gt;yyy</city&gt;
<state&gt;zzz</state&gt;
</geoDetails&gt;
<otherDetails&gt;
<desc&gt;abcd111</desc&gt;
<comments&gt;good item</comments&gt;
</otherDetails&gt;
<key&gt;
<name&gt;item1</name&gt;
<id&gt;123</id&gt;
<color&gt;red</color&gt;
</key&gt;
<misc&gt;
<available&gt;false</available&gt;
<details&gt;geo</details&gt;
</misc&gt;
</item&gt;
<item&gt;
<otherDetails&gt;
<desc&gt;efgh222</desc&gt;
<comments&gt;good item</comments&gt;
</otherDetails&gt;
<key&gt;
<name&gt;item2</name&gt;
<id&gt;123</id&gt;
<color&gt;red</color&gt;
</key&gt;
<misc&gt;
<available&gt;false</available&gt;
<details&gt;other</details&gt;
</misc&gt;
</item&gt;
<item&gt;
<geoDetails&gt;
<street&gt;ppp</street&gt;
<city&gt;qqq</city&gt;
<state&gt;rrr</state&gt;
</geoDetails&gt;
<otherDetails&gt;
<desc&gt;ijkl333</desc&gt;
<comments&gt;best item</comments&gt;
</otherDetails&gt;
<key&gt;
<name&gt;item3</name&gt;
<id&gt;456</id&gt;
<color&gt;blue</color&gt;
</key&gt;
<misc&gt;
<available&gt;false</available&gt;
<details&gt;other</details&gt;
</misc&gt;
</item&gt;
</items&gt;
The key for grouping 'item' nodes is concat(/items/item/key/id,/items/item/key/color)
For each identified group, merge should happen with the logic as given below:
a. Extract 'geoDetails' from the 'item' where misc/details is 'geo'.
b. Extract 'otherDetails' from the 'item' where misc/details is 'other'. Only one misc/details is present in each 'item' with the value of either 'geo' or 'other'.
c. Extract the 'key' element from the first 'item' in a given group.
d. The 'misc' element should contain 'available' element as it is (this is always 'false' and hence populating this once is enough) and the 'details' element should have value based on the element 'geoDetails' or 'otherDetails' populated through the logic as given in steps a &amp; b above. There should be 2 'details' elements if both the 'geoDetails' and 'otherDetails' are populated.
e. Any other single 'item' element that is which is not part of a group should be pushed to output as it is.
And the expected output based on above logic is as shown below:
<items&gt;
<item&gt;
<geoDetails&gt;
<street&gt;xxx</street&gt;
<city&gt;yyy</city&gt;
<state&gt;zzz</state&gt;
</geoDetails&gt;
<otherDetails&gt;
<desc&gt;efgh222</desc&gt;
<comments&gt;good item</comments&gt;
</otherDetails&gt;
<key&gt;
<name&gt;item1</name&gt;
<id&gt;123</id&gt;
<color&gt;red</color&gt;
</key&gt;
<misc&gt;
<available&gt;false</available&gt;
<details&gt;geo</details&gt;
<details&gt;other</details&gt;
</misc&gt;
</item&gt;
<item&gt;
<geoDetails&gt;
<street&gt;ppp</street&gt;
<city&gt;qqq</city&gt;
<state&gt;rrr</state&gt;
</geoDetails&gt;
<otherDetails&gt;
<desc&gt;ijkl333</desc&gt;
<comments&gt;best item</comments&gt;
</otherDetails&gt;
<key&gt;
<name&gt;item3</name&gt;
<id&gt;456</id&gt;
<color&gt;blue</color&gt;
</key&gt;
<misc&gt;
<available&gt;false</available&gt;
<details&gt;other</details&gt;
</misc&gt;
</item&gt;
I tried the transformation based on Muenchian grouping using xsl:key and apply-templates. I was able to group the 'item' elements but couldn't proceed further on how to merge these grouped elements based on the above set of conditions.
XSLT Transformation:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:exsl="http://exslt.org/common" version="1.0" exclude-result-prefixes="exsl"&gt;
<xsl:key match="item" name="itemKey" use="concat(key/id,key/color)" /&gt;
<xsl:template match="/"&gt;
<xsl:variable name="output"&gt;
<xsl:apply-templates /&gt;
</xsl:variable&gt;
<xsl:copy-of select="exsl:node-set($output)/*" /&gt;
</xsl:template&gt;
<!-- default template --&gt;
<xsl:template match="node( ) | @*"&gt;
<xsl:copy&gt;
<xsl:apply-templates select="@*" /&gt;
<xsl:apply-templates /&gt;
</xsl:copy&gt;
</xsl:template&gt;
<xsl:template match="item[generate-id(.)=generate-id(key('itemKey',concat(key/id,key/color))[1])]"&gt;
<xsl:copy&gt;
<xsl:apply-templates select="@* | key('itemKey',concat(key/id,key/color))/node()" /&gt;
</xsl:copy&gt;
</xsl:template&gt;
<xsl:template match="item" /&gt;
I've gone through several related questions on Stackoverflow but couldn't adapt the merging process to my current scenario. Any help is greatly appreciated.
Comment: **1.** The merge conditions should be applied only in case of grouped elements. In case of a stand alone element that is 'item' tag whose key doesn't match with any other 'item' tags, just output the element as it is. This is the condition as explained in point-e.

Comment: **2.** point-d is for populating the 'misc' element. The 'available' element is always 'false', hence it can be hardcoded and for 'details' element, if 'geoDetails' was populated from point-a, then make 'details' value to 'geo', if 'otherDetails' was populated from point-b, then make 'details' value to 'other', if both 'geoDetails' and 'otherDetails' were populated then there should be 2 'details' tags one with 'geo' and other with 'other'.

Comment: **1.** Your output does not match you stated requirements. You say: '*a. Extract 'geoDetails' from the 'item' where misc/details is 'geo'.*" but your second item contains geoDetails from an item where misc/details is 'other'. -- **2.** I could not understand points d and e.

Here is the accepted answer: AFAICT, this satisfies your conditions (as far as I was able to understand them):
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"&gt;
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/&gt;
<xsl:strip-space elements="*"/&gt;
<xsl:key name="itemKey" match="item" use="concat(key/id, key/color)" /&gt;
<xsl:template match="/items"&gt;
<xsl:copy&gt;
<xsl:apply-templates select="item[generate-id()=generate-id(key('itemKey', concat(key/id, key/color))[1])]"/&gt;
</xsl:copy&gt;
</xsl:template&gt;
<xsl:template match="item"&gt;
<xsl:variable name="curr-group" select="key('itemKey', concat(key/id, key/color))" /&gt;
<xsl:choose&gt;
<!-- e. Any other single 'item' element that is which is not part of a group should be pushed to output as it is. --&gt;
<xsl:when test="count($curr-group)=1"&gt;
<xsl:copy-of select="."/&gt;
</xsl:when&gt;
<xsl:otherwise&gt;
<xsl:copy&gt;
<!-- a. Extract 'geoDetails' from the 'item' where misc/details is 'geo'. --&gt;
<xsl:copy-of select="$curr-group[misc/details='geo']/geoDetails"/&gt;
<!-- b. Extract 'otherDetails' from the 'item' where misc/details is 'other'. --&gt;
<xsl:copy-of select="$curr-group[misc/details='other']/otherDetails"/&gt;
<!-- c. Extract the 'key' element from the first 'item' in a given group. --&gt;
<xsl:copy-of select="key"/&gt;
<!-- d. ??? --&gt;
<misc&gt;
<available&gt;false</available&gt;
<xsl:copy-of select="$curr-group/misc/details"/&gt;
</misc&gt;
</xsl:copy&gt;
</xsl:otherwise&gt;
</xsl:choose&gt;
</xsl:template&gt;
</xsl:stylesheet&gt;
Comment for this answer: that's amazing..! It gives me the expected output precisely. A clarification here: The line of code **** retrieves the misc/details from both the 'item' elements of the $curr-group..? My understanding was that xsl-copy-of or xsl:value-of for that matter would retrieve the values from first node even though the same xpath is repeated in subsequent nodes. Can you please throw some light here..?
Comment for this answer: Sure, I'll accept the answer. As we are in the context here, I just tried by writing **
** and executed against my input xml which returned only one details tag like **geo** but when I tried with **

** it returned all the 3 details elements like **geootherother**. Why would this happen?
Comment for this answer: Your understanding is correct with regard to `xsl:value-of` (in XSLT 1.0), not  `xsl-copy-of`.


{'source': 'StackExchangeDataset', 'subset': 'stackoverflow.com', 'metadata': {'AnswerCount': 1, 'CommentCount': 3, 'ContentLicense': 'CC BY-SA 3.0', 'CreationDate': '2015:00:11/27/15', 'FavoriteCount': None, 'PostTypeId': 1, 'Tags': '<xml><xslt><merge><muenchian-grouping><apply-templates>', 'Title': 'XSLT 1.0 conditional merge after grouping', 'ViewCount': 339, 'id': 33952586, 'user_AboutMe': '', 'user_AccountId': 4721897.0, 'user_DisplayName': 'Lokesh', 'user_Reputation': 109.0, 'user_Views': 35.0, 'user_id': 3819537.0}}