It might be useful to think of xsl:for-each-group as providing a solution for
"stereotype" grouping use cases that are commonly encountered, but looking to
other functional primitives for solutions to the more general problem: for
example you can use fn:fold-left to build up the set of groups e.g. as an array
of arrays. For the common use-case tackled by group-by, creating an index in
the form of a map is often a convenient alternative to xsl:for-each-group.
If you're starting with the requirement "create a sequence of groups, starting
a new group at item x when $test(x) returns true" then rather than use
xsl:for-each-group with a group-starting pattern, another approach (producing a
sequence of arrays) would be
fold-left($population, (), function($groups, $x) {if ($test($x)) then ([$x],
$groups) else array:append(head($groups), $x))}) => reverse()
(It builds the list of groups in reverse order for convenience and then
reverses it at the end).
At what point you stop using the "stereotype" xsl:for-each-group instruction
and start rolling-your-own is an open question, but with maps, arrays, and
higher-order-functions, rolling your own is much more viable than it was.
Michael Kay
Saxonica
On 8 Jul 2020, at 10:24, Matthieu RICAUD-DUSSARGET
m(_dot_)ricaud-dussarget(_at_)lefebvre-sarrut(_dot_)eu
<xsl-list-service(_at_)lists(_dot_)mulberrytech(_dot_)com> wrote:
Hi all,
While teaching XSLT, someone ask if it was possible to have a kind of generic
function to group XML elements.
I first answer the only way to do it was to use (eventually nested)
<xsl:for-each-group> instruction with specific conditions according to the
data.
Later, as I was discovering XSLT3, I realize the grouping condition might be
sent as an higher-order-function parameter, which lead me to write some
generic grouping functions to wrap XML elements (see example below).
The original code can be found on github at
https://github.com/ELSGestion/els-sie-xsl-lib/blob/master/src/main/xsl/els-common_xml.xsl
<https://github.com/ELSGestion/els-sie-xsl-lib/blob/master/src/main/xsl/els-common_xml.xsl>
There’s also some generic xsl to nest titles
(https://github.com/ELSGestion/els-sie-xsl-lib/blob/master/src/main/xsl/nest-titles.xsl
<https://github.com/ELSGestion/els-sie-xsl-lib/blob/master/src/main/xsl/nest-titles.xsl>)
and a specific implementation of it to nest HTML titles
(https://github.com/ELSGestion/els-sie-xsl-lib/blob/master/src/main/xsl/nest-html-titles.xsl
<https://github.com/ELSGestion/els-sie-xsl-lib/blob/master/src/main/xsl/nest-html-titles.xsl>)
Working examples can be found in the test folder
https://github.com/ELSGestion/els-sie-xsl-lib/tree/master/src/test
<https://github.com/ELSGestion/els-sie-xsl-lib/tree/master/src/test>, as
xspec unit test or xspec driven integration tests.
I actually have a bug with Saxon 9.9 (https://saxonica.plan.io/issues/4636
<https://saxonica.plan.io/issues/4636>) on the generic starting-with grouping
function, but this is an occasion to share theses grouping libraries J
Any comments, feedbacks or similar code implementation are welcome !
Cheers
Matthieu Ricaud-Dussarget
Example of generic adjacent grouping function used to define a specific
“by-name grouping” implementation (there’s a similar set of functions for
starting-with) :
<xd:doc>
<xd:desc>
<xd:p>Wrap adjacent elements into a new "wrapper" element</xd:p>
<xd:p>CAUTION : any text, pi, comments within context will be loose</xd:p>
</xd:desc>
<xd:param name="context">Parent of the adjacent elements to wrap</xd:param>
<xd:param name="adjacent.function">Xpath function to set the adjacency
condition</xd:param>
<xd:param name="wrapper">Element wrapper</xd:param>
<xd:param name="keep-context">Say if the context should be kept or not in
the result</xd:param>
<xd:return>context (or its content) with wrapped adjacent
element</xd:return>
</xd:doc>
<xsl:function name="els:wrap-elements-adjacent" as="node()*">
<xsl:param name="context" as="element()"/>
<xsl:param name="adjacent.function"/> <!--as="xs:string"-->
<xsl:param name="wrapper" as="element()"/>
<xsl:param name="keep-context" as="xs:boolean"/>
<xsl:variable name="content" as="item()*">
<xsl:for-each-group select="$context/*"
group-adjacent="$adjacent.function(.)">
<xsl:choose>
<xsl:when test="current-grouping-key()">
<xsl:for-each select="$wrapper">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:sequence select="current-group()"/>
</xsl:copy>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:copy-of select="current-group()"/>
</xsl:otherwise>
</xsl:choose>
</xsl:for-each-group>
</xsl:variable>
<xsl:choose>
<xsl:when test="$keep-context">
<xsl:for-each select="$context">
<xsl:copy>
<xsl:copy-of select="@*"/>
<xsl:sequence select="$content"/>
</xsl:copy>
</xsl:for-each>
</xsl:when>
<xsl:otherwise>
<xsl:sequence select="$content"/>
</xsl:otherwise>
</xsl:choose>
</xsl:function>
<xd:doc>
<xd:desc>
<xd:p>Wrap "adjacent by name" elements into a new "wrapper"
element</xd:p>
<xd:p>CAUTION : any text, pi, comment within context will be loose</xd:p>
</xd:desc>
<xd:param name="context">Parent of the adjacent elements to wrap</xd:param>
<xd:param name="adjacent.names">sequence of qualified names to set adjacent
elements</xd:param>
<xd:param name="wrapper">element wrapper</xd:param>
<xd:param name="keep-context">Say if the context should be kept or not in
the result</xd:param>
<xd:return>context (or its content) with wrapped adjacent
element</xd:return>
</xd:doc>
<xsl:function name="els:wrap-elements-adjacent-by-names" as="node()*">
<xsl:param name="context" as="element()"/>
<xsl:param name="adjacent.names" as="xs:string+"/>
<xsl:param name="wrapper" as="element()"/>
<xsl:param name="keep-context" as="xs:boolean"/>
<xsl:sequence select="els:wrap-elements-adjacent(
$context,
function($e) as xs:boolean {name($e) = $adjacent.names},
$wrapper,
$keep-context)"/>
</xsl:function>
XSL-List info and archive <http://www.mulberrytech.com/xsl/xsl-list>
EasyUnsubscribe <http://lists.mulberrytech.com/unsub/xsl-list/293509> (by
email <>)
--~----------------------------------------------------------------
XSL-List info and archive: http://www.mulberrytech.com/xsl/xsl-list
EasyUnsubscribe: http://lists.mulberrytech.com/unsub/xsl-list/1167547
or by email: xsl-list-unsub(_at_)lists(_dot_)mulberrytech(_dot_)com
--~--