Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Uniformly process grouped sibling elements

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.

like image 256
kostix Avatar asked Feb 21 '26 12:02

kostix


1 Answers

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".

like image 161
michael.hor257k Avatar answered Feb 23 '26 08:02

michael.hor257k



Donate For Us

If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!