Punctuation in XPath, part 1: dot (".")

by Evan Lenz

When you write XQuery, a large subset of what you're writing is actually XPath 2.0. In fact, XQuery 1.0 is defined formally as an extension to, or superset of, XPath 2.0.

In this series of blog posts, I'm going to talk about the "punctuation" of XPath—all the little marks and what they mean: dot (.), slash (/), slash-slash (//), at-sign (@), dot-dot (..), etc. Since XPath is the common sub-language of both XQuery and XSLT, everything in this series applies both to XQuery and XSLT.

The dot, or period, character (".") in XPath is called the "context item expression" because it refers to the context item. This could be a node (such as an element, attribute, or text node) or an atomic value (such as a string, number, or boolean). When it's a node, it's also called the context node. Try typing "." at the top of your XQuery expression in CQ. What happens?

This is what I get: [1.0-ml] XDMP-CONTEXT: (err:XPDY0002) Expression depends on the context where none is defined:

[1.0-ml] XDMP-CONTEXT: (err:XPDY0002) Expression depends on the context where none is defined

If we look up the part in parentheses ("XPDY0002"), we'll see that this is the standard message you get when you try to access the context item when none is defined. That's because the context item is not defined at the top level in XQuery.

When "." doesn't yield an error, it returns an item (node or atomic value). Interestingly, those are the only two possibilities: a sequence of one item or an error. Dot (".") will never return an empty sequence or a sequence of more than one item.

In XQuery, by default, the context item (".") is undefined at the top level of expressions (and MarkLogic's implementation does not override that default). However, it's different in XSLT, which has the concept of a current node. The current node, when defined, is what determines the context item at the top level of expressions. XSLT normally assumes the presence of a single source document ("source tree"), whose document node becomes the initial context node. So the normal case in XSLT is the opposite of XQuery: a context item is defined at the top level of expressions. Try running this stylesheet (by copying and pasting the following code into CQ):

xdmp:xslt-eval(
<xsl:stylesheet version="2.0" 
  xmlns:xsl="http://www.w3.org/1999/XSL/Transform">
 
  <xsl:template match="/">
    <xsl:copy-of select="."/> 
  </xsl:template>
 
</xsl:stylesheet>,
document{<my-doc/>})

The result is not an error in this case but instead the source document (<my-doc/>) is copied to the result. That's because the current node is the source tree's document node (which you can tell from the match="/" part).

CQ window showing result of query

So when else is "." defined (besides at the top level of expressions in XSLT)? There are two cases:

  • inside a predicate (using "[...]")
  • inside a path expression step (to the right of "/")

We'll look at predicates first. Run this expression in CQ:

(1 to 100)[. mod 2 eq 0]

The predicate filters the sequence to its left, yielding only those items for which the predicate expression ". mod 2 eq 0" returns true. In this case, it yields all the even numbers between 1 and 100 (2, 4, 6, 8, ...). In each case, the "." expression refers to the context item. It gets evaluated exactly 100 times—once for each of the items in the sequence (1 to 100). First, "." returns 1, then 2, then 3, etc.

A predicate can also be applied to a sequence of nodes, or even a heterogeneous sequence of nodes and atomic values. Run this expression in CQ:

(<foo/>, 5, "a string",<bar>Hello</bar>)[string(.) eq "Hello"]

In this case, the predicate gets evaluated four times, and "." refers respectively to <foo/>, 5, "a string", and <bar>Hello</bar>. It filters out every item whose value when converted to a string does not equal "Hello". Thus, the result contains just one item: <bar>Hello</bar>.

The context item is also defined whenever a "/" is used in an expression. Let's say we want to take a sequence of nodes and get the value of each one, converted to an upper-case string. For the sake of convenience, let's use a bit of XQuery to define a global variable, giving us a document to work with. Start by entering this into CQ:

declare variable $doc :=   
  <doc>     
    <msg>Hello</msg>     
    <msg>Good-bye</msg>   
  </doc>;

A natural way to get what we want would be to use a "for" expression. Add this to the text box in CQ and execute the query:

for $msg in $doc/msg return upper-case($msg)

Sure enough, that gives us what we want:

"HELLO", "GOOD-BYE"

But we could also just use a "/" along with ".". Replace the "for" expression with the following and run the query again:

$doc/msg/upper-case(.)

In this case, rather than explicitly binding a named variable ($msg), we implicitly bind the context item. "." is evaluated once for each of the items in the sequence returned by $doc/msg. So it's evaluated twice (returning a <msg> element both times) and converted to an upper-case string using the fn:upper-case() function.

One peculiar aspect of the "/" operator is that it can only apply to node sequences. Whereas it's fine if we write this:

for $msg in ("Hello","Good-bye") return upper-case($msg)

We unfortunately can't write:

("Hello","Good-bye")/upper-case(.)

I'll touch on this again in part 2 of this series on the "/" operator.

One final bit of trivia around "." is that in XPath 1.0 (and XSLT 1.0), "." was just syntax sugar for "self::node()". And that's still what it means—at least when the context item is a node. But XPath 2.0 (and XQuery) introduced the ability to have sequences of values, not just nodes. So in XPath 2.0, "." is no longer a shorthand for something else. It's now a primitive of the language—the "context item expression."

Comments

  • It's misleading to say the context item is undefined at the top level in XQuery. It may or may not be defined/undefined, depending on the processor and the API. If you use XQJ, for example, it provides a way to set the context item for the top-level XQuery expression (though it doesn't require you to do so).
    • Hi Michael, of course you're quite right. I've changed the sentence so it's less misleading: <blockquote>In XQuery, by default, the <a href="http://www.w3.org/TR/xquery/#id-xq-evaluation-context-components">context item (".") is undefined</a> at the top level of expressions (and MarkLogic's implementation does not override that default).</blockquote>
  • Perhaps worth mentioning the XSLT function 'current()'? (http://www.w3.org/TR/xslt20/#function-current). For those who are unfamiliar with it, briefly: it refers to the matched or selected node or item from the nearest xsl:template or xsl:for-each(-group). Can be very usefull within predicates in which the period has a different meaning than this current() function.  
    • Thanks, Geert. The current() function is definitely worth pointing out. Here's an example of using the current() function to join between an element in another document and the current one: &lt;xsl:template match="person">   &lt;xsl:value-of select="doc('titles.xml')/titles/title[@id eq current()/@id]"/> &lt;/xsl:template> In the above case, current() binds to the &lt;person> element (the current node, which is the context node at the top-level of the expression). Without current(), you'd have to first store the current node in a variable and refer to it that way: &lt;xsl:variable name="person" select="."/> &lt;xsl:value-of select="doc('titles.xml')/titles/title[@id eq $person/@id]"/>