tags:

views:

280

answers:

3

Within an XSL sheet, I have a nodeset in a variable, $context.

How do I properly query an attribute of the topmost node in $context? Obviously, if $context was the current node, I would just write "@attname", but it is not. Currently I do $context/../@attname which is doesn't look great to me.

EDIT

Ok, the data. Here is what I see in the VS.

In case you wonder how I got this strange thing: I UNION'ed a node with it's subnodes and an attribute selected out from a different node:

<xsl:with-param name="context" select=". | ../@res" />.

I'm not completely aware what results from this, but I can see that it works. Highlighted is the attribute I'm interested in.

Maybe that creates an attribute attached to nothing, if that makes sence at all :|

+1  A: 
$context/../@attname

does not make too much sense. You can't go "up" here, as this would bring you "outside of" $context.

If the node-set contains something like this (a single node)

<node attname="foo">
  <bar />
</node>

then:

$context/@attname

If it is like this (a list of nodes):

<node attname="foo">
  <bar />
</node>
<node attname="foo">
  <bar />
</node>

then:

$context[1]/@attname

All of this does not work if the variable contains a result tree fragment (RTF). In this case, you need to employ an extension function called node-set(). Most XSLT processors provide this function.


EDIT: Your variable holds a union of the current node and a naked attribute node from its parent:

<xsl:with-param name="context" select=". | ../@res" />

The result of a union will always be in document order, so even though you selected the attribute after the context node in the XPath, in the resulting node set it will come before - the parent of the context node is before the context node in document order.

You need $context[1] to grab the attribute node, and $context[2] to grab the other node.

I must say that this is some strange and probably unnecessary complicated use of variables. I'm sure there is a way to do this in a less painful fashion. For example you could do

 <xsl:with-param name="context" select="." />

and then use $context/../@res in the called template. That would be a lot more straight-forward than what you are trying now.

Also, if the <xsl:with-param> you show here is part of an <xsl:call-template>, you can drop that param entirely. When a template is called (instead of applied), then the context node does not change, passing it in is redundant.

Tomalak
It does make sense because first slash brings you one level down and then double-dot brings you onle level back. $context/@attname would refer to an attribute of <bar> in your example.
GSerg
it will refer to the attribute of `node` in his example. you go one down from the node/document **context** same as how `/` selects the document element by going one down from the document **context**.
Martijn Laarman
You can't "go up" if nodes in `$context` are newly created copies. If they are references to the original nodes (which is what you get if you e.g. just bind the result of any XPath expression to a variable), then `..` and friends will work just fine, and will traverse the tree those nodes (still) reside in.
Pavel Minaev
@GSerg: Unless you show what `$context` actually contains, we are forced to guess. Can you post what `<xsl:copy of select="$context" />` gives?
Tomalak
@Pavel Minaev: Naturally. I didn't think of this. (The OP does not seem to have that in mind, though.)
Tomalak
@GSerg: Edited my answer to accommodate the changed question.
Tomalak
Ah. Yes, I just wanted to avoid indexers. But then, I suppose, my existing syntax is basically using an index implicitly, so I'm safe to write "[1]" anyway?
GSerg
As I indicated, you should not rely on such things. It is hard to read and obscure. Be explicit, put only one node into the variable. I'm also not sure what you mean by "indexers" and why you want to avoid them.
Tomalak
@Tomalak, @GSerg, I think what he means by "indexers" is that he'd rather not depend on the order of the nodes. In this case the order is known, but it isn't always. GSerg, another option would be to use `$context[not(self::*)]`, which says "all nodes in $context that are not elements". Of course that requires that the attribute you're looking for is the only non-element. But I agree with Tomalak - use one of the more straightforward ways he suggested. E.g. pass `@res` alone as a parameter, and let the called template get `.` from the context, or pass it separately.
LarsH
A: 

In addition to tomalak's answer if you ever do need to propagate back up to the root you could try:

ancestor::*[not(..)]

Would love to hear of a situation where you might want/need this though.

Try it yourself by pasting:

//pet/ancestor::*[not(..)]

in this online Xpath test tool.

Martijn Laarman
Tomalak's answer was the first and most natural thing that I tried. It didn't work, $context/@attname returns an empty nodeset. Whereas $context/../@attname returns the attribute value. I do not know why. But I can clearly see that in both processor results and VS debugger.
GSerg
@GSerg: I have the suspicion that you might have a wrong understanding how XPath works. If I have a node `<node foo="bar">`, then the XPath to get the attribute is `node/@foo`. If that node is in a variable `$x`, then the XPath to get the attribute is `$x/@bar`.
Tomalak
You might well be very right. I just look at results VS gives me and try to make conclusions.
GSerg
@GSerg: Ironocally, the screenshot you posted says `($context)[1]` as the expression for the highlighted node right there. ;-)
Tomalak
Yes it does, I just didn't want to use indexing in case order ever changes :)
GSerg
@GSerg: Ah, I see. The order will not change, but the point is that you should not use a variable like this at all.
Tomalak
A: 

As you have <xsl:with-param name="context" select=". | ../@res" /> the 'res' attribute is part of the node-set (XPath 1.0) or sequence (XPath 2.0) the variable named 'context' is bound to. With XPath 2.0 you could use $context/self::attribute(res) but XPath 1.0 has no comparable expression so what you have already ($context[1] or $context/../@res) is all you can do in my view.

Martin Honnen