views:

362

answers:

5

Hi, I have the following working 100% correctly. However to satisfy my curiosity... is there a way to achieve the same without declaring the currentID variable? Is there some way to reference it from within the Xpath "test" condition?

The xpath query in the condition must refer to 2 @id attributes to see if they match.

  • the 'current' @id
  • each 'ancestor' @id

Here's the code:

<xsl:variable name="currentID" select="@id" />
<xsl:attribute name="class">
<xsl:if test="count($currentPage/ancestor::node [@id = $currentID])&gt;0">descendant-selected </xsl:if>
</xsl:attribute>
A: 

You could simply do:

<xsl:if test="count($currentPage[ancestor::node/@id = @id])&gt;0">descendant-selected </xsl:if>
Tim
hmm nope, both @id are within the scope of $currentPage, but one of them should NOT be within that scope.
Myster
+1  A: 

Tim got me thinking.... I think I was over complicating things, and I tried the following which works.

<xsl:if test="@id = $currentPage/ancestor::node/@id">descendant-selected </xsl:if>

XSLT seems happy comparing an attribute with a selection of attributes, and evaluating true if any of them match? if anyone has a better explanation of why this works or something better (more succinct) then put it down.

Myster
Yes, the `=` operator, when used against a node-set, compares all nodes and returns true only if all are equal. Note that the `!=` does not do that, it compares against the first node of the set only.
Tomalak
Not exactly. = returns true if there is one node in the node-set such that the result of performing the comparison is true. != works like this as well.
Erlock
This is called "existential quantification". It applies to =,!=,>,<,>=,<=. It returns true if any left/right pair returns true. XPath 2.0 adds additional syntax to handle this more explicitly. Here's an equivalent expression: some $id in $currentPage/ancestor::node/@id satisfies $id eq @id. If you want to perform "universal quantification" (which is what Tomalak mistakenly described above): every $id in $currentPage/ancestor::node/@id satisfies $id eq @id. These generalize to arbitrary tests, which is nice. Finally, if you want to only compare two single items, use "eq" instead of "=".
Evan Lenz
+2  A: 

Use current() to refer to the current node processed by the template:

<xsl:if test="count($currentPage/ancestor::node [@id = current()/@id])&gt;0">
Erlock
+3  A: 

Since you select the $currentID from the context node:

<xsl:variable name="currentID" select="@id" />

you can use the current() function, which always refers to the XSLT context node:

<xsl:attribute name="class">
  <xsl:if test="count($currentPage/ancestor::node[@id = current()/@id) &gt; 0]">
    <xsl:text>descendant-selected </xsl:text>
  </xsl:if>
</xsl:attribute>

This way you don't need a variable.

A few other notes:

  • I recommend using <xsl:text> like shown above. This gives you more freedom to format your code and avoid overly long lines.
  • You don't need to do a count() > 0, simply selecting the nodes is sufficient. If none exist, the empty node-set is returned. It always evaluates to false, while non-empty node-sets always evaluate to true.

If you refer to nodes by @id regularly in your XSL stylesheet, an <xsl:key> would become beneficial:

<xsl:key name="kNodeById" match="node" use="@id" />

<!-- ... -->

<xsl:attribute name="class">
  <xsl:if test="key('kNodeById', @id)">
    <xsl:text>descendant-selected </xsl:text>
  </xsl:if>
</xsl:attribute>

The above does not need current() since outside of an XPath predicate, the context is unchanged. Also, I don't count() the nodes, since this is redundant (as explained).

Tomalak
I think you mean something like "the `current()` function, which always refers to the current node even when the current node is not the context node": http://www.w3.org/TR/1999/REC-xslt-19991116#misc-func
NickFitz
Yes, that's what I mean. This is why I said "XSLT context node", as opposed to "XPath context node". I wanted to avoid saying "the `current()` function, that always refers to the current node" because this sounds silly and does not explain a lot.
Tomalak
Cool (you're missing a ] in your first example) but that's the answer to my question. Does use of the key tag improve performance or just readability?
Myster
Both. It improves performance because it creates an associative array and it is shorter, too.
Tomalak
+1  A: 

As it has already become clear, referring to the "outer scope" was not an issue, since you could do a direct comparison using the "=" operator. However, there are some cases where you do need current() and more besides where even current() doesn't cut it (because you need to "join" between more than just two contexts). In those cases, XPath 2.0's "for" expressions are indispensable.

Evan Lenz