I have a simple XML with two levels (Header and Line) of tags such as:
<?xml version="1.0"?>
<Header>
<line>Line 1</line>
<line>Line 2</line>
<line>Line 3</line>
<line>Line 4</line>
<line>Line 5</line>
<line>Line 6</line>
<line>Line 7</line>
<line>Line 8</line>
<line>Line 9</line>
</Header>
I need to group the lines on sets of X (X=3 for example) lines so that my output is the following:
<?xml version="1.0"?>
<Header>
<set>
<line>Line 1</line>
<line>Line 2</line>
<line>Line 3</line>
</set>
<set>
<line>Line 4</line>
<line>Line 5</line>
<line>Line 6</line>
</set>
<set>
<line>Line 7</line>
<line>Line 8</line>
<line>Line 9</line>
</set>
</Header>
How do I write a XSLT that can do this kind of transformation?
The value used to select the items in the current group. The current-grouping-key() function is only useful inside an <xsl:for-each-group> element with a group-by or group-adjacent attribute. Calling current-grouping-key() anywhere else returns the empty sequence.
Allocates the items in an input sequence into groups of items (that is, it establishes a collection of sequences) based either on common values of a grouping key, or on a pattern that the initial or final node in a group must match.
The following transformation produces the required result:
<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="vN" select="3"/>
<xsl:template match="/*">
<xsl:copy>
<xsl:apply-templates
select="line[position() mod $vN = 1]"/>
</xsl:copy>
</xsl:template>
<xsl:template match="line">
<set>
<xsl:apply-templates mode="copy" select=
".
|
following-sibling::line[position() < $vN]"/>
</set>
</xsl:template>
<xsl:template match="line" mode="copy">
<xsl:copy-of select="."/>
</xsl:template>
</xsl:stylesheet>
When applied on the provided XML document:
<Header>
<line>Line 1</line>
<line>Line 2</line>
<line>Line 3</line>
<line>Line 4</line>
<line>Line 5</line>
<line>Line 6</line>
<line>Line 7</line>
<line>Line 8</line>
<line>Line 9</line>
</Header>
the result is:
<Header>
<set>
<line>Line 1</line>
<line>Line 2</line>
<line>Line 3</line>
</set>
<set>
<line>Line 4</line>
<line>Line 5</line>
<line>Line 6</line>
</set>
<set>
<line>Line 7</line>
<line>Line 8</line>
<line>Line 9</line>
</set>
</Header>
Do note the following:
The use of the XPath mod
operator to find out the first line
element in every group of vN
elements.
The use of modes, in order to be able to process different line
elements by different templates
In general in XSLT if you want to create a heirarchy from a list you can make use of the preceding-sibling and following-sibling keywords. This is easist if there is a marker entry between sets.
As you don't have a marker as such in this case I imagine a solution could involve the following-sibling keyword and the mod operator. The mod providing the division between sets.
I haven't tried it but that would be my first start.
xslt is normally a good place to start with understanding the different keywords.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With