Reputation: 56604
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.
Upvotes: 14
Views: 14441
Reputation: 56604
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 anypara
element in the document (even apara
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 fordiv/descendant-or-self::node()/child::para
and so will select allpara
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 descendantpara
element; the former selects all descendantpara
elements that are the firstpara
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]"/>
Upvotes: 4
Reputation: 7595
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]" />
Upvotes: 14