I have been working with XSLT lately, but now I got to a new problem trying to group a new xml file. I can't work out how to put a scope on the grouping. I want to group the <Detail> inside every <Info> node. Simple sample file:
<?xml version="1.0" encoding="utf-8"?>
<File>
<Info>
<Id>1111</Id>
<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>
</Info>
<Info>
<Id>2222</Id>
<Detail type="A" group="1" >
<Data>
<Nr>1</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>2</Nr>
</Data>
</Detail>
<Detail type="A" group="1">
<Data>
<Nr>3</Nr>
</Data>
</Detail>
</Info>
</File>
The output should be
<?xml version="1.0" encoding="utf-8"?>
<File>
<Info>
<Id>1111</Id>
<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>
</Info>
<Info>
<Id>2222</Id>
<Detail type="A" group="1" >
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>3</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>2</Nr>
</Data>
</Detail>
</Info>
</File>
In my try I don't know how to copy the values from the <Info> element (ID, it could be other elements too), I just write out <Info> element, and every <Detail> gets grouped in the first <Info> element, leaving the last <Info> element empty.
Here is my xslt so far
<?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/Info">
<Info>
<xsl:for-each select="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>
</Info>
</xsl:for-each>
</File>
</xsl:template>
</xsl:stylesheet>
and here is my result so far
<File>
<Info>
<Detail type="A" group="1">
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
<Data>
<Nr>6</Nr>
</Data>
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>3</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>3</Nr>
</Data>
<Data>
<Nr>4</Nr>
</Data>
<Data>
<Nr>2</Nr>
</Data>
</Detail>
<Detail type="B" group="2">
<Data>
<Nr>5</Nr>
</Data>
</Detail>
</Info>
<Info />
</File>
Thanks for any help :)
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:strip-space elements="*"/>
<xsl:key name="kDetailChildren" match="Detail"
use="concat(generate-id(..),'+',#type,'+',#group)"/>
<xsl:template match="node()|#*">
<xsl:copy>
<xsl:apply-templates select="node()|#*"/>
</xsl:copy>
</xsl:template>
<xsl:template match=
"Detail
[not(generate-id()
=
generate-id(key('kDetailChildren',
concat(generate-id(..),'+',#type,'+',#group)
)[1])
)]"/>
<xsl:template match="Detail">
<Detail>
<xsl:apply-templates select="#*"/>
<xsl:apply-templates select=
"key('kDetailChildren',
concat(generate-id(..),'+',#type,'+',#group)
)/node()"/>
</Detail>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document:
<File>
<Info>
<Id>1111</Id>
<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>
</Info>
<Info>
<Id>2222</Id>
<Detail type="A" group="1" >
<Data>
<Nr>1</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>2</Nr>
</Data>
</Detail>
<Detail type="A" group="1">
<Data>
<Nr>3</Nr>
</Data>
</Detail>
</Info>
</File>
produces the wanted, correct result:
<File>
<Info>
<Id>1111</Id>
<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>
</Info>
<Info>
<Id>2222</Id>
<Detail type="A" group="1">
<Data>
<Nr>1</Nr>
</Data>
<Data>
<Nr>3</Nr>
</Data>
</Detail>
<Detail type="B" group="1">
<Data>
<Nr>2</Nr>
</Data>
</Detail>
</Info>
</File>
Explanation:
Proper use of the identity rule and the Muenchian grouping method using composite keys.
Note how the parent's identity is included in the key.
Related
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.
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 am working in a project where I need to calculate the sum of hours after blank hours until the next blank hours and display them as in the output.
Here is the Input:
<Nodes>
<Node>
<EmpId>1<EmpId>
<InTime></InTime>
<Hours></Hours>
</Node>
<Node>
<EmpId>1<EmpId>
<InTime>10/12/2010</InTime>
<Hours>5</Hours>
</Node>
<Node>
<EmpId>1<EmpId>
<InTime>10/13/2010</InTime>
<Hours>5</Hours>
</Node>
<Node>
<EmpId>1<EmpId>
<InTime></InTime>
<Hours></Hours>
</Node>
<Node>
<EmpId>1</EmpId>
<InTime></InTime>
<Hours></Hours>
</Node>
<Node>
<EmpId>1</EmpId>
<InTime>10/14/2010</InTime>
<Hours>2</Hours>
</Node>
<Node>
<EmpId>1</EmpId>
<InTime>10/14/2010</InTime>
<Hours>3</Hours>
</Node>
</Nodes>
Output should be like:
<Nodes>
<Detail>
<EmpId>1</EmpId>
<InTime>10/12/2010</InTime>
<Hours>10</Hours>
</Detail>
<Detail>
<EmpId>1</EmpId>
<InTime>10/14/2010</InTime>
<Hours>5</Hours>
</Detail>
</Nodes>
Appreciate if any one could help me on this.
Your input XML is malformed (several <EmpId> tags where you should have </EmpId>), but once that's fixed, I believe this does what you describe:
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output method="xml" indent="yes"/>
<xsl:template match="/Nodes">
<Nodes>
<xsl:apply-templates select="Node[Hours != '' and not(normalize-space(preceding-sibling::Node[1]/Hours))]" />
</Nodes>
</xsl:template>
<xsl:template match="Node">
<Detail>
<xsl:copy-of select="EmpId | InTime"/>
<Hours>
<xsl:apply-templates select="." mode="SumHours" />
</Hours>
</Detail>
</xsl:template>
<xsl:template match="Node[normalize-space(following-sibling::Node[1]/Hours)]" mode="SumHours">
<xsl:param name="total" select="0" />
<xsl:apply-templates select="following-sibling::Node[1]" mode="SumHours">
<xsl:with-param name="total" select="$total + Hours" />
</xsl:apply-templates>
</xsl:template>
<xsl:template match="Node" mode="SumHours">
<xsl:param name="total" select="0" />
<xsl:value-of select="$total + Hours"/>
</xsl:template>
</xsl:stylesheet>
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>
<corpus>
<header id="1">
<file>
<info>
<title id="A" />
</info>
</file>
</header>
<TEI>
<header id="2">
<file>
<info>
<title id="B" />
</info>
</file>
</header>
<header id="3">
<file>
<info>
<record>
<title id="C" />
</record>
</info>
</file>
</header>
<header id="4">
<file>
</file>
</header>
</TEI>
</corpus>
$list is a set of <title> nodes.
The depth of <title> varies, but is always somewhere under a <header>. The depth of <header> varies, but its depth from root is always the same for all nodes in a given $list.
Given a $list, I need a for-each loop that loops through headers.
When the only node in $list is title A, I need to loop only through header 1.
When the nodes in $list are titles B and C, I need to loop through headers 2, 3 and 4.
Use:
$list/ancestor::header[1]/../header
XSLT - based verification:
<xsl:stylesheet version="1.0"
xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:variable name="vList1" select=
"/*/header[1]/*/*/title"/>
<xsl:variable name="vList2" select=
"/*/TEI//title"/>
<xsl:template match="/">
<xsl:copy-of select="$vList1"/>
====
<xsl:copy-of select="$vList2"/>
====
<xsl:copy-of select=
"$vList1/ancestor::header[1]/../header"/>
====
<xsl:copy-of select=
"$vList2/ancestor::header[1]/../header"/>
</xsl:template>
</xsl:stylesheet>
when this transformation is applied on the provided XML document:
<corpus>
<header id="1">
<file>
<info>
<title id="A" />
</info>
</file>
</header>
<TEI>
<header id="2">
<file>
<info>
<title id="B" />
</info>
</file>
</header>
<header id="3">
<file>
<info>
<record>
<title id="C" />
</record>
</info>
</file>
</header>
<header id="4">
<file>
</file>
</header>
</TEI>
</corpus>
the XPath expressions are evaluated and the wanted in each case header elements are selected and output:
<title id="A"/>
====
<title id="B"/>
<title id="C"/>
====
<header id="1">
<file>
<info>
<title id="A"/>
</info>
</file>
</header>
====
<header id="2">
<file>
<info>
<title id="B"/>
</info>
</file>
</header>
<header id="3">
<file>
<info>
<record>
<title id="C"/>
</record>
</info>
</file>
</header>
<header id="4">
<file>
</file>
</header>