I often have the problem of wanting to select the previous 'cousin' to a node.
For example:
<root>
<level1>
<level2 id="1"/>
<level2 id="2"/>
<level2 id="3"/>
<level2 id="4"/>
</level1>
<level1>
<level2 id="5">
<level2 id="9"/>
</level2>
<level2 id="6"/>
<level2 id="7"/>
<level2 id="8"/>
</level1>
</root>
If the context node is
/root/level1/level2[@id='6']
I don't want "id=9"; I want "id=5".
Similarly if the context node is
/root/level1/level2[@id='5']
I want "id=4".
So....preceding-sibling does work in the former case but not the latter. preceding selects in the latter case but not the former.
(And there are probably a multitude of other cases.)
So my current solution is this (using id=6 as the context node...I could put it in a function, but this should be clear enough)
(/root/level1/level2[@id='6']/preceding::level2 intersect /root/level1/level2)[last()]
this does work, but seems..convoluted, and possibly inefficient...am I missing a simpler solution?
(I'm current using XPath 3.1 in XSLT 3, but I think its a general question.)
(I've limited this to XPath 2.0+, but I'll probably open up the question to XPath 1.0, as that seems even worse because intersect doesn't exist...but I'll do that in another post.)
For XSLT 1.0 this works:
/root/level1/level2[@id='6']/preceding::level2[parent::level1[parent::root]][1]
and is debatably a better solution.
What I'd like to do is
/root/level1/level2[@id='6']/preceding::(root/level/level2)[1]
and have that be interpreted as a match on the path root/level/level2,
but I don't think that's valid?
there are some really good answer here, I want completely clear what the question was, apologies.
I'm looking for all 'nth cousins' where 0th cousins are siblings.
irritatingly the elegant solutions just get 0th and 1st, and the more complex solutions are 'correct' but quite clunky (no offense).
For the moment the most elegant solutions I have is to (as a programmer) take the absolute simple path to your current node, in this case
let $this = ....
(root/level1/level2)[. << $this][last()]
the clever solutions do this programatically (one using depth, the other the path), and are a bit mind boggling to me.
the elegant solutions, use the '<<' operator, but only match 0th and 1st siblings (which was a reasonable interpretation).
the XSLT 1.0 solution similarly only does 0th and 1st, and I think I'd actually use the parent:: solution above, and match the full path to the root to ensure all cousins are matched.
(this of course presumes you know the full path, though martin honnens solution works in general).
If I understand your requirements correctly, XPath 1.0 is all you need.
Given your input XML, this XSLT 2.0 (for the test program – the key XPath is 1.0),
produces these test results,
which covers the two cases of concern and produces reasonable results for all starting
level2elements as well.