Rafe
Rafe

Reputation: 19

XPath using position() in a nested context does not produce the desired result

Using XPath 1, I'm trying to return a node set of items within a certain range which have distinct dept_number values within them. The range which these items are in is dynamic. The document is structured like the following:

<data>
    <items>
        <item>
            <dept_number>1</dept_number>
        </item>
        <item>
            <dept_number>1</dept_number>
        </item>
        <item>
            <dept_number>2</dept_number>
        </item>
        <item>
            <dept_number>2</dept_number>
        </item>
        <item>
            <dept_number>3</dept_number>
        </item>
        <item>
            <dept_number>4</dept_number>
        </item>
        <item>
            <dept_number>4</dept_number>
        </item>
    </items>
</data>

The best way I could think of doing this is like the following:

/data/items/item[not(dept_number = preceding-sibling::item[position() > $min]/dept_number) and position() > $min and position() <= $max]/dept_number

where $min is the lower bound of the range and $max is the upper bound. This is not working. For instance, if I use

/data/items/*[name() = 'item' and not(dept_number = preceding-sibling::*[position() > 1]/dept_number) and position() > 1 and position() <= 5]/dept_number

it returns

<dept_number>1</dept_number>
<dept_number>2</dept_number>
<dept_number>2</dept_number>
<dept_number>3</dept_number>

which is not a set of distinct values. What I would expect is

<dept_number>1</dept_number>
<dept_number>2</dept_number>
<dept_number>3</dept_number>

I believe the issue is with using position() within a nested context (i.e. /data/items/item[... preceding-sibling::item[position() > $min] ...]. It seems to be using the result you would get from position() when used in the outer context, not the inner one.

Within xslt, I have also tried to use keys as such:

<xsl:key name="dept_number_key" match="/data/items/item" use="dept_number"/>
<xsl:for-each select="/data/items/item[generate-id() = generate-id(key('dept_number_key', dept_number)[1]) and position() $gt; $min-pos and position() &lt;= $max-pos]">
</xsl:for-each>

but this still does not work.

Any help would be much appreciated.

Edit: The range I am referring to is not a range of dept_number values, but a range of <item> tags within <items>. For instance, if the range was 3 to 5 I would want to get back the third, fourth and fifth <item> tag within <items>.

Upvotes: 0

Views: 277

Answers (1)

michael.hor257k
michael.hor257k

Reputation: 116959

--- edited in response to clarification ---

I would start by copying the items in range to a new "document", so that you can later use Muenchian grouping on them to get only distinct values. Without this, the grouping can fail if, for example, item #3 is not the first item with the given dept_numbervalue.

Consider the following stylesheet:

XSLT 1.0 + EXSLT node-set() function

<xsl:stylesheet version="1.0" 
xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
xmlns:exsl="http://exslt.org/common"
extension-element-prefixes="exsl">
<xsl:output method="xml" version="1.0" encoding="UTF-8" indent="yes"/>

<xsl:variable name="min-pos" select="3" />
<xsl:variable name="max-pos" select="5" />

<xsl:key name="dept_number_key" match="item" use="dept_number" />

<xsl:template match="/data">
    <xsl:variable name="items-in-range">
        <xsl:copy-of select="items/item[$min-pos &lt;= position() and position() &lt;= $max-pos]"/>
    </xsl:variable>
    <root>
        <xsl:copy-of select="exsl:node-set($items-in-range)/item[generate-id() = generate-id(key('dept_number_key', dept_number)[1])]/dept_number"/>
    </root>
</xsl:template>

</xsl:stylesheet>

Note that some XSLT 1.0 processors support the EXSLT set:distinct() extension function. This could shorten the code here.

Upvotes: 1

Related Questions