Re: [xsl] Grouping elements that have at least one common value

Subject: Re: [xsl] Grouping elements that have at least one common value
From: "Matthieu Ricaud-Dussarget ricaudm@xxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Wed, 28 Jun 2023 18:18:33 -0000
Hi,


I finally got something that works and doesn't take too long. Debugging
with xsl:message,  I adapted the XSLT to prevent calculating the same thing
a lot of time.

I come back to "except" which is quite more readable.


This is it :

<xsl:stylesheet
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  xmlns:xs="http://www.w3.org/2001/XMLSchema";
  xmlns:xd="http://www.oxygenxml.com/ns/doc/xsl";
  xmlns:xf="http://www.lefebvre-sarrut.eu/ns/xmlfirst";
  xmlns:els="http://www.lefebvre-sarrut.eu/ns/els";
  xmlns:saxon="http://saxon.sf.net/";
  exclude-result-prefixes="#all"
  version="3.0">

  <xsl:key name="getGrchoixbyChoixCode" match="GRCHOIX" use="CHOIX/@CODE"/>
  <xsl:variable name="root" select="/" as="document-node()"/>

  <xsl:template match="FORMS">
    <xsl:copy>
      <xsl:call-template name="els:process">
        <xsl:with-param name="GRCHOIX" as="element()*" select="GRCHOIX"/>
      </xsl:call-template>
    </xsl:copy>
  </xsl:template>

  <xsl:template name="els:process">
    <xsl:param name="GRCHOIX" as="element(GRCHOIX)*"/>
    <xsl:param name="processed-GRCHOIX" select="()" as="element(GRCHOIX)*"/>
    <xsl:if test="not(empty($GRCHOIX))">
      <xsl:variable name="start-node" select="($GRCHOIX)[1]"
as="element(GRCHOIX)"/>
      <xsl:variable name="start-node.transitive-closure"
as="element(GRCHOIX)*"
        select="els:transitive-closure($start-node)"/>
      <GROUP
GRCHOIX="{distinct-values($start-node.transitive-closure/@CODE)}">
        <xsl:sequence select="$start-node.transitive-closure"/>
      </GROUP>
      <xsl:variable name="processed-GRCHOIX.local"
select="($processed-GRCHOIX, $start-node.transitive-closure)"
as="element()*"/>
      <xsl:call-template name="els:process">
        <xsl:with-param name="GRCHOIX" select="$GRCHOIX except
$processed-GRCHOIX.local"/>
        <xsl:with-param name="processed-GRCHOIX"
select="$processed-GRCHOIX.local"/>
      </xsl:call-template>
    </xsl:if>
  </xsl:template>

  <xsl:function name="els:getGrchoixbyChoixCode" as="element(GRCHOIX)*">
    <xsl:param name="GRCHOIX" as="element(GRCHOIX)"/>
    <xsl:sequence select="key('getGrchoixbyChoixCode',
$GRCHOIX/CHOIX/@CODE, $root)"/>
  </xsl:function>

  <xsl:function name="els:transitive-closure" as="element()*">
    <xsl:param name="start-node" as="element()"/>
    <xsl:iterate select="1 to 100000000">
      <xsl:param name="result" as="element()*" select="()"/>
      <xsl:param name="origins" as="element()*" select="$start-node"/>
      <xsl:param name="previous-origins" as="element()*" select="()"/>
      <xsl:variable name="current-iteration.result" as="element()*"
        select="($origins except $previous-origins) !
els:getGrchoixbyChoixCode(.) except $result"/>
      <xsl:choose>
        <xsl:when test="empty($current-iteration.result)">
          <xsl:sequence select="$result"/>
          <xsl:break/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:next-iteration>
            <xsl:with-param name="result" select="$result |
$current-iteration.result"/>
            <xsl:with-param name="origins"
select="$current-iteration.result except $origins"/>
            <xsl:with-param name="previous-origins"
select="$previous-origins | $origins"/>
          </xsl:next-iteration>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:iterate>
  </xsl:function>

</xsl:stylesheet>


Probably not perfect, but I also write a short XSLT to check the result :

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform";
  xmlns:xs="http://www.w3.org/2001/XMLSchema";
  exclude-result-prefixes="xs"
  version="3.0">

  <xsl:key name="getGROUPbyChoixCode" match="GROUP"
use="GRCHOIX/CHOIX/@CODE"/>

  <xsl:template match="/FORMS">
    <xsl:copy>
      <xsl:variable name="self" select="." as="element()"/>
      <xsl:for-each select="distinct-values(GROUP/GRCHOIX/CHOIX/@CODE)">
        <xsl:variable name="CHOIX.CODE" select="."/>
        <CHOIX CODE="{$CHOIX.CODE}"
COUNT-GROUP="{count(key('getGROUPbyChoixCode', $CHOIX.CODE, $self))}"/>
      </xsl:for-each>
    </xsl:copy>
  </xsl:template>

</xsl:stylesheet>


And yes every CHOIX/@CODE appears in the same GROUP (COUNT-GROUP is always
equals to 1)


So I'm fine with this :)


Thanks again for helping


Cheers

Matthieu RICAUD-DUSSARGET

Current Thread