tags:

views:

324

answers:

2

I support a web site which generates content XML that is then translated into web pages using XSLT. I have been asked to create a new stylesheet which will transform the output of the "archive" page into Atom for syndication. The problem I'm running into is that the archive page contains a rather large number of items — 142 and counting — and the feed should never have more than thirty items.

Currently, the output from the archive page looks something like this:

<archive>
    <year>
        <month>
            <day>
            <day>
            ...
        </month>

        ...
    </year>

    ...
</archive>

The year and month tags are used by the HTML transform but are completely irrelevant for an Atom feed. I had hoped that using the position() function with the descendant axis would work (//day[position()>last()-30]), but this selects the last 30 days of each month, which isn't at all what I need. :-)

Is there a way to do this with XSLT or XPath? Having to modify the XML generator to add, say, a feed="true" attribute to the last thirty days seems like a pretty nasty kludge.

+4  A: 

position()/last() returns position/last position within the current context, so when the navigator is positioned in one <month>, position() will return <day> within that month, and last() will return last <day> within that month, but i guess you know that.

Therefore, what you could do is flatten all <day>'s in an array and put in a variable, prior to selecting just like you did before.

<xsl:variable name="days" select="//day"/>
<xsl:apply-templates select="$days[position()>last()-30]" />
baretta
Perfect! Thank you!
Ben Blank
I think that's pretty clever. Is there a lot of call for that technique?
Rob Kennedy
Sorry, but i am not sure what you mean by a lot of call? If you mean is it a commonly known technique, i don't think so. Just a creative workaround!!! :)
baretta
Just found out you can do this without creating a variable by not using the `//` shortcut. See my answer.
Ben Blank
A: 

Browsing through the XSLT spec today, I found a note which explains why // behaves this way:

// is short for /descendant-or-self::node()/. For example, //para is short for /descendant-or-self::node()/child::para and so will select any para element in the document (even a para element that is a document element will be selected by //para since the document element node is a child of the root node); div//para is short for div/descendant-or-self::node()/child::para and so will select all para descendants of div children.

NOTE: The location path //para[1] does not mean the same as the location path /descendant::para[1]. The latter selects the first descendant para element; the former selects all descendant para elements that are the first para children of their parents.

In other words, when using //, the position() is calculated along the child axis, not the descendant-or-self axis. Specifying descendant or descendant-or-self allows you to get the first/last n nodes as you'd expect:

<xsl:apply-templates select="descendant::day[position()>last()-30]"/>
Ben Blank