Grouping XML Transformation Elements Using XSLT

I'm struggling with the concept of grouping (across multiple keys) XML based table with hierarchy with XSLT

The grouping is based on the first four elements, but the grouping must break if another element exists between the set.

XML source:

<RECORDS> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F1>A1</F1> </RECORD> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F1>A2</F1> </RECORD> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <E5>195</E5> <F1>A3</F1> </RECORD> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F1>A4</F1> </RECORD> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F1>A5</F1> </RECORD> <RECORD> <E1>DONALD</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F1>A6</F1> </RECORD> <RECORD> <E1>DONALD</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F1>A7</F1> </RECORD> </RECORDS> 

XML output

  <RECORDS> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F> <F1>A1</F1> <F1>A2</F1> </F> </RECORD> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <E5>195</E5> <F> <F1>A3</F1> <F1>A4</F1> </F> </RECORD> <RECORD> <E1>MICKEY</E1> <!--Must break and not merge in first group --> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F> <F1>A5</F1> </F> </RECORD> <RECORD> <E1>DONALD</E1> <E2>TEST</E2> <E4>14</E4> <E5>196</E5> <F> <F1>A6</F1> <F1>A7</F1> </F> </RECORD> </RECORDS> 

Here is the XSL I'm still with ...

 <?xml version="1.0"?> <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> <xsl:output method="xml" indent="yes"/> <xsl:key name="grouped" match="RECORD" use="concat(E1, '+', E2, '+', E4 , '+', E5 )"/> <xsl:template match="node()|@*"> <xsl:copy> <xsl:apply-templates select="node()|@*"/> </xsl:copy> </xsl:template> <xsl:template match="/*"> <RECORDS> <xsl:apply-templates select= "RECORD[generate-id() = generate-id(key('grouped', concat(E1, '+', E2, '+', E4 , '+', E5 ) ) [1] ) ] "/> </RECORDS> </xsl:template> <xsl:template match="RECORD"> <RECORD> <E1><xsl:value-of select="E1"/></E1> <E2><xsl:value-of select="E2"/></E2> <E4><xsl:value-of select="E4"/></E4> <F> <xsl:for select="F1"> <F1><xsl:value-of select="F1"/></F1> </xsl:for> </F> </RECORD> </xsl:template> </xsl:stylesheet> 

The problem is that I cannot create an internal binding for each f1. Also I should get 4 sets of RECORDS, not 3 that I get with this.

 <RECORDS> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <F></F> </RECORD> <RECORD> <E1>MICKEY</E1> <E2>TEST</E2> <E4>14</E4> <F></F> </RECORD> <RECORD> <E1>DONALD</E1> <E2>TEST</E2> <E4>14</E4> <F></F> </RECORD> </RECORDS> 
+5
source share
2 answers

Here is the solution using the keys . In short (28% fewer lines of code and does not require horizontal scrolling). More reliable (see the end of this answer for details)

This is more general because it will work even when there are other elements between the elements that we want to group that need to be ignored (that is, where preceding-sibling::*[1] may be the element we want exclude from grouping - in the current problem - not the RECORD element):

 <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="kStartGroup" match="/*/*" use= "generate-id(preceding-sibling::* [not(concat(E1, '|', E2, '|', E4, '|', E5) = concat(current()/E1, '|', current()/E2, '|', current()/E4, '|', current()/E5) ) ][1])"/> <xsl:template match="*[not(concat(E1, '|', E2, '|', E4, '|', E5) = concat(preceding-sibling::*[1]/E1, '|', preceding-sibling::*[1]/E2, '|', preceding-sibling::*[1]/E4, '|', preceding-sibling::*[1]/E5) )]"> <xsl:copy> <xsl:copy-of select="E1 | E2 | E4 | E5"/> <F><xsl:copy-of select= "key('kStartGroup', generate-id(preceding-sibling::*[1]))/F1"/></F> </xsl:copy> </xsl:template> <xsl:template match="/*"><xsl:copy><xsl:apply-templates/></xsl:copy></xsl:template> <xsl:template match="text()"/> </xsl:stylesheet> 

Reliability / Scalability

Since this transformation does not contain recursion (nested calls <xsl:apply-templates ), it is reliable and scalable when applied to large XML files.

On the other hand, when re-converting in a sufficiently large XML document, in another answer โ€œrecursion guysโ€ , a crash occurs due to . In my case, this failure was observed with the original XML document of about 13,000 (13 thousand lines) - this may vary depending on the available RAM, XSLT processor, etc.

The current conversion is successfully performed even on extremely large XML documents, for example, with 1,200,000 (one million and 200 thousand lines).

+4
source

Apparently you want to make XSLT 2.0 equivalent to group-adjacent XSLT 2.0. This can be achieved using a method known as "sister recursion":

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"/> <xsl:template match="/RECORDS"> <xsl:copy> <!-- start the first group --> <xsl:apply-templates select="RECORD[1]"/> </xsl:copy> </xsl:template> <xsl:template match="RECORD"> <xsl:variable name="key" select="concat(E1, '+', E2, '+', E4 , '+', E5)" /> <xsl:copy> <xsl:copy-of select="E1 | E2 | E4 | E5"/> <F> <xsl:copy-of select="F1"/> <!-- immediate sibling in the same group --> <xsl:apply-templates select="following-sibling::RECORD[1][concat(E1, '+', E2, '+', E4 , '+', E5) = $key]" mode="collect"/> </F> </xsl:copy> <!-- start the next group --> <xsl:apply-templates select="following-sibling::RECORD[not(concat(E1, '+', E2, '+', E4 , '+', E5)=$key)][1]"/> </xsl:template> <xsl:template match="RECORD" mode="collect"> <xsl:variable name="key" select="concat(E1, '+', E2, '+', E4 , '+', E5)" /> <xsl:copy-of select="F1"/> <!-- immediate sibling in the same group --> <xsl:apply-templates select="following-sibling::RECORD[1][concat(E1, '+', E2, '+', E4 , '+', E5) = $key]" mode="collect" /> </xsl:template> </xsl:stylesheet> 
+3
source

All Articles