3

Given the following XML document

<root>
  <a pos="0" total="2"/>
  <a pos="1" total="2"/>

  <a pos="0" total="3"/>
  <a pos="1" total="3"/>
  <a pos="2" total="3"/>

  <a pos="0" total="4"/>
  <a pos="1" total="4"/>
  <a pos="2" total="4"/>
  <a pos="3" total="4"/>
</root>

I need to translate it to

<root>
  <group>
    <a pos="0" total="2"/>
    <a pos="1" total="2"/>
  </group>
  <group>
    <a pos="0" total="3"/>
    <a pos="1" total="3"/>
    <a pos="2" total="3"/>
  </group>
  <group>
    <a pos="0" total="4"/>
    <a pos="1" total="4"/>
    <a pos="2" total="4"/>
    <a pos="3" total="4"/>
  </group>
</root>

using an XSLT 1.0 stylesheet.

That is, each <a> element with the @pos attribute of 0 in the document implicitly starts a group consisting of it and the @total-1 following <a> elements. To explain this in other words, @pos denotes a 0-based index (position) of the element in the group of @total adjacent elements.

I have come up with the following stylesheet, which works:

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform">

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

    <xsl:template match="/">
        <xsl:apply-templates select="root" />
    </xsl:template>

    <xsl:template match="root">
        <xsl:apply-templates select="a[@pos=0]" mode="leader"/>
    </xsl:template>

    <xsl:template match="a" mode="leader">
        <group>
            <xsl:apply-templates select="." />
            <xsl:apply-templates select="following-sibling::a[position() &lt;= current()/@total - 1]" />
        </group>
    </xsl:template>

    <xsl:template match="a">
         <xsl:copy-of select="." />
    </xsl:template>

</xsl:stylesheet>

The problem I have with my solution is that it makes those a[@pos=0] elements "special": to further process each <a> element in a prospective group, I have to separately apply the appropriate template first to the "group leader" element and then to the rest of the elements in the group.

In other words I would very much like to have something like (incorrect)

    <xsl:template match="a" mode="leader">
        <group>
            <xsl:apply-templates select=". and following-sibling::a[position() &lt;= current()/@total - 1]" />
        </group>
    </xsl:template>

which would apply my <xsl:template match="a"> template to all the elements in the group in one go. (To rephrase what I've attempted to spell in the select expression: "select the context element and its following sibling elements matching …".)

Is there a way to have what I want with XSLT 1.0 without resorting to hacks like variables and exslt:node-set()? May be there's a better way to do such grouping based on element counts than the one I have come up with (which inherently makes the first element in each group special)?


I admit the question's title is rather weak but I failed to come up with a succint one which correctly reflect the essense of my question.

kostix
  • 51,517
  • 14
  • 93
  • 176

1 Answers1

1

You could do:

<xsl:apply-templates select=". | following-sibling::a[position() &lt;= current()/@total - 1]" />

P.S. Using variables or the node-set() function does not qualify as a "hack".

michael.hor257k
  • 113,275
  • 6
  • 33
  • 51
  • Thank you very much -- silly me ;-) Regarding your comment: I'm trying to write my stylesheets in as functional approach as possible (somehow having had absorbed this is considered "best practices"). In my experience, using a variable or calling `node-set()` (which is not in XSLT 1.0 core) almost always has been an indication of some workarond which could have been reworked in "plain" XSLT facilities. So yes, you're correct and supposedly I just picked bad wording to try to say what I meant. – kostix Nov 23 '16 at 10:34
  • 1
    I disagree with your assessment of `node-set()`. It's a workaround for the short-sightedness of the XSLT 1.0 specification. In many situations, avoiding it will result in a horrible contraption. There is a good reason why it's so widely supported. And of course, variables are an intrinsic and necessary part of the language. – michael.hor257k Nov 23 '16 at 10:58