Re: [xsl] Feedback on grouping solution

Subject: Re: [xsl] Feedback on grouping solution
From: "Rick Quatro rick@xxxxxxxxxxxxxx" <xsl-list-service@xxxxxxxxxxxxxxxxxxxxxx>
Date: Mon, 28 Oct 2019 12:52:19 -0000
Hi Dimitre,



Thanks for the follow up. Yes, the children of adjacent step elements have to
be treated as one group and that lead me to discover another possibility: the
first para in each step in the group has to start on the "left" (an
odd-numbered position in the output group). I modified the test input so that
the first para in the last step would require a spacer.



<?xml version="1.0" encoding="UTF-8"?>

<procedure>

    <title/>

    <step>

        <para/>

        <important/>

        <figure/>

        <figure/>

    </step>

    <note/>

    <note/>

    <step>

        <para/>

        <figure/>

        <figure/>

        <note/>

        <figure/>

        <caution/>

    </step>

    <step>

        <para/> <!-- will need a spacer -->

        <figure/>

        <figure/>

    </step>

</procedure>



Here is the updated stylesheet:



<?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="2.0">



    <xsl:output indent="yes"/>



    <xsl:template match="/procedure">

        <procedure>

            <!-- Group the children of the procedure, keeping adjacent steps
together. -->

            <xsl:for-each-group select="child::*"
group-adjacent="boolean(self::step)">

                <xsl:choose>

                    <xsl:when test="current-grouping-key()">

                        <!-- step group, process the children. -->

                        <xsl:variable name="elements"
select="current-group()/*"/>

                        <xsl:call-template name="step-element">

                            <xsl:with-param name="elements"
select="$elements"/>

                            <xsl:with-param name="positionInGroup"
select="1"/>

                            <xsl:with-param name="positionInOutput"
select="1"/>

                        </xsl:call-template>

                    </xsl:when>

                    <xsl:otherwise>

                        <xsl:apply-templates select="current-group()"/>

                    </xsl:otherwise>

                </xsl:choose>

            </xsl:for-each-group>

        </procedure>

    </xsl:template>



    <xsl:template name="step-element">

        <xsl:param name="elements"/>

        <xsl:param name="positionInGroup"/>

        <xsl:param name="positionInOutput"/>

        <xsl:variable name="currentElement"
select="$elements[$positionInGroup]"/>

        <xsl:choose>

            <xsl:when test="$currentElement[self::figure or self::para]">

                <xsl:choose>

                    <!-- See where the figure or first para is going to fall
in the output. -->

                    <xsl:when test="($currentElement[self::figure] and
$positionInOutput mod 2 != 0) or

                        ($currentElement[self::para] and $positionInOutput mod
2 = 0 and not($currentElement/preceding-sibling::*))">

                        <!-- A spacer to force the figure to the right

                             or the first para in a step to the left. -->

                        <spacer/>  <xsl:comment select="$positionInOutput"/>

                        <xsl:call-template name="step-element">

                            <xsl:with-param name="elements"
select="$elements"/>

                            <xsl:with-param name="positionInGroup"
select="$positionInGroup"/>

                            <xsl:with-param name="positionInOutput"
select="$positionInOutput + 1"/>

                        </xsl:call-template>

                    </xsl:when>

                    <xsl:otherwise>

                        <xsl:apply-templates select="$currentElement"/>
<xsl:comment select="$positionInOutput"/>

                        <xsl:if test="$elements[$positionInGroup + 1]">

                            <xsl:call-template name="step-element">

                                <xsl:with-param name="elements"
select="$elements"/>

                                <xsl:with-param name="positionInGroup"
select="$positionInGroup + 1"/>

                                <xsl:with-param name="positionInOutput"
select="$positionInOutput + 1"/>

                            </xsl:call-template>

                        </xsl:if>

                    </xsl:otherwise>

                </xsl:choose>

            </xsl:when>

            <xsl:otherwise>

                <xsl:apply-templates select="$currentElement"/> <xsl:comment
select="$positionInOutput"/>

                <xsl:if test="$elements[$positionInGroup + 1]">

                    <xsl:call-template name="step-element">

                        <xsl:with-param name="elements" select="$elements"/>

                        <xsl:with-param name="positionInGroup"
select="$positionInGroup + 1"/>

                        <xsl:with-param name="positionInOutput"
select="$positionInOutput + 1"/>

                    </xsl:call-template>

                </xsl:if>

            </xsl:otherwise>

        </xsl:choose>

    </xsl:template>



    <!-- Identity transform -->

    <xsl:template match="element()">

        <xsl:copy>

            <xsl:apply-templates select="@*, node()"/>

        </xsl:copy>

    </xsl:template>



</xsl:stylesheet>



Here is the output with comments indicating each element's position within the
group in the output.



<?xml version="1.0" encoding="UTF-8"?>

<procedure>

   <title/>

   <para/>   <!--1-->

   <important/>   <!--2-->

   <spacer/>   <!--3-->

   <figure/>   <!--4-->

   <spacer/>   <!--5-->

   <figure/>   <!--6-->

   <note/>

   <note/>

   <para/>   <!--1-->

   <figure/>   <!--2-->

   <spacer/>   <!--3-->

   <figure/>   <!--4-->

   <note/>   <!--5-->

   <figure/>   <!--6-->

   <caution/>   <!--7-->

   <spacer/>   <!--8-->

   <para/>   <!--9-->

   <figure/>   <!--10-->

   <spacer/>   <!--11-->

   <figure/>   <!--12-->

</procedure>



Although the two-pass solution is more compact and XSLT 1.0-compatible, it has
been useful for me to work through and try to understand the recursion I have
to use. Thank you to all who contributed. This is a great list!



Rick

Current Thread