Some help on how groupings actually work would be much appreciated












0















I'm trying to get my head around how the different grouping techniques work. What causes a group, how are each group defined, and how the key are formed for each group.



If I wanted to use "group-adjacent" to move all following siblings of a specific element name into the first of preceding sibling of a given type. Would this be doable? I know how I can do this with recursive templates, and to some extent with keys in xslt 1.0. But I cannot get the 2.0 groups to work for me.



Lets say that I want to move all fig elements into the first preceding para, given that there are no other kinds of elements in between the fig(s) and the preceding para element, in this simple xml.



<root>
<first_lvl>
<title>First heading</title>
<para>First para under first heading</para>
<para>Second para under first heading</para>
<fig>fig1</fig>
<fig>fig 2</fig>
<table>Table A</table>
<fig>fig 3</fig>
<para>Third para under first heading</para>
<para>Fourth para under first heading</para>
<fig>fig4</fig>
</first_lvl>
</root>


Desired result:



<root>
<first_lvl>
<title>First heading</title>
<para>First para under first heading</para>
<para>Second para under first heading
<fig>fig1</fig>
<fig>fig 2</fig>
</para>
<table>Table A</table>
<fig>fig 3</fig>
<para>Third para under first heading</para>
<para>Fourth para under first heading
<fig>fig4</fig>
</para>
</first_lvl>
</root>


How can I set a grouping up that takes care of every directly following fig element?



This doesn't work:



<xsl:template match=para[following-sibling::*[1][self::fig]]>
<xsl:for-each-group select"folowing-sibling::*" group-adjacent="boolean(self::fig)">
<xsl:apply-templates select="current-group()" mode="move"/>
</xsl:for-each-group>
</xsl:template>


And then I've added atemplate to build content for each fig inside the para, and one to ignore those figs when they appear later on in the processing.



No luck though.



I have no other values to group by, other that the fact that they are fig elements.



What am I missing here?










