xsl-list
[Top] [All Lists]

[xsl] XSLT3 generic grouping functions

2020-07-08 04:24:21
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
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)
 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)
Working examples can be found in the test folder 
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) on 
the generic starting-with grouping function, but this is an occasion to share 
theses grouping libraries :)

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/1167547
or by email: xsl-list-unsub(_at_)lists(_dot_)mulberrytech(_dot_)com
--~--
<Prev in Thread] Current Thread [Next in Thread>