Grouping mechanisms that are specific to XSLT. Should always be complemented with either the xslt-1.0, xslt-2.0 or xslt-3.0 tag to indicate the version used.
Grouping in XSLT 3
Grouping in XSLT 3, the current and latest version of XSLT since 2017, can be achieved primarily using the xsl:for-each-group
instruction where you select the items to be grouped, the grouping population, with the select
attribute and then have three different ways to group the population:
- the
group-by
attribute selecting a sequence of grouping keys by which the grouping population is to be grouped - the
group-adjacent
attribute selecting a sequence of grouping keys by which adjacent items in the population are to be grouped - the
group-starting-with
orgroup-ending-with
attributes defining patterns to identify and start groups based on pattern matching of the initial (group-starting-with
) or final (group-ending-with
) member of a group
Access to the items of a currently processed group is given by the current-group()
function, access to the current grouping key by the current-grouping-key()
function.
The XSLT 3 specification gives at least one example for each grouping approach directly in the specification section https://www.w3.org/TR/xslt-30/#grouping-examples; below you find links to XSLT fiddles based on these examples:
- grouping items based on a single common value, using
group-by
: https://xsltfiddle.liberty-development.net/ncdD7mq/1 - grouping items based on a sequence of values as the grouping key, i.e. a composite key, using
group-by
: https://xsltfiddle.liberty-development.net/pPzifps - grouping alternate sequences of items using
group-adjacent
: https://xsltfiddle.liberty-development.net/3NJ38Zc - identifying a group by its initial element, using
group-starting-with
: https://xsltfiddle.liberty-development.net/bnnZWa - identifying a group by its final element, using
group-ending-with
: https://xsltfiddle.liberty-development.net/6r5Gh3e - grouping items into several groups using
group-by
if thegroup-by
expression gives a sequence of more than one value: https://xsltfiddle.liberty-development.net/94rmq6g
Of course the different approaches can, for more complex tasks, be combined by nesting xsl:for-each-group
instructions.
XSLT 3 with XPath 3.1 support can also group JSON represented as XPath 3.1 maps and arrays, here is a list of showing the previous XML grouping samples now using JSON input and directly grouping the map/array data structure returned by the parse-json
function:
- grouping items based on a single common value, using
group-by
: https://xsltfiddle.liberty-development.net/jyRYYip - grouping items based on a sequence of values as the grouping key, i.e. a composite key, using
group-by
: https://xsltfiddle.liberty-development.net/ncdD7mr - identifying a group by its initial item, using
group-starting-with
: https://xsltfiddle.liberty-development.net/pPzifpt - identifying a group by its final item, using
group-ending-with
: https://xsltfiddle.liberty-development.net/bnnZWb - grouping items into several groups using
group-by
if thegroup-by
expression gives a sequence of more than one value: https://xsltfiddle.liberty-development.net/3NJ38Zd - grouping items into several groups using
group-by
if thegroup-by
expression gives a sequence of more than one value, this time with JSON output: https://xsltfiddle.liberty-development.net/3NJ38Zd/1
Positional grouping
Positional grouping can be used to split a sequence of items into groups of a certain size; in XSLT 2 or 3 this is easily achieved using e.g. for-each-group select="foo" group-adjacent="(position() - 1) idiv $chunk-size"
: https://xsltfiddle.liberty-development.net/asoTK9
https://martin-honnen.github.io/xslt/generic-positional-grouping-functions.xsl is a compact XSLT 3 module using higher-order functions importable by other code that needs to split up a sequence of items using positional grouping, you could use it as follows (Saxon-JS 2, Saxon 10 or higher all editions, Saxon 9.8 or higher PE and EE, Altova XML 2017 R3 or later)
<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
version="3.0"
xmlns:xs="http://www.w3.org/2001/XMLSchema"
xmlns:mf="http://example.com/mf"
xmlns:ex="http://example.com/ex"
exclude-result-prefixes="#all"
expand-text="yes">
<xsl:import href="https://martin-honnen.github.io/xslt/generic-positional-grouping-functions.xsl"/>
<xsl:function name="ex:wrap-rows" as="element()">
<xsl:param name="group" as="item()*"/>
<xsl:param name="pos" as="xs:integer"/>
<xsl:param name="wrapper-name" as="xs:QName"/>
<xsl:element name="{$wrapper-name}" namespace="{namespace-uri-from-QName($wrapper-name)}">
<xsl:attribute name="index" select="$pos"/>
<xsl:sequence select="$group"/>
</xsl:element>
</xsl:function>
<xsl:output method="xml" indent="yes"/>
<xsl:template match="root">
<root>
<xsl:sequence select="mf:group-chunks(item, 3, ex:wrap-rows(?, ?, QName('', 'chunk')))"/>
</root>
</xsl:template>
<xsl:template match="/">
<xsl:next-match/>
<xsl:comment>Run with {system-property('xsl:product-name')} {system-property('xsl:product-version')} {system-property('Q{http://saxon.sf.net/}platform')}</xsl:comment>
</xsl:template>
</xsl:stylesheet>
to e.g. wrap any chunk into a container element.
Grouping in XSLT 2
Grouping in XSLT 2 also works with the xsl:for-each-group
instruction as in XSLT 3, the main restrictions are that XSLT 2 does not support composite grouping keys (so to group on various items you need to concat
or string-join
them as single value (e.g. composite="yes" group-by="foo, bar"
in XSLT 3 needs to be done as group-by="string-join((foo, bar), '|')"
)) and that pattern matching can only be done on nodes, not on primitive values.
The XSLT 2 specification gives at least one example for each grouping approach directly in the specification section https://www.w3.org/TR/xslt20/#grouping-examples; below you find links to XSLT fiddles based on these examples:
- grouping items based on a single common value, using
group-by
: https://xsltfiddle.liberty-development.net/gW3LG7m - grouping items based on a composite, concatenated grouping key using
group-by
: https://xsltfiddle.liberty-development.net/ncdD7ms - grouping adjacent nodes using
group-adjacent
: https://xsltfiddle.liberty-development.net/3NJ38Ze - grouping nodes based on the pattern of the initial node in a group, using
group-starting-with
: https://xsltfiddle.liberty-development.net/pPzifpu - grouping nodes based on the pattern of the final node in a group using
group-ending-with
: https://xsltfiddle.liberty-development.net/bnnZWc group-by
example adding an item to several groups if grouping key expression returns a sequence of more than one value: https://xsltfiddle.liberty-development.net/6r5Gh3g
Grouping in XSLT 1.0
The preferred solution in XSLT 1.0 is to use the Muenchian grouping method:
http://www.jenitennison.com/xslt/grouping/muenchian.html