This transformation:
<xsl:stylesheet version="2.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:xs="http://www.w3.org/2001/XMLSchema" xmlns:my="my:my">
<xsl:output omit-xml-declaration="yes" indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:template match="node()|@*">
<xsl:copy>
<xsl:apply-templates select="node()|@*"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*" priority="3">
<xsl:sequence select="my:grouping(., *)"/>
</xsl:template>
<xsl:function name="my:grouping" as="element()*">
<xsl:param name="pElem" as="element()"/>
<xsl:param name="pChildren" as="element()*"/>
<xsl:element name="{name($pElem)}" namespace="{namespace-uri($pElem)}">
<xsl:apply-templates select="$pElem/@*"/>
<xsl:for-each-group select="$pChildren" group-by="my:signature(.)">
<xsl:copy>
<xsl:apply-templates select="@*|node()[not(self::*)]"/>
<xsl:apply-templates select=
"my:grouping(., current-group()/*)/*"/>
</xsl:copy>
</xsl:for-each-group>
</xsl:element>
</xsl:function>
<xsl:function name="my:signature" as="xs:string">
<xsl:param name="pElem" as="element()"/>
<xsl:variable name="vAttibs" as="xs:string*">
<xsl:perform-sort select="$pElem/@*">
<xsl:sort select="name()"/>
</xsl:perform-sort>
</xsl:variable>
<xsl:sequence select=
"string-join((name($pElem)
,for $at in $vAttibs
return concat($at, '+', $pElem/@*[name()=$at])
)
,'|')"/>
</xsl:function>
</xsl:stylesheet>
when applied on the provided XML document:
<Xml>
<House houseId="3" address="123 Main">
<Dog dogId="13" name="Rover">
<Flea fleaId="17" name="Chester" />
</Dog>
<Dog dogId="13" name="Rover">
<Flea fleaId="23" name="Poindexter" />
</Dog>
</House>
<House houseId="3" address="123 Main">
<Human humanId="9" name="Mr. Johnson">
<Child childId="11" name="Susie" />
</Human>
<Human humanId="9" name="Mr. Johnson">
<Child childId="31" name="Sandy" />
</Human>
</House>
<House houseId="5" address="987 Wall">
<Dog dogId="13" name="Rover">
<Flea fleaId="17" name="Chester" />
</Dog>
<Dog dogId="13" name="Rover">
<Flea fleaId="19" name="Wilhelm" />
</Dog>
</House>
</Xml>
produces the wanted, correct result:
<Xml>
<House houseId="3" address="123 Main">
<Dog dogId="13" name="Rover">
<Flea fleaId="17" name="Chester"/>
<Flea fleaId="23" name="Poindexter"/>
</Dog>
<Human humanId="9" name="Mr. Johnson">
<Child childId="11" name="Susie"/>
<Child childId="31" name="Sandy"/>
</Human>
</House>
<House houseId="5" address="987 Wall">
<Dog dogId="13" name="Rover">
<Flea fleaId="17" name="Chester"/>
<Flea fleaId="19" name="Wilhelm"/>
</Dog>
</House>
</Xml>
and with this extended XML document (text nodes added):
<Xml>
<House houseId="3" address="123 Main">
<Dog dogId="13" name="Rover">
Dog named Rover
<Flea fleaId="17" name="Chester">Regular dog flee</Flea>
</Dog>
<Dog dogId="13" name="Rover">
<Flea fleaId="23" name="Poindexter">Flea named Poindexter</Flea>
</Dog>
</House>
<House houseId="3" address="123 Main">
<Human humanId="9" name="Mr. Johnson">
<Child childId="11" name="Susie">Susan Johnson</Child>
</Human>
<Human humanId="9" name="Mr. Johnson">
<Child childId="31" name="Sandy">Sandy Johnson</Child>
</Human>
</House>
<House houseId="5" address="987 Wall">
<Dog dogId="13" name="Rover">
<Flea fleaId="17" name="Chester" />
</Dog>
<Dog dogId="13" name="Rover">
<Flea fleaId="19" name="Wilhelm" />
</Dog>
</House>
</Xml>
again the correct result is produced:
<Xml>
<House houseId="3" address="123 Main">
<Dog dogId="13" name="Rover">
Dog named Rover
<Flea fleaId="17" name="Chester">Regular dog flee</Flea>
<Flea fleaId="23" name="Poindexter">Flea named Poindexter</Flea>
</Dog>
<Human humanId="9" name="Mr. Johnson">
<Child childId="11" name="Susie">Susan Johnson</Child>
<Child childId="31" name="Sandy">Sandy Johnson</Child>
</Human>
</House>
<House houseId="5" address="987 Wall">
<Dog dogId="13" name="Rover">
<Flea fleaId="17" name="Chester"/>
<Flea fleaId="19" name="Wilhelm"/>
</Dog>
</House>
</Xml>
Explanation:
We use two functions: my:signature()
and my:grouping()
:
my:signature()
creates a signature for each element -- this is the pipe-separated string of the element name and all attrName+value pairs, sorted by attrName.my:grouping()
usesmy:signature()
to do correct grouping. It has a second argument, containing the elements to be grouped.