Reputation: 13
I came through various answers regarding this topic but I couldn't find a solution so far.
I have input XML with the structure like this:
<RootNode>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
<ITEMELEMENT1>ITEM11</ITEMELEMENT1>
<ITEMELEMENT2>ITEM21</ITEMELEMENT2>
<ITEMELEMENT3>ITEM31</ITEMELEMENT3>
<ITEMELEMENT4>ITEM41</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
<ITEMELEMENT1>ITEM21</ITEMELEMENT1>
<ITEMELEMENT2>ITEM22</ITEMELEMENT2>
<ITEMELEMENT3>ITEM23</ITEMELEMENT3>
<ITEMELEMENT4>ITEM24</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABD</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD21</HEADELEMENT1>
<HEADELEMENT2>HEAD22</HEADELEMENT2>
<HEADELEMENT3>HEAD23</HEADELEMENT3>
<HEADELEMENT4>HEAD24</HEADELEMENT4>
<ITEMELEMENT1>ITEM31</ITEMELEMENT1>
<ITEMELEMENT2>ITEM32</ITEMELEMENT2>
<ITEMELEMENT3>ITEM33</ITEMELEMENT3>
<ITEMELEMENT4>ITEM34</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>002</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD31</HEADELEMENT1>
<HEADELEMENT2>HEAD32</HEADELEMENT2>
<HEADELEMENT3>HEAD33</HEADELEMENT3>
<HEADELEMENT4>HEAD34</HEADELEMENT4>
<ITEMELEMENT1>ITEM41</ITEMELEMENT1>
<ITEMELEMENT2>ITEM42</ITEMELEMENT2>
<ITEMELEMENT3>ITEM43</ITEMELEMENT3>
<ITEMELEMENT4>ITEM44</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
<ITEMELEMENT1>ITEM51</ITEMELEMENT1>
<ITEMELEMENT2>ITEM52</ITEMELEMENT2>
<ITEMELEMENT3>ITEM53</ITEMELEMENT3>
<ITEMELEMENT4>ITEM54</ITEMELEMENT4>
</record>
</RootNode>
The result of the transformation should look like this:
<ResultXml>
<record>
<header>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
</header>
<item>
<ITEMELEMENT1>ITEM11</ITEMELEMENT1>
<ITEMELEMENT2>ITEM21</ITEMELEMENT2>
<ITEMELEMENT3>ITEM31</ITEMELEMENT3>
<ITEMELEMENT4>ITEM41</ITEMELEMENT4>
</item>
<item>
<ITEMELEMENT1>ITEM21</ITEMELEMENT1>
<ITEMELEMENT2>ITEM22</ITEMELEMENT2>
<ITEMELEMENT3>ITEM23</ITEMELEMENT3>
<ITEMELEMENT4>ITEM24</ITEMELEMENT4>
</item>
<item>
<ITEMELEMENT1>ITEM51</ITEMELEMENT1>
<ITEMELEMENT2>ITEM52</ITEMELEMENT2>
<ITEMELEMENT3>ITEM53</ITEMELEMENT3>
<ITEMELEMENT4>ITEM54</ITEMELEMENT4>
</item>
</record>
<record>
<header>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABD</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD21</HEADELEMENT1>
<HEADELEMENT2>HEAD22</HEADELEMENT2>
<HEADELEMENT3>HEAD23</HEADELEMENT3>
<HEADELEMENT4>HEAD24</HEADELEMENT4>
</header>
<item>
<ITEMELEMENT1>ITEM31</ITEMELEMENT1>
<ITEMELEMENT2>ITEM32</ITEMELEMENT2>
<ITEMELEMENT3>ITEM33</ITEMELEMENT3>
<ITEMELEMENT4>ITEM34</ITEMELEMENT4>
</item>
</record>
<record>
<header>
<KEYELEMENT1>002</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD31</HEADELEMENT1>
<HEADELEMENT2>HEAD32</HEADELEMENT2>
<HEADELEMENT3>HEAD33</HEADELEMENT3>
<HEADELEMENT4>HEAD34</HEADELEMENT4>
</header>
<item>
<ITEMELEMENT1>ITEM41</ITEMELEMENT1>
<ITEMELEMENT2>ITEM42</ITEMELEMENT2>
<ITEMELEMENT3>ITEM43</ITEMELEMENT3>
<ITEMELEMENT4>ITEM44</ITEMELEMENT4>
</item>
</record>
</ResultXml>
For each distinct values in KEYELEMENT1, KEYELEMENT2, and KEYELEMENT3 I have to create one record in the result. Other header fields are the same and are transformed to header element with the key fields. Items should be mapped under the record with the same keys.
I tried the Muenchian method with something like this:
<xsl:key name="keyfields" match="record" use="concat(KEYELEMENT1, '|', KEYELEMENT2, '|', KEYELEMENT3)"/>
<xsl:template match="/">
<ResultXml>
<xsl:apply-templates select="record[generate-id() = generate-id(key('keyfields',concat(KEYELEMENT1, '|', KEYELEMENT2, '|', KEYELEMENT3))[1])]" mode="header"/>
</ResultXml>
</xsl:template>
<xsl:template match="record" mode="header">
<record>
<header>
<KEYELEMENT1><xsl:value-of select="KEYELEMENT1"/></KEYELEMENT1>
<KEYELEMENT2><xsl:value-of select="KEYELEMENT2"/></KEYELEMENT2>
<KEYELEMENT3><xsl:value-of select="KEYELEMENT3"/></KEYELEMENT3>
<HEADELEMENT1><xsl:value-of select="HEADELEMENT1"/></HEADELEMENT1>
<HEADELEMENT2><xsl:value-of select="HEADELEMENT2"/></HEADELEMENT2>
<HEADELEMENT3><xsl:value-of select="HEADELEMENT3"/></HEADELEMENT3>
<HEADELEMENT4><xsl:value-of select="HEADELEMENT4"/></HEADELEMENT4>
</header>
</record>
</xsl:template>
But I am not able to produce even header records. Any help would be appreciated.
Upvotes: 1
Views: 1948
Reputation: 31011
Muenchiann grouping in XSLT 1.0 requires a bit of ordered approach and careful usage of a number of grouping idioms.
We must start from creation of a key, to group records, in this case on KEYELEMENT1 / ...2 / ...3.
Then the main template (matching RootNode) applies "group" template to the first record from each group.
The "group" template for record
:
record
tag,head
element filled with KEY... and HEAD...
source elements,record
tag.The "normal" template for record
prints item
element filled
with ITEM... source elements.
And the last thing you need is the identity template.
So the whole script looks like below:
<?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:key name="recs" match="record"
use="concat(KEYELEMENT1, '|', KEYELEMENT2, '|', KEYELEMENT3)"/>
<xsl:template match="RootNode">
<ResultXml>
<!-- Apply "group" template to the first record in group -->
<xsl:apply-templates select="record[generate-id() = generate-id(
key('recs', concat(KEYELEMENT1, '|', KEYELEMENT2, '|', KEYELEMENT3))
[1])]" mode="group"/>
</ResultXml>
</xsl:template>
<!-- "Group" template for record -->
<xsl:template match="record" mode="group">
<record>
<head>
<xsl:copy-of select="*[starts-with(name(), 'KEY') or starts-with(name(), 'HEAD')]"/>
</head>
<!-- Apply "normal" template to all members of the current group -->
<xsl:apply-templates select="key('recs',
concat(KEYELEMENT1, '|', KEYELEMENT2, '|', KEYELEMENT3))"/>
</record>
</xsl:template>
<!-- "Normal" template for record -->
<xsl:template match="record">
<item>
<xsl:copy-of select="*[starts-with(name(), 'ITEM')]"/>
</item>
</xsl:template>
<xsl:template match="@*|node()">
<xsl:copy><xsl:apply-templates select="@*|node()"/></xsl:copy>
</xsl:template>
</xsl:stylesheet>
For a working example see http://xsltfiddle.liberty-development.net/6qM2e2k
Upvotes: 1
Reputation: 52888
If you change your first template match from /
to /*
(or RootNode
) you'll get your groups correctly.
After that it's just a matter of grouping the items into sets of 4. One way to do that is by using mod
in a predicate.
Here's an example. I use count()
instead of generate-id()
in my Muenchian grouping, but it can be done either way.
Example...
XML Input
<RootNode>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
<ITEMELEMENT1>ITEM11</ITEMELEMENT1>
<ITEMELEMENT2>ITEM21</ITEMELEMENT2>
<ITEMELEMENT3>ITEM31</ITEMELEMENT3>
<ITEMELEMENT4>ITEM41</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
<ITEMELEMENT1>ITEM21</ITEMELEMENT1>
<ITEMELEMENT2>ITEM22</ITEMELEMENT2>
<ITEMELEMENT3>ITEM23</ITEMELEMENT3>
<ITEMELEMENT4>ITEM24</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABD</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD21</HEADELEMENT1>
<HEADELEMENT2>HEAD22</HEADELEMENT2>
<HEADELEMENT3>HEAD23</HEADELEMENT3>
<HEADELEMENT4>HEAD24</HEADELEMENT4>
<ITEMELEMENT1>ITEM31</ITEMELEMENT1>
<ITEMELEMENT2>ITEM32</ITEMELEMENT2>
<ITEMELEMENT3>ITEM33</ITEMELEMENT3>
<ITEMELEMENT4>ITEM34</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>002</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD31</HEADELEMENT1>
<HEADELEMENT2>HEAD32</HEADELEMENT2>
<HEADELEMENT3>HEAD33</HEADELEMENT3>
<HEADELEMENT4>HEAD34</HEADELEMENT4>
<ITEMELEMENT1>ITEM41</ITEMELEMENT1>
<ITEMELEMENT2>ITEM42</ITEMELEMENT2>
<ITEMELEMENT3>ITEM43</ITEMELEMENT3>
<ITEMELEMENT4>ITEM44</ITEMELEMENT4>
</record>
<record>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
<ITEMELEMENT1>ITEM51</ITEMELEMENT1>
<ITEMELEMENT2>ITEM52</ITEMELEMENT2>
<ITEMELEMENT3>ITEM53</ITEMELEMENT3>
<ITEMELEMENT4>ITEM54</ITEMELEMENT4>
</record>
</RootNode>
XSLT 1.0
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
<xsl:output indent="yes"/>
<xsl:strip-space elements="*"/>
<xsl:key name="keyfields" match="record"
use="concat(KEYELEMENT1,'|',KEYELEMENT2,'|',KEYELEMENT3)"/>
<xsl:template match="@* | node()">
<xsl:copy>
<xsl:apply-templates select="@* | node()"/>
</xsl:copy>
</xsl:template>
<xsl:template match="/*">
<ResultXml>
<xsl:for-each
select="record[count(.|key('keyfields',concat(KEYELEMENT1,'|',KEYELEMENT2,'|',KEYELEMENT3))[1])=1]">
<record>
<header>
<xsl:apply-templates
select="*[starts-with(local-name(), 'KEYELEMENT') or starts-with(local-name(), 'HEADELEMENT')]"/>
</header>
<xsl:for-each select="key('keyfields',concat(KEYELEMENT1,'|',KEYELEMENT2,'|',KEYELEMENT3))/*[starts-with(local-name(),'ITEMELEMENT')][position() mod 4 = 1]">
<item>
<xsl:apply-templates
select=".|following-sibling::*[starts-with(local-name(),'ITEMELEMENT')][position() >= 1 and 4 > position()]"/>
</item>
</xsl:for-each>
</record>
</xsl:for-each>
</ResultXml>
</xsl:template>
</xsl:stylesheet>
Output
<ResultXml>
<record>
<header>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD11</HEADELEMENT1>
<HEADELEMENT2>HEAD12</HEADELEMENT2>
<HEADELEMENT3>HEAD13</HEADELEMENT3>
<HEADELEMENT4>HEAD14</HEADELEMENT4>
</header>
<item>
<ITEMELEMENT1>ITEM11</ITEMELEMENT1>
<ITEMELEMENT2>ITEM21</ITEMELEMENT2>
<ITEMELEMENT3>ITEM31</ITEMELEMENT3>
<ITEMELEMENT4>ITEM41</ITEMELEMENT4>
</item>
<item>
<ITEMELEMENT1>ITEM21</ITEMELEMENT1>
<ITEMELEMENT2>ITEM22</ITEMELEMENT2>
<ITEMELEMENT3>ITEM23</ITEMELEMENT3>
<ITEMELEMENT4>ITEM24</ITEMELEMENT4>
</item>
<item>
<ITEMELEMENT1>ITEM51</ITEMELEMENT1>
<ITEMELEMENT2>ITEM52</ITEMELEMENT2>
<ITEMELEMENT3>ITEM53</ITEMELEMENT3>
<ITEMELEMENT4>ITEM54</ITEMELEMENT4>
</item>
</record>
<record>
<header>
<KEYELEMENT1>001</KEYELEMENT1>
<KEYELEMENT2>ABD</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD21</HEADELEMENT1>
<HEADELEMENT2>HEAD22</HEADELEMENT2>
<HEADELEMENT3>HEAD23</HEADELEMENT3>
<HEADELEMENT4>HEAD24</HEADELEMENT4>
</header>
<item>
<ITEMELEMENT1>ITEM31</ITEMELEMENT1>
<ITEMELEMENT2>ITEM32</ITEMELEMENT2>
<ITEMELEMENT3>ITEM33</ITEMELEMENT3>
<ITEMELEMENT4>ITEM34</ITEMELEMENT4>
</item>
</record>
<record>
<header>
<KEYELEMENT1>002</KEYELEMENT1>
<KEYELEMENT2>ABC</KEYELEMENT2>
<KEYELEMENT3>EFG</KEYELEMENT3>
<HEADELEMENT1>HEAD31</HEADELEMENT1>
<HEADELEMENT2>HEAD32</HEADELEMENT2>
<HEADELEMENT3>HEAD33</HEADELEMENT3>
<HEADELEMENT4>HEAD34</HEADELEMENT4>
</header>
<item>
<ITEMELEMENT1>ITEM41</ITEMELEMENT1>
<ITEMELEMENT2>ITEM42</ITEMELEMENT2>
<ITEMELEMENT3>ITEM43</ITEMELEMENT3>
<ITEMELEMENT4>ITEM44</ITEMELEMENT4>
</item>
</record>
</ResultXml>
Fiddle: http://xsltfiddle.liberty-development.net/3Nqn5Yi
Upvotes: 1