share|improve this question



























    0















    I'm trying to get my head around how the different grouping techniques work. What causes a group, how are each group defined, and how the key are formed for each group.



    If I wanted to use "group-adjacent" to move all following siblings of a specific element name into the first of preceding sibling of a given type. Would this be doable? I know how I can do this with recursive templates, and to some extent with keys in xslt 1.0. But I cannot get the 2.0 groups to work for me.



    Lets say that I want to move all fig elements into the first preceding para, given that there are no other kinds of elements in between the fig(s) and the preceding para element, in this simple xml.



    <root>
    <first_lvl>
    <title>First heading</title>
    <para>First para under first heading</para>
    <para>Second para under first heading</para>
    <fig>fig1</fig>
    <fig>fig 2</fig>
    <table>Table A</table>
    <fig>fig 3</fig>
    <para>Third para under first heading</para>
    <para>Fourth para under first heading</para>
    <fig>fig4</fig>
    </first_lvl>
    </root>


    Desired result:



    <root>
    <first_lvl>
    <title>First heading</title>
    <para>First para under first heading</para>
    <para>Second para under first heading
    <fig>fig1</fig>
    <fig>fig 2</fig>
    </para>
    <table>Table A</table>
    <fig>fig 3</fig>
    <para>Third para under first heading</para>
    <para>Fourth para under first heading
    <fig>fig4</fig>
    </para>
    </first_lvl>
    </root>


    How can I set a grouping up that takes care of every directly following fig element?



    This doesn't work:



    <xsl:template match=para[following-sibling::*[1][self::fig]]>
    <xsl:for-each-group select"folowing-sibling::*" group-adjacent="boolean(self::fig)">
    <xsl:apply-templates select="current-group()" mode="move"/>
    </xsl:for-each-group>
    </xsl:template>


    And then I've added atemplate to build content for each fig inside the para, and one to ignore those figs when they appear later on in the processing.



    No luck though.



    I have no other values to group by, other that the fact that they are fig elements.



    What am I missing here?










    share|improve this question

























      0












      0








      0








      I'm trying to get my head around how the different grouping techniques work. What causes a group, how are each group defined, and how the key are formed for each group.



      If I wanted to use "group-adjacent" to move all following siblings of a specific element name into the first of preceding sibling of a given type. Would this be doable? I know how I can do this with recursive templates, and to some extent with keys in xslt 1.0. But I cannot get the 2.0 groups to work for me.



      Lets say that I want to move all fig elements into the first preceding para, given that there are no other kinds of elements in between the fig(s) and the preceding para element, in this simple xml.



      <root>
      <first_lvl>
      <title>First heading</title>
      <para>First para under first heading</para>
      <para>Second para under first heading</para>
      <fig>fig1</fig>
      <fig>fig 2</fig>
      <table>Table A</table>
      <fig>fig 3</fig>
      <para>Third para under first heading</para>
      <para>Fourth para under first heading</para>
      <fig>fig4</fig>
      </first_lvl>
      </root>


      Desired result:



      <root>
      <first_lvl>
      <title>First heading</title>
      <para>First para under first heading</para>
      <para>Second para under first heading
      <fig>fig1</fig>
      <fig>fig 2</fig>
      </para>
      <table>Table A</table>
      <fig>fig 3</fig>
      <para>Third para under first heading</para>
      <para>Fourth para under first heading
      <fig>fig4</fig>
      </para>
      </first_lvl>
      </root>


      How can I set a grouping up that takes care of every directly following fig element?



      This doesn't work:



      <xsl:template match=para[following-sibling::*[1][self::fig]]>
      <xsl:for-each-group select"folowing-sibling::*" group-adjacent="boolean(self::fig)">
      <xsl:apply-templates select="current-group()" mode="move"/>
      </xsl:for-each-group>
      </xsl:template>


      And then I've added atemplate to build content for each fig inside the para, and one to ignore those figs when they appear later on in the processing.



      No luck though.



      I have no other values to group by, other that the fact that they are fig elements.



      What am I missing here?










      share|improve this question














      I'm trying to get my head around how the different grouping techniques work. What causes a group, how are each group defined, and how the key are formed for each group.



      If I wanted to use "group-adjacent" to move all following siblings of a specific element name into the first of preceding sibling of a given type. Would this be doable? I know how I can do this with recursive templates, and to some extent with keys in xslt 1.0. But I cannot get the 2.0 groups to work for me.



      Lets say that I want to move all fig elements into the first preceding para, given that there are no other kinds of elements in between the fig(s) and the preceding para element, in this simple xml.



      <root>
      <first_lvl>
      <title>First heading</title>
      <para>First para under first heading</para>
      <para>Second para under first heading</para>
      <fig>fig1</fig>
      <fig>fig 2</fig>
      <table>Table A</table>
      <fig>fig 3</fig>
      <para>Third para under first heading</para>
      <para>Fourth para under first heading</para>
      <fig>fig4</fig>
      </first_lvl>
      </root>


      Desired result:



      <root>
      <first_lvl>
      <title>First heading</title>
      <para>First para under first heading</para>
      <para>Second para under first heading
      <fig>fig1</fig>
      <fig>fig 2</fig>
      </para>
      <table>Table A</table>
      <fig>fig 3</fig>
      <para>Third para under first heading</para>
      <para>Fourth para under first heading
      <fig>fig4</fig>
      </para>
      </first_lvl>
      </root>


      How can I set a grouping up that takes care of every directly following fig element?



      This doesn't work:



      <xsl:template match=para[following-sibling::*[1][self::fig]]>
      <xsl:for-each-group select"folowing-sibling::*" group-adjacent="boolean(self::fig)">
      <xsl:apply-templates select="current-group()" mode="move"/>
      </xsl:for-each-group>
      </xsl:template>


      And then I've added atemplate to build content for each fig inside the para, and one to ignore those figs when they appear later on in the processing.



      No luck though.



      I have no other values to group by, other that the fact that they are fig elements.



      What am I missing here?







      xslt-2.0






      share|improve this question













      share|improve this question











      share|improve this question




      share|improve this question










      asked Nov 28 '18 at 18:01









      Zug_BugZug_Bug

      52




      52
























          1 Answer
          1






          active

          oldest

          votes


















          0














          I would start with a group-starting-with on those para followed by a fig and then inside use group-adjacent to identify only the first group of adjacent figs. With the verbosity of XSLT that looks a bit convoluted but does the job as far as I have understood your requirements:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="tail(current-group())" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh



          I have taken the liberty to use XSLT 3 instead of 2 but you simply would have to spell out the identity transformation declared by the xsl:mode on-no-match="shallow-copy" and make sure you use



          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>


          instead of the XSLT 3 only xsl:copy with a select:



                                        <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>


          and instead of the XPath 3 tail function you use subsequence e.g.



          <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

          <xsl:template match="@*|node()">
          <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
          </xsl:template>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="subsequence(current-group(), 2)" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:transform>


          http://xsltransform.hikmatu.com/bFDb2BN



          On the other hand, I am not sure whether an attempt not using xsl:for-each-group but rather a template matching para[following-sibling::*[1][self::fig]] and then consuming following sibling figs (which can be done easily in XSLT 3 with xsl:iterate) is not more compact:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes"/>
          <xsl:strip-space elements="*"/>

          <xsl:template match="para[following-sibling::*[1][self::fig]]">
          <xsl:copy>
          <xsl:apply-templates select="@*, node()"/>
          <xsl:iterate select="following-sibling::*">
          <xsl:choose>
          <xsl:when test="self::fig">
          <xsl:copy-of select="."/>
          </xsl:when>
          <xsl:otherwise>
          <xsl:break/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:iterate>
          </xsl:copy>
          </xsl:template>

          <xsl:template match="fig[preceding-sibling::*[not(self::fig)][1][self::para]]"/>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh/3






          share|improve this answer


























          • Thank you for your quick response - much appreciated! I cannot get this to work in XMLSpy 2015, but I'm guessing it'll work better with Saxon. I'm trying to understand some parts of your solution; first of all, won't the select in the first for-each-group be relative to the context node, which will be a para or a fig?

            – Zug_Bug
            Nov 28 '18 at 19:35













          • The template matches on the parent element of the para and fig with *[para and fig], so the for-each-group select="*" selects all child elements of the parent. In your case the parent is a first_lvl element, I was not sure whether that matters or is known, of course for the sample shown you could as well match explicitly on match="first_lvl".

            – Martin Honnen
            Nov 28 '18 at 20:03











          • As for XSLT 2 and XML Spy, see the edit, I had used some other features not supported in that language (tail) and had not correctly implemented the xsl:copy select with xsl:element, now corrected, at least now works with an XSLT 2 processor although I don't have XMLSpy 2015 to test that particular processor.

            – Martin Honnen
            Nov 28 '18 at 20:10












          Your Answer






          StackExchange.ifUsing("editor", function () {
          StackExchange.using("externalEditor", function () {
          StackExchange.using("snippets", function () {
          StackExchange.snippets.init();
          });
          });
          }, "code-snippets");

          StackExchange.ready(function() {
          var channelOptions = {
          tags: "".split(" "),
          id: "1"
          };
          initTagRenderer("".split(" "), "".split(" "), channelOptions);

          StackExchange.using("externalEditor", function() {
          // Have to fire editor after snippets, if snippets enabled
          if (StackExchange.settings.snippets.snippetsEnabled) {
          StackExchange.using("snippets", function() {
          createEditor();
          });
          }
          else {
          createEditor();
          }
          });

          function createEditor() {
          StackExchange.prepareEditor({
          heartbeatType: 'answer',
          autoActivateHeartbeat: false,
          convertImagesToLinks: true,
          noModals: true,
          showLowRepImageUploadWarning: true,
          reputationToPostImages: 10,
          bindNavPrevention: true,
          postfix: "",
          imageUploader: {
          brandingHtml: "Powered by u003ca class="icon-imgur-white" href="https://imgur.com/"u003eu003c/au003e",
          contentPolicyHtml: "User contributions licensed under u003ca href="https://creativecommons.org/licenses/by-sa/3.0/"u003ecc by-sa 3.0 with attribution requiredu003c/au003e u003ca href="https://stackoverflow.com/legal/content-policy"u003e(content policy)u003c/au003e",
          allowUrls: true
          },
          onDemand: true,
          discardSelector: ".discard-answer"
          ,immediatelyShowMarkdownHelp:true
          });


          }
          });














          draft saved

          draft discarded


















          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53525474%2fsome-help-on-how-groupings-actually-work-would-be-much-appreciated%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown

























          1 Answer
          1






          active

          oldest

          votes








          1 Answer
          1






          active

          oldest

          votes









          active

          oldest

          votes






          active

          oldest

          votes









          0














          I would start with a group-starting-with on those para followed by a fig and then inside use group-adjacent to identify only the first group of adjacent figs. With the verbosity of XSLT that looks a bit convoluted but does the job as far as I have understood your requirements:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="tail(current-group())" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh



          I have taken the liberty to use XSLT 3 instead of 2 but you simply would have to spell out the identity transformation declared by the xsl:mode on-no-match="shallow-copy" and make sure you use



          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>


          instead of the XSLT 3 only xsl:copy with a select:



                                        <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>


          and instead of the XPath 3 tail function you use subsequence e.g.



          <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

          <xsl:template match="@*|node()">
          <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
          </xsl:template>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="subsequence(current-group(), 2)" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:transform>


          http://xsltransform.hikmatu.com/bFDb2BN



          On the other hand, I am not sure whether an attempt not using xsl:for-each-group but rather a template matching para[following-sibling::*[1][self::fig]] and then consuming following sibling figs (which can be done easily in XSLT 3 with xsl:iterate) is not more compact:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes"/>
          <xsl:strip-space elements="*"/>

          <xsl:template match="para[following-sibling::*[1][self::fig]]">
          <xsl:copy>
          <xsl:apply-templates select="@*, node()"/>
          <xsl:iterate select="following-sibling::*">
          <xsl:choose>
          <xsl:when test="self::fig">
          <xsl:copy-of select="."/>
          </xsl:when>
          <xsl:otherwise>
          <xsl:break/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:iterate>
          </xsl:copy>
          </xsl:template>

          <xsl:template match="fig[preceding-sibling::*[not(self::fig)][1][self::para]]"/>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh/3






          share|improve this answer


























          • Thank you for your quick response - much appreciated! I cannot get this to work in XMLSpy 2015, but I'm guessing it'll work better with Saxon. I'm trying to understand some parts of your solution; first of all, won't the select in the first for-each-group be relative to the context node, which will be a para or a fig?

            – Zug_Bug
            Nov 28 '18 at 19:35













          • The template matches on the parent element of the para and fig with *[para and fig], so the for-each-group select="*" selects all child elements of the parent. In your case the parent is a first_lvl element, I was not sure whether that matters or is known, of course for the sample shown you could as well match explicitly on match="first_lvl".

            – Martin Honnen
            Nov 28 '18 at 20:03











          • As for XSLT 2 and XML Spy, see the edit, I had used some other features not supported in that language (tail) and had not correctly implemented the xsl:copy select with xsl:element, now corrected, at least now works with an XSLT 2 processor although I don't have XMLSpy 2015 to test that particular processor.

            – Martin Honnen
            Nov 28 '18 at 20:10
















          0














          I would start with a group-starting-with on those para followed by a fig and then inside use group-adjacent to identify only the first group of adjacent figs. With the verbosity of XSLT that looks a bit convoluted but does the job as far as I have understood your requirements:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="tail(current-group())" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh



          I have taken the liberty to use XSLT 3 instead of 2 but you simply would have to spell out the identity transformation declared by the xsl:mode on-no-match="shallow-copy" and make sure you use



          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>


          instead of the XSLT 3 only xsl:copy with a select:



                                        <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>


          and instead of the XPath 3 tail function you use subsequence e.g.



          <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

          <xsl:template match="@*|node()">
          <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
          </xsl:template>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="subsequence(current-group(), 2)" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:transform>


          http://xsltransform.hikmatu.com/bFDb2BN



          On the other hand, I am not sure whether an attempt not using xsl:for-each-group but rather a template matching para[following-sibling::*[1][self::fig]] and then consuming following sibling figs (which can be done easily in XSLT 3 with xsl:iterate) is not more compact:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes"/>
          <xsl:strip-space elements="*"/>

          <xsl:template match="para[following-sibling::*[1][self::fig]]">
          <xsl:copy>
          <xsl:apply-templates select="@*, node()"/>
          <xsl:iterate select="following-sibling::*">
          <xsl:choose>
          <xsl:when test="self::fig">
          <xsl:copy-of select="."/>
          </xsl:when>
          <xsl:otherwise>
          <xsl:break/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:iterate>
          </xsl:copy>
          </xsl:template>

          <xsl:template match="fig[preceding-sibling::*[not(self::fig)][1][self::para]]"/>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh/3






          share|improve this answer


























          • Thank you for your quick response - much appreciated! I cannot get this to work in XMLSpy 2015, but I'm guessing it'll work better with Saxon. I'm trying to understand some parts of your solution; first of all, won't the select in the first for-each-group be relative to the context node, which will be a para or a fig?

            – Zug_Bug
            Nov 28 '18 at 19:35













          • The template matches on the parent element of the para and fig with *[para and fig], so the for-each-group select="*" selects all child elements of the parent. In your case the parent is a first_lvl element, I was not sure whether that matters or is known, of course for the sample shown you could as well match explicitly on match="first_lvl".

            – Martin Honnen
            Nov 28 '18 at 20:03











          • As for XSLT 2 and XML Spy, see the edit, I had used some other features not supported in that language (tail) and had not correctly implemented the xsl:copy select with xsl:element, now corrected, at least now works with an XSLT 2 processor although I don't have XMLSpy 2015 to test that particular processor.

            – Martin Honnen
            Nov 28 '18 at 20:10














          0












          0








          0







          I would start with a group-starting-with on those para followed by a fig and then inside use group-adjacent to identify only the first group of adjacent figs. With the verbosity of XSLT that looks a bit convoluted but does the job as far as I have understood your requirements:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="tail(current-group())" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh



          I have taken the liberty to use XSLT 3 instead of 2 but you simply would have to spell out the identity transformation declared by the xsl:mode on-no-match="shallow-copy" and make sure you use



          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>


          instead of the XSLT 3 only xsl:copy with a select:



                                        <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>


          and instead of the XPath 3 tail function you use subsequence e.g.



          <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

          <xsl:template match="@*|node()">
          <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
          </xsl:template>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="subsequence(current-group(), 2)" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:transform>


          http://xsltransform.hikmatu.com/bFDb2BN



          On the other hand, I am not sure whether an attempt not using xsl:for-each-group but rather a template matching para[following-sibling::*[1][self::fig]] and then consuming following sibling figs (which can be done easily in XSLT 3 with xsl:iterate) is not more compact:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes"/>
          <xsl:strip-space elements="*"/>

          <xsl:template match="para[following-sibling::*[1][self::fig]]">
          <xsl:copy>
          <xsl:apply-templates select="@*, node()"/>
          <xsl:iterate select="following-sibling::*">
          <xsl:choose>
          <xsl:when test="self::fig">
          <xsl:copy-of select="."/>
          </xsl:when>
          <xsl:otherwise>
          <xsl:break/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:iterate>
          </xsl:copy>
          </xsl:template>

          <xsl:template match="fig[preceding-sibling::*[not(self::fig)][1][self::para]]"/>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh/3






          share|improve this answer















          I would start with a group-starting-with on those para followed by a fig and then inside use group-adjacent to identify only the first group of adjacent figs. With the verbosity of XSLT that looks a bit convoluted but does the job as far as I have understood your requirements:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="tail(current-group())" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh



          I have taken the liberty to use XSLT 3 instead of 2 but you simply would have to spell out the identity transformation declared by the xsl:mode on-no-match="shallow-copy" and make sure you use



          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>


          instead of the XSLT 3 only xsl:copy with a select:



                                        <xsl:copy select="$para-head">
          <xsl:apply-templates select="node(), current-group()"/>
          </xsl:copy>


          and instead of the XPath 3 tail function you use subsequence e.g.



          <xsl:transform xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0">

          <xsl:template match="@*|node()">
          <xsl:copy>
          <xsl:apply-templates select="@*|node()"/>
          </xsl:copy>
          </xsl:template>

          <xsl:output method="xml" indent="yes" />

          <xsl:template match="*[para and fig]">
          <xsl:copy>
          <xsl:apply-templates select="@*"/>
          <xsl:for-each-group select="*" group-starting-with="para[following-sibling::*[1][self::fig]]">
          <xsl:choose>
          <xsl:when test="self::para[following-sibling::*[1][self::fig]]">
          <xsl:variable name="para-head" select="."/>
          <xsl:for-each-group select="subsequence(current-group(), 2)" group-adjacent="boolean(self::fig)">
          <xsl:choose>
          <xsl:when test="position() = 1">
          <xsl:element name="{name($para-head)}" namespace="{namespace-uri($para-head)}">
          <xsl:apply-templates select="$para-head/node(), current-group()"/>
          </xsl:element>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:when>
          <xsl:otherwise>
          <xsl:apply-templates select="current-group()"/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:for-each-group>
          </xsl:copy>
          </xsl:template>

          </xsl:transform>


          http://xsltransform.hikmatu.com/bFDb2BN



          On the other hand, I am not sure whether an attempt not using xsl:for-each-group but rather a template matching para[following-sibling::*[1][self::fig]] and then consuming following sibling figs (which can be done easily in XSLT 3 with xsl:iterate) is not more compact:



          <xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
          xmlns:xs="http://www.w3.org/2001/XMLSchema"
          exclude-result-prefixes="#all"
          version="3.0">

          <xsl:mode on-no-match="shallow-copy"/>

          <xsl:output method="xml" indent="yes"/>
          <xsl:strip-space elements="*"/>

          <xsl:template match="para[following-sibling::*[1][self::fig]]">
          <xsl:copy>
          <xsl:apply-templates select="@*, node()"/>
          <xsl:iterate select="following-sibling::*">
          <xsl:choose>
          <xsl:when test="self::fig">
          <xsl:copy-of select="."/>
          </xsl:when>
          <xsl:otherwise>
          <xsl:break/>
          </xsl:otherwise>
          </xsl:choose>
          </xsl:iterate>
          </xsl:copy>
          </xsl:template>

          <xsl:template match="fig[preceding-sibling::*[not(self::fig)][1][self::para]]"/>

          </xsl:stylesheet>


          https://xsltfiddle.liberty-development.net/6qVRKxh/3







          share|improve this answer














          share|improve this answer



          share|improve this answer








          edited Nov 28 '18 at 20:54

























          answered Nov 28 '18 at 18:24









          Martin HonnenMartin Honnen

          113k66279




          113k66279













          • Thank you for your quick response - much appreciated! I cannot get this to work in XMLSpy 2015, but I'm guessing it'll work better with Saxon. I'm trying to understand some parts of your solution; first of all, won't the select in the first for-each-group be relative to the context node, which will be a para or a fig?

            – Zug_Bug
            Nov 28 '18 at 19:35













          • The template matches on the parent element of the para and fig with *[para and fig], so the for-each-group select="*" selects all child elements of the parent. In your case the parent is a first_lvl element, I was not sure whether that matters or is known, of course for the sample shown you could as well match explicitly on match="first_lvl".

            – Martin Honnen
            Nov 28 '18 at 20:03











          • As for XSLT 2 and XML Spy, see the edit, I had used some other features not supported in that language (tail) and had not correctly implemented the xsl:copy select with xsl:element, now corrected, at least now works with an XSLT 2 processor although I don't have XMLSpy 2015 to test that particular processor.

            – Martin Honnen
            Nov 28 '18 at 20:10



















          • Thank you for your quick response - much appreciated! I cannot get this to work in XMLSpy 2015, but I'm guessing it'll work better with Saxon. I'm trying to understand some parts of your solution; first of all, won't the select in the first for-each-group be relative to the context node, which will be a para or a fig?

            – Zug_Bug
            Nov 28 '18 at 19:35













          • The template matches on the parent element of the para and fig with *[para and fig], so the for-each-group select="*" selects all child elements of the parent. In your case the parent is a first_lvl element, I was not sure whether that matters or is known, of course for the sample shown you could as well match explicitly on match="first_lvl".

            – Martin Honnen
            Nov 28 '18 at 20:03











          • As for XSLT 2 and XML Spy, see the edit, I had used some other features not supported in that language (tail) and had not correctly implemented the xsl:copy select with xsl:element, now corrected, at least now works with an XSLT 2 processor although I don't have XMLSpy 2015 to test that particular processor.

            – Martin Honnen
            Nov 28 '18 at 20:10

















          Thank you for your quick response - much appreciated! I cannot get this to work in XMLSpy 2015, but I'm guessing it'll work better with Saxon. I'm trying to understand some parts of your solution; first of all, won't the select in the first for-each-group be relative to the context node, which will be a para or a fig?

          – Zug_Bug
          Nov 28 '18 at 19:35







          Thank you for your quick response - much appreciated! I cannot get this to work in XMLSpy 2015, but I'm guessing it'll work better with Saxon. I'm trying to understand some parts of your solution; first of all, won't the select in the first for-each-group be relative to the context node, which will be a para or a fig?

          – Zug_Bug
          Nov 28 '18 at 19:35















          The template matches on the parent element of the para and fig with *[para and fig], so the for-each-group select="*" selects all child elements of the parent. In your case the parent is a first_lvl element, I was not sure whether that matters or is known, of course for the sample shown you could as well match explicitly on match="first_lvl".

          – Martin Honnen
          Nov 28 '18 at 20:03





          The template matches on the parent element of the para and fig with *[para and fig], so the for-each-group select="*" selects all child elements of the parent. In your case the parent is a first_lvl element, I was not sure whether that matters or is known, of course for the sample shown you could as well match explicitly on match="first_lvl".

          – Martin Honnen
          Nov 28 '18 at 20:03













          As for XSLT 2 and XML Spy, see the edit, I had used some other features not supported in that language (tail) and had not correctly implemented the xsl:copy select with xsl:element, now corrected, at least now works with an XSLT 2 processor although I don't have XMLSpy 2015 to test that particular processor.

          – Martin Honnen
          Nov 28 '18 at 20:10





          As for XSLT 2 and XML Spy, see the edit, I had used some other features not supported in that language (tail) and had not correctly implemented the xsl:copy select with xsl:element, now corrected, at least now works with an XSLT 2 processor although I don't have XMLSpy 2015 to test that particular processor.

          – Martin Honnen
          Nov 28 '18 at 20:10




















          draft saved

          draft discarded




















































          Thanks for contributing an answer to Stack Overflow!


          • Please be sure to answer the question. Provide details and share your research!

          But avoid



          • Asking for help, clarification, or responding to other answers.

          • Making statements based on opinion; back them up with references or personal experience.


          To learn more, see our tips on writing great answers.




          draft saved


          draft discarded














          StackExchange.ready(
          function () {
          StackExchange.openid.initPostLogin('.new-post-login', 'https%3a%2f%2fstackoverflow.com%2fquestions%2f53525474%2fsome-help-on-how-groupings-actually-work-would-be-much-appreciated%23new-answer', 'question_page');
          }
          );

          Post as a guest















          Required, but never shown





















































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown

































          Required, but never shown














          Required, but never shown












          Required, but never shown







          Required, but never shown







          Popular posts from this blog

          A CLEAN and SIMPLE way to add appendices to Table of Contents and bookmarks

          Calculate evaluation metrics using cross_val_predict sklearn

          Insert data from modal to MySQL (multiple modal on website)