The Input has two xml in it , The InputMessagePart_0 have Multiple Location id and InputMessagePart_1 have Multiple ItemMaster then i need to create an Output where for every location id i need to have the Item Master.
I have written the Xslt where its not looping on the InputMessagePart_0(Record) level and its taking only the first Location id ,
XSlT1.0 :
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 s2 s1" version="1.0" xmlns:s0="http://Test.ItemMaster" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s1="http://Test.Lookup" xmlns:ns0="http://Test.Out">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s2:Root" />
</xsl:template>
<xsl:template match="/s2:Root">
<ns0:Root>
<xsl:for-each select="InputMessagePart_1/s0:Root/ItemMaster">
<xsl:variable name="var:v1" select="../../../InputMessagePart_0/s1:Root/Record/LocationId" />
<xsl:variable name="var:v2" select="ItemId" />
<xsl:variable name="var:v3" select="ItemName" />
<xsl:variable name="var:v4" select="Quantity" />
<Detail>
<LocationId>
<xsl:value-of select="$var:v1" />
</LocationId>
<ItemId>
<xsl:value-of select="$var:v2" />
</ItemId>
<ItemName>
<xsl:value-of select="$var:v3" />
</ItemName>
<Qty>
<xsl:value-of select="$var:v4" />
</Qty>
</Detail>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
Input XML :
<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
<InputMessagePart_0>
<ns0:Root xmlns:ns0="http://Test.Lookup">
<Record>
<LocationId>12</LocationId>
</Record>
<Record>
<LocationId>13</LocationId>
</Record>
<Record>
<LocationId>14</LocationId>
</Record>
</ns0:Root>
</InputMessagePart_0>
<InputMessagePart_1>
<ns0:Root xmlns:ns0="http://Test.ItemMaster">
<ItemMaster>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
<ItemMaster>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
<ItemMaster>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Quantity>1</Quantity>
</ItemMaster>
</ns0:Root>
</InputMessagePart_1>
</ns0:Root>
Current OutPut:
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://Test.Out">
<Detail>
<LocationId>12</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>12</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>13</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>123</ItemId>
<ItemName>Knife</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1234</ItemId>
<ItemName>Knife1</ItemName>
<Qty>1</Qty>
</Detail>
<Detail>
<LocationId>14</LocationId>
<ItemId>1235</ItemId>
<ItemName>Knife3</ItemName>
<Qty>1</Qty>
</Detail>
</ns0:Root>
You need to have an xsl:for-each (or xsl:apply-templates) to get the Record elements with the locations. (And this xsl:for-each would contain the current xsl:for-each on the ItemMaster)
Try this XSLT
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s0 s2 s1" version="1.0" xmlns:s0="http://Test.ItemMaster" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s1="http://Test.Lookup" xmlns:ns0="http://Test.Out">
<xsl:output omit-xml-declaration="yes" method="xml" indent="yes" version="1.0" />
<xsl:template match="/s2:Root">
<ns0:Root>
<xsl:for-each select="InputMessagePart_0/s1:Root/Record">
<xsl:variable name="var:v1" select="LocationId" />
<xsl:for-each select="../../../InputMessagePart_1/s0:Root/ItemMaster">
<Detail>
<LocationId>
<xsl:value-of select="$var:v1" />
</LocationId>
<ItemId>
<xsl:value-of select="ItemId" />
</ItemId>
<ItemName>
<xsl:value-of select="ItemName" />
</ItemName>
<Qty>
<xsl:value-of select="Quantity" />
</Qty>
</Detail>
</xsl:for-each>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
Note, the template matching / wasn't really necessary here, as XSLT's built-in template does the same thing.
Related
I have requirement two merge two xml files. Based on the key field in the files I want to merge entire content of the xml 1 to xml 2.
Could you please help me to achieve this scenario.
I have tried this xslt but I am getting below error.
"a sequence of more than one item is not allowed as the first argument of fn:parse-xml()"
XSLT code.
<xsl:stylesheet version="3.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes" />
<xsl:mode on-no-match="shallow-copy" />
<xsl:param name="XML1" />
<xsl:variable name="details-doc" select="parse-xml($XML1)" />
<xsl:template match="Key">
<xsl:copy-of select="." />
<xsl:variable name="Key" select="string(.)" />
<xsl:copy-of select="$details-doc/Record/Detail[Key = $Key]" />
</xsl:template>
</xsl:stylesheet>
XML1.
<Record>
<Detail>
<Key>1</Key>
<Place>Ocean</Place>
<City>Urban</City>
</Detail>
<Detail>
<Key>2</Key>
<Place>Road</Place>
<City>Rural</City>
</Detail>
<Detail>
<Key>3</Key>
<Place>Plane</Place>
<City>Semiurban</City>
</Detail>
</Record>
XML2
<Record>
<Contact>
<Key>1</Key>
<Name>Jack</Name>
</Contact>
<Contact>
<Key>2</Key>
<Name>Ethan</Name>
</Contact>
<Contact>
<Key>3</Key>
<Name>Ron</Name>
</Contact>
</Record>
And the expected output.
<Record>
<Contact>
<Key>1</Key>
<Name>Jack</Name>
<Place>Ocean</Place>
<City>Urban</City>
</Contact>
<Contact>
<Key>2</Key>
<Name>Ethan</Name>
<Place>Road</Place>
<City>Rural</City>
</Contact>
<Contact>
<Key>3</Key>
<Name>Ron</Name>
<Place>Plane</Place>
<City>Semiurban</City>
</Contact>
</Record>
Having a requirement where i have aggregated message and populate and loop for each message in output , below is what i have but need help where to start with.
Input Message :
<ns0:Root xmlns:ns0="http://schemas.microsoft.com/BizTalk/2003/aggschema">
<InputMessagePart_0>
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Inpu1">
<Header>
<SeqNo>1</SeqNo>
<FileName>Test</FileName>
</Header>
<Detail>
<ItemName>Item1</ItemName>
<Quantity>1</Quantity>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Quantity>2</Quantity>
</Detail>
</ns0:Root>
</InputMessagePart_0>
<InputMessagePart_1>
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Input2">
<FileName>Test</FileName>
<Header>
<DestinationLocation>Miami</DestinationLocation>
<DestinationName>State</DestinationName>
<Detail>
<ItemName>Item1</ItemName>
<Rate>100</Rate>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Rate>200</Rate>
</Detail>
</Header>
</ns0:Root>
</InputMessagePart_1>
</ns0:Root>
Desired OutPut :
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Output">
<SeqNo>1</SeqNo>
<FileName>Test</FileName>
<DestinationLocation>Miami</DestinationLocation>
<DestinationName>State</DestinationName>
<Detail>
<ItemName>Item1</ItemName>
<Quantity>1</Quantity>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Quantity>2</Quantity>
</Detail>
<Detail>
<ItemName>Item1</ItemName>
<Rate>100</Rate>
</Detail>
<Detail>
<ItemName>Item2</ItemName>
<Rate>200</Rate>
</Detail>
</ns0:Root>
XSLT I Have started :
<?xml version="1.0" encoding="UTF-16"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:msxsl="urn:schemas-microsoft-com:xslt" xmlns:var="http://schemas.microsoft.com/BizTalk/2003/var" exclude-result-prefixes="msxsl var s2 s0 s1" version="1.0" xmlns:ns0="http://TestXSLT1._0.Output" xmlns:s2="http://schemas.microsoft.com/BizTalk/2003/aggschema" xmlns:s0="http://TestXSLT1._0.Input2" xmlns:s1="http://TestXSLT1._0.Inpu1">
<xsl:output omit-xml-declaration="yes" method="xml" version="1.0" />
<xsl:template match="/">
<xsl:apply-templates select="/s2:Root" />
</xsl:template>
<xsl:template match="/s2:Root">
<ns0:Root>
<SeqNo>
<xsl:value-of select="InputMessagePart_0/s1:Root/Header/SeqNo/text()" />
</SeqNo>
<FileName>
<xsl:value-of select="InputMessagePart_0/s1:Root/Header/FileName/text()" />
</FileName>
<DestinationLocation>
<xsl:value-of select="InputMessagePart_1/s0:Root/Header/DestinationLocation/text()" />
</DestinationLocation>
<DestinationName>
<xsl:value-of select="InputMessagePart_1/s0:Root/Header/DestinationName/text()" />
</DestinationName>
<xsl:for-each select="InputMessagePart_0/s1:Root/Detail">
<Detail>
<ItemName>
<xsl:value-of select="ItemName/text()" />
</ItemName>
<Quantity>
<xsl:value-of select="Quantity/text()" />
</Quantity>
<Rate>
<xsl:value-of select="../../../InputMessagePart_1/s0:Root/Header/Detail/Rate/text()" />
</Rate>
</Detail>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
The XSLT I have made doesn't give me the desired output.
This should be done in XSLT 1.0
Appreciate for the help ~
The output you show can be produced using:
XSLT 1.0
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes" omit-xml-declaration="yes"/>
<xsl:template match="/*">
<ns0:Root xmlns:ns0="http://TestXSLT1._0.Output">
<!-- header -->
<SeqNo>
<xsl:value-of select="InputMessagePart_0/*/Header/SeqNo" />
</SeqNo>
<FileName>
<xsl:value-of select="InputMessagePart_0/*/Header/FileName" />
</FileName>
<DestinationLocation>
<xsl:value-of select="InputMessagePart_1/*/Header/DestinationLocation" />
</DestinationLocation>
<DestinationName>
<xsl:value-of select="InputMessagePart_1/*/Header/DestinationName" />
</DestinationName>
<!-- details part 0 -->
<xsl:for-each select="InputMessagePart_0/*/Detail">
<Detail>
<ItemName>
<xsl:value-of select="ItemName" />
</ItemName>
<Quantity>
<xsl:value-of select="Quantity" />
</Quantity>
</Detail>
</xsl:for-each>
<!-- details part 1 -->
<xsl:for-each select="InputMessagePart_1/*/Header/Detail">
<Detail>
<ItemName>
<xsl:value-of select="ItemName" />
</ItemName>
<Rate>
<xsl:value-of select="Rate" />
</Rate>
</Detail>
</xsl:for-each>
</ns0:Root>
</xsl:template>
</xsl:stylesheet>
I'm new to xslt. I'm trying to group by 2 different elements. First by Worker, then by Pay code. Please see that the amounts sum because of the group by paycode. Below is a before sample xsl, then a sample after xsl that I would like as output.
Before:
<?xml version='1.0' encoding='utf-8'?>
<File xmlns:is="java:com.workday.esb.intsys.xpath.ParsedIntegrationSystemFunctions"
xmlns:tv="java:com.workday.esb.intsys.TypedValue">
<Worker>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>5572.800000</Amount>
</Detail>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>FICA</PayCode>
<Amount>40.000000</Amount>
</Detail>
</Worker>
<Worker>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>13545.000000</Amount>
</Detail>
</Worker>
<Worker>
<Detail>
<EmployeeID>00012345</EmployeeID>
<FirstName>RUSSELL</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>84811.050000</Amount>
</Detail>
</Worker>
</File>
What I would like as output, grouping first by Worker, then group by Pay Code. Amounts sum because of grouping:
<?xml version='1.0' encoding='utf-8'?>
<File xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:tv="java:com.workday.esb.intsys.TypedValue">
<Worker>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>19117.800000</Amount>
</Detail>
<Detail>
<EmployeeID>0008765</EmployeeID>
<FirstName>ROBERT</FirstName>
<PayCode>FICA</PayCode>
<Amount>40.000000</Amount>
</Detail>
</Worker>
<Worker>
<Detail>
<EmployeeID>00012345</EmployeeID>
<FirstName>RUSSELL</FirstName>
<PayCode>RSVEST</PayCode>
<Amount>84811.050000</Amount>
</Detail>
</Worker>
</File>
Below is my XSL that doesn't work:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="2.0" exclude-result-prefixes="xsl wd xsd this env"
xmlns:wd="urn:com.workday/bsvc"
xmlns:xsd="http://www.w3.org/2001/XMLSchema"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:env="http://schemas.xmlsoap.org/soap/envelope/"
xmlns:this="urn:this-stylesheet">
<xsl:output indent="yes" method="xml"/>
<xsl:template match="/">
<File>
<Worker>
<xsl:for-each-group select="File" group-by="Worker">
<Detail>
<EmployeeID><xsl:value-of select="Worker/current-group()/EmployeeID"></xsl:value-of></EmployeeID>
<FirstName><xsl:value-of select="//current-group()//FirstName"></xsl:value-of></FirstName>
<PayCode><xsl:value-of select="PayCode"></xsl:value-of></PayCode>
<Amount><xsl:value-of select="format-number(sum(current-group()/number(translate(Amount,',',''))),'######.00')"></xsl:value-of></Amount>
</Detail>
</xsl:for-each-group>
</Worker>
</File>
</xsl:template>
</xsl:stylesheet>
Can someone please put an SL that will transform above to the desired output?
I've been working on this for hours and waving the white flag lol.
Thank you!
If you want to have a group for each EmployeeID, and within each such group a subgroup for each PayCode, then obviously you need to nest two xsl:for-each-group instructions. And the nodes you need to be grouping are the Detail elements, not the root File element of which there only will be one.
XSLT 2.0
<xsl:stylesheet version="2.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="/File">
<xsl:copy>
<xsl:for-each-group select="Worker/Detail" group-by="EmployeeID">
<Worker>
<xsl:for-each-group select="current-group()" group-by="PayCode">
<Detail>
<xsl:copy-of select="EmployeeID | FirstName | PayCode"/>
<Amount>
<xsl:value-of select="format-number(sum(current-group()/Amount),'#.000000')"/>
</Amount>
</Detail>
</xsl:for-each-group>
</Worker>
</xsl:for-each-group>
</xsl:copy>
</xsl:template>
</xsl:stylesheet>
Below is my Input xml and I want to use xslt 1.0 to transform into below specified xml.
Element ref needs to be used as key to merge and create the array in the output
Input xml :
<Input>
<row>
<Name>ABC</Name>
<Ref>12345</Ref>
<Status>O</Status>
<Currency>USD</Currency>
<Date>2016-05-16</Date>
</row>
<row>
<Name>ABC</Name>
<Ref>12345</Ref>
<Status>O</Status>
<Currency>AUD</Currency>
<Date>2016-05-01</Date>
</row>
<row>
<Name>XYZ</Name>
<Ref>54321</Ref>
<Status>O</Status>
<Currency>AUD</Currency>
<Date>2016-03-01</Date>
</row>
<row>
<Name>XYZ</Name>
<Ref>54321</Ref>
<Status>O</Status>
<Currency>USD</Currency>
<Date>2016-05-01</Date>
</row>
</Input>
Output Xml: using the ref element in the request the there will be only two row elements in the output xml, but the values that are different are clubbed in a another sub node.
<Output>
<row>
<Name>ABC</Name>
<Ref>12345</Ref>
<Status>O</Status>
<Details>
<Detail>
<Currency>USD</Currency>
<Date>2016-05-16</Date>
</Detail>
<Detail>
<Currency>AUD</Currency>
<Date>2016-05-01</Date>
</Detail>
</Details>
</row>
<row>
<Name>XYZ</Name>
<Ref>54321</Ref>
<Status>O</Status>
<Details>
<Detail>
<Currency>AUD</Currency>
<Date>2016-03-01</Date>
</Detail>
<Detail>
<Currency>USD</Currency>
<Date>2016-05-01</Date>
</Detail>
</Details>
</row>
</Output>
Any help ?
Tried using the below xslt , but the if condition isn't working
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:soapenv="http://schemas.xmlsoap.org/soap/envelope/">
<xsl:output method="xml" version="1.0"/>
<xsl:template match="/">
<xsl:element name="Output">
<xsl:apply-templates select="Input"/>
</xsl:element>
</xsl:template>
<xsl:template match="Input">
<xsl:for-each select="row">
<xsl:if test="(following-sibling::Ref = /Ref)">
<xsl:element name="row">
<xsl:element name="Name">
<xsl:value-of select="Name" />
</xsl:element>
<xsl:element name="Ref">
<xsl:value-of select="Ref" />
</xsl:element>
<xsl:element name="Status">
<xsl:value-of select="Status" />
</xsl:element>
<xsl:element name="Details">
<xsl:element name="Detail">
<xsl:element name="Currency">
<xsl:value-of select="Currency" />
</xsl:element>
<xsl:element name="Date">
<xsl:value-of select="Date" />
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:element>
</xsl:if>
</xsl:for-each>
</xsl:template>
</xsl:stylesheet>
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="kRowByRefNameStat" match="row" use="concat(Name, '|', Ref, '|', Status)"/>
<xsl:template match="/*">
<Output>
<xsl:apply-templates select=
"row[generate-id()
=
generate-id(key('kRowByRefNameStat',
concat(Name, '|', Ref, '|', Status))[1])]"/>
</Output>
</xsl:template>
<xsl:template match="row">
<row>
<xsl:copy-of select="Name | Ref | Status"/>
<Details>
<xsl:apply-templates mode="inGroup" select=
"key('kRowByRefNameStat', concat(Name, '|', Ref, '|', Status))"/>
</Details>
</row>
</xsl:template>
<xsl:template match="row" mode="inGroup">
<Detail><xsl:copy-of select="Currency | Date"/></Detail>
</xsl:template>
</xsl:stylesheet>
when applied on the provided XML document:
<Input>
<row>
<Name>ABC</Name>
<Ref>12345</Ref>
<Status>O</Status>
<Currency>USD</Currency>
<Date>2016-05-16</Date>
</row>
<row>
<Name>ABC</Name>
<Ref>12345</Ref>
<Status>O</Status>
<Currency>AUD</Currency>
<Date>2016-05-01</Date>
</row>
<row>
<Name>XYZ</Name>
<Ref>54321</Ref>
<Status>O</Status>
<Currency>AUD</Currency>
<Date>2016-03-01</Date>
</row>
<row>
<Name>XYZ</Name>
<Ref>54321</Ref>
<Status>O</Status>
<Currency>USD</Currency>
<Date>2016-05-01</Date>
</row>
</Input>
produces the wanted, correct result:
<Output>
<row>
<Name>ABC</Name>
<Ref>12345</Ref>
<Status>O</Status>
<Details>
<Detail>
<Currency>USD</Currency>
<Date>2016-05-16</Date>
</Detail>
<Detail>
<Currency>AUD</Currency>
<Date>2016-05-01</Date>
</Detail>
</Details>
</row>
<row>
<Name>XYZ</Name>
<Ref>54321</Ref>
<Status>O</Status>
<Details>
<Detail>
<Currency>AUD</Currency>
<Date>2016-03-01</Date>
</Detail>
<Detail>
<Currency>USD</Currency>
<Date>2016-05-01</Date>
</Detail>
</Details>
</row>
</Output>
Explanation:
Muenchian grouping using composite-keys
Modes
I have tried some of the examples here, but I still can't get the correct output. I haven't worked much with XSLT before. I want to group on the "Detail" element and get all the "Data" elements matching that group as children to the "Detail" element.
Example:
input
<?xml version="1.0" encoding="utf-8"?>
<File>
<Detail type="A" group="1" >
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
<Detail type="A" group="1">
<Data>
<Nr>6</Nr>
</Data>
</Detail>
</File>
Wanted output ("Detail type="A" group="1"> elements grouped together and all the elements matching that group as children)
<?xml version="1.0" encoding="utf-8"?>
<File>
<Detail type="A" group="1" >
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
<Data>
<Nr>6</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
</File>
Thanks for help :)
I. XSLT 1.0
Here's a solution that makes use of push-oriented templates, rather than <xsl:for-each> (which is normally a more reusable approach).
When this XSLT:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="1.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:key name="kDetailAttr" match="Detail" use="concat(#type, '+', #group)" />
<xsl:template match="/*">
<File>
<xsl:apply-templates select="Detail[generate-id() = generate-id(key('kDetailAttr', concat(#type, '+', #group))[1])]" />
</File>
</xsl:template>
<xsl:template match="Detail">
<Detail type="{#type}" group="{#group}">
<xsl:copy-of select="key('kDetailAttr', concat(#type, '+', #group))/Data" />
</Detail>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML:
<?xml version="1.0" encoding="UTF-8"?>
<File>
<Detail type="A" group="1">
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
<Detail type="A" group="1">
<Data>
<Nr>6</Nr>
</Data>
</Detail>
</File>
...the wanted result is produced:
<?xml version="1.0" encoding="utf-8"?>
<File>
<Detail type="A" group="1">
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
<Data>
<Nr>6</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
</File>
Explanation:
By properly applying the Muenchian Grouping Method (which is necessary in XSLT 1.0, given that it does not have any "inline" grouping mechanism), we can find unique nodes and group their descendants.
II. XSLT 2.0
When this XSLT:
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="2.0">
<xsl:output omit-xml-declaration="no" indent="yes" />
<xsl:strip-space elements="*" />
<xsl:template match="/*">
<File>
<xsl:for-each-group select="Detail"
group-by="concat(#type, '+', #group)">
<Detail type="{#type}" group="{#group}">
<xsl:copy-of select="current-group()/Data" />
</Detail>
</xsl:for-each-group>
</File>
</xsl:template>
</xsl:stylesheet>
...is applied to the original XML, the same correct result is produced.
Explanation:
By properly applying XSLT 2.0's for-each-group element, we can arrive at the same result.
NOTE: This question languished in the bin for 6 hours before I took it up. My answer languished in the bin for two hours before someone else disguised some non-essential comments as an answer.
Study up on Muenchian grouping. It's handy for these grouping problems.
The heavy lifters are <xsl:key>, which creates a key based on a concat of #type and #group, and this line here, <xsl:for-each select="File/Detail[count(. | key('details', concat(#type,'_',#group))[1]) = 1]">, which isolates the first occurrence of the Detail node having a particular key and by extension a particular pairing of #group and #type.
<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
<xsl:key name="details" match="Detail"
use="concat(#type,'_',#group)"/>
<xsl:template match='/'>
<File>
<xsl:for-each select="File/Detail[count(. | key('details', concat(#type,'_',#group))[1]) = 1]">
<xsl:sort select="concat(#type,'_',#group)" />
<Detail type="{#type}" group="{#group}">
<xsl:for-each select="key('details', concat(#type,'_',#group))">
<xsl:copy-of select="Data"/>
</xsl:for-each>
</Detail>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>