XPath and XPointer/XPointer Beyond XPath
(Initial conversion from Docbook)
(Initial conversion from Docbook)
|Line 282:||Line 282:|
| <tt>mag:</tt> namespace location || The namespace location itself || 0 || 28 (length of string-value "http://www.example.com/magml")
| <tt>mag:</tt> namespace location || The namespace location itself || 0 || 28 (length of string-value "http://www.example.com/magml")
| Root || The root location itself || 0 || 2 (number of children of root location — don't forget the PI
| Root || The root location itself || 0 || 2 (number of children of root location — don't forget the PI)
| <tt>xml-stylesheet</tt> PI || Root (parent of the PI) || 0 (the PI has no preceding siblings) || 1 (index of the start point, plus 1)
| <tt>xml-stylesheet</tt> PI || Root (parent of the PI) || 0 (the PI has no preceding siblings) || 1 (index of the start point, plus 1)
Using XPath, in conjunction with XPointer, provides a wide (seemingly infinite) variety of ways to locate XML content. But it stops short of being able to "locate" everything. This chapter covers XPointer extensions to XPath's already rich facilities, extensions that plug the gap between what XPath does and what users might expect of a full-fledged "point to some XML-based content" standard.
In this chapter, when I refer to "XPointer" or "the XPointer spec," I'm referring to the XPointer xpointer( ) Scheme Working Draft dated July 10, 2002.
Why Extend XPath?
After you've worked with XPath for a while — especially in XSLT — you may start to get a little cocky. It seems impossible that something in an XML document might be unlocatable. From everything in the document down to individual PIs, comments, even individual characters (in the sense of using functions such as substring( ) and translate( )) — what's left? The reason your cockiness may be unjustified is that you've overlooked a small but disproportionately significant class of activities for which XPointer would be useful, but for which XPath provides absolutely no support.
Consider XPointer's unintelligent counterpart in XHTML: simple fragment identifiers that use named anchors; then consider the activity XHTML supports: web browsing. The Web is already driven by content authors and browser capabilities, no? What could possibly be missing?
What's missing is the third party in a web-based transaction: the user. The web site visitor. The human sitting at her PC, mouse, or other pointing device probably at the ready. Ready, in short, to select content in ways that cannot be anticipated by a document author or browser vendor.
Now ask yourself: How do I construct an XPath-based XPointer that corresponds to the selected text? To answer this question, you'll need to examine the XHTML code behind the page, the relevant portion of which may look something like the following:
<h1>Why extend XPath?</h1> <!-- Intro paragraph --> <p>After you've worked with XPath for a while-especially in XSLT-you start to get a little cocky. It seems impossible that something in an XML document might be <em>un</em>locatable.</p>
You can construct such an XPointer, but it will be extremely ad hoc and ungainly, involving, first, acquiring all text in the document and then sub-stringing it from the "X" in the level-1 heading through the "d" in "worked." And somehow you'd need to grab that comment, too, and ensure — to preserve the source's integrity — that the comment was somehow placed where it belonged: smack in the middle of the two partial text nodes. The resulting mutant XPointer might look something like this:
xpointer(substring(//h1, 12, 6) | //comment( ) | substring(//p, 1, 19))
And you'd really be frustrated when the XPointer processor fails (because the result of this XPointer is a simple string).
XPointer would be much more natural and easy-to-use if there were an XPointer analog to the real-world practice of selecting text with a pointing device: starting at one point in the document and continuing over a contiguous range of content to a second point. That's the function of XPointer's handful of extensions to XPath.
Points and Ranges
I mentioned these extensions to XPath's basic node types first in Chapter 7 (elements, attributes, and so on). Now let's look at them in detail.
A point is simply a location in a document — a location between two characters of interest. Like a point in plane geometry, a point in XPointer has no dimension at all: a single point "contains" nothing at all, not even a single character. There's also a point at the start of the string-value and at its end (and, as you will see, before and after each node in the document).
Note that points are found not only in text nodes, but in any string-value. An attribute's value, a comment, a PI, indeed the root node itself — all have string-values as well and are thus potential targets in which to locate points.
Each point has two properties, a container node and an index. The container node, obviously, is the point's parent — the context in which the point is located. The index is a positive integer ranging from 0 through the number of points (less 1) in that container node.
Consider the following fragment:
The storage element's string-value — its text node child — consists of four characters; within the text node, the interstice between each pair of characters (d and i, i and s, and s and k) is one point, and there's also a point before the d and after the k — a total of five points. The container node for the points (indexes 0 through 4) before and after each character is the text node; the container node for the two points (indexes 0 and 1) before and after the text node is the storage element. Figure 9-3 illustrates.
An important implication of Figure 9-3 is that the node tree as understood by XPath is not the same as the "location tree" as understood by XPointer (although naturally there are overlaps). See Figure 9-4.
As you can see, the storage element now has not just one child (the text node), but three children (the text node plus the points before and after it). Furthermore, while a text node can have no children from XPath's perspective, it does from XPointer's; the number of child points always equals the length of the text node, plus 1.
One effect of a point's being defined by its container and its index within that container is that there are no identical points in a document, at least within a given container. However, there will be points indistinguishable from one another. For instance, in the above example, point P0 within the storage element is indistinguishable from the "separate" point P0 within the text node.
This might seem disconcerting at first. It makes sense, though, when you remember that XPath, too, often provides several ways of locating a given "thing"; a given node might be visible along more than one axis, for example.
Node points versus character points
A given point can be classified as either a node point or a character point.
- A node point is any point in the document occurring between adjacent nodes; there's also a node point immediately before and after each child of a given node. In the above example, there's a node point immediately preceding and following the text node.
- A character point is any point in the document occurring between adjacent characters in a text node, before the first character in a text node or following the text node's last character. The storage element as described above has five character-point children.
To address a point with an XPointer, use the point( ) node (or location) type in the XPath expression used by a full XPointer, especially with a predicate. For example, assume the simple storage element we've been using so far is part of a larger document:
<media> <storage>microform</storage> <storage>CD-ROM</storage> <storage>DVD</storage> <storage>disk</storage> </media>
To locate the character point between the m and i in "microform" — that is, the second point within the first storage element — you could use the following:
While the numbering of the storage elements starts at 1, the numbering for the points is zero-based.
Points as "nodes"
Like other node types, the XPointer point node-type extension can be followed in an XPath expression by further location steps that shift the context up, down, or sideways in the document as a whole. These shifts in context — accomplished here as elsewhere using axes — behave a little differently for a point-type "node," however; Table 9-1 breaks it down for you.
Table 9-1. Axes and points
|Axes||Node-set locatable from a point|
|child, descendant, preceding-sibling, following-sibling, preceding, following, attribute, namespace||None (node-set is empty)|
|self, descendant-or-self||The point itself|
|parent||The point's container node|
|ancestor||The point's container node, that container's parent, and so on (up the tree to the root node)|
|ancestor-or-self||The point itself, its container node, and the container node's ancestors|
Unlike standard XPath node types, points do not have an expanded-name; their string-values are empty.
Points and general entities
Consider the following document:
<!DOCTYPE gadget [ <!ENTITY R "Ronco" > ]> <gadget> <name>Veg-O-Matic</name> <company>&R;</company> </gadget>
How many character points are there within the company element, and where are they?
There are six character points there — four between each pair of characters in the entity's replacement text "Ronco," and one point before and after the replacement text. There is no point, for instance, between the & and the R in the &R; entity reference itself. This is consistent with XPath, with XML, and for that matter with common sense: by the time an XPointer-aware application gets its hands on a document's content, the document has already been parsed — entity substitutions made, attribute values defaulted, and so on. In short, the entity reference itself might just as well not exist.
A range, as you might expect, is whatever lies in a given document between any two given points, not surprisingly referred to as the start and end points. The start and end points may be equal (in which case the range is called a collapsed range); if they are not equal, the end point must follow the start point, in document order.
A reminder: I'm using the term "document" loosely in the preceding sentence. In this sense, it comprises not only well-formed XML documents, but also non-well-formed external parsed entities. The spec asserts that the start and end point must be within the same document or external parsed entity; nonetheless, it's safe to assume that a document containing a reference to an external parsed entity may have ranges that overlap the "border" between document and entity. Consider the case of the following document:
<!DOCTYPE speechinfo [ <!ENTITY getty SYSTEM "gettysburg.xml"> ]> <speechinfo> <speaker>Abraham Lincoln</speaker> <text>&getty;</text> </speechinfo>
where the gettysburg.xml document contains the text (marked up or otherwise) of Lincoln's famous address.
A start point/end point for a legitimate range might be, respectively, the point just before the L in Lincoln (in the main document) and following the o in and seven years ago (in the external parsed entity). As I discussed at the end of the section about points, for all intents and purposes, an entity reference can be assumed to have been expanded by the time the XPointer application sees a document. What the spec really means by restricting the start and end point to the same document or external parsed entity is, for example, that you can't set the start point to just before the L in Lincoln and the end point in some other document (or in some unreferenced external parsed entity).
What can be in a range
There's one important restriction to the kinds of content a legitimate range may contain, a restriction based on the container nodes for the start and end points. If the container for one point is anything other than the root node, an element, or a text node, the two points must lie within the same node.
At the beginning of this chapter, following Figure 9-2, I showed some hypothetical source code behind a user's selection of text on a web page. The range in question looked like this:
XPath?</h1><!-- Intro paragraph --><p>After you've worked
Although it looks a little nonsensical (and certainly not well-formed!), this is a legitimate range. Both start and end points lie within element nodes (more precisely, within text nodes within element nodes), and they need not lie within the same node for the range to be valid. On the other hand, the boldfaced portion of the following fragment does not constitute a valid range:
XPath?</h1><!-- Intro paragraph --><p>After you've worked
The end point is still within a text node, true. But the start point lies within the comment. The only legitimate end point for such a start point would be a point within the same comment (and equal to or following the start point, of course).
Like points, ranges have their own node type, range( ) in this case, which may be referred to in the expression portion of a full XPointer. Therefore, the following is presumably a legitimate XPointer:
I say "presumably" because although syntactically correct — translated, this would mean something like "locate the fourth range in the document" — it's not practically useful. If a range is delimited by its start and end points, where does the range in question start and end? (The start point, obviously, is the one corresponding to index 0; it's the end point that's not clear.)
More likely, you'll use the range( ) node type with any of several XPointer functions in the predicate. You will see examples of this use in a moment.
Ranges as "nodes"
Like points, ranges have no expanded names. Unlike points, though, ranges do have string-values: "the characters that are in text nodes and that are between the start point and end point of the range."
So says the spec, anyhow. But this is a bit ambiguous, and in the absence of examples, we're left to conjecture what might be the string-value of a range such as the following:
<front_matter> <epigraph id="GIRA001"> <author born="1882-10-29" died="1944-01-31">Giraudoux, Jean</author> <source year="1942">The Apollo of Bellac</source> <text>When you see a woman who can go nowhere without a staff of admirers, it is not so much because they think she is beautiful, it is because she has told them they are handsome.</text> </epigraph> </front_matter>
The start point of this range immediately precedes the epigraph element's start tag; the end point follows the period at the end of the text element. A literal reading of the spec might seem to indicate that the string value of this range is:
GIRA0011882-10-291944-01-31Giradoux, Jean1942The Apollo of BellacWhen you see a woman who can [...] handsome.
That is, not only the text nodes but also the attribute values might seem to make up the string-value. After all, the latter are indeed "between the start point and end point," aren't they? Don't be deceived, though; this literal reading is too literal. The key word in the excerpt from the spec is "and" — that is, the range's string-value consists of those portions of all text nodes that lie between the start and end points. Thus, the true string-value of this range is:
Giradoux, JeanThe Apollo of BellacWhen you see a woman who can [...] handsome.
That said, there is a problem with the wording of this portion of the spec, in the phrase "text nodes." But text nodes exist only as children of elements. Given that a legitimate range in the above example might be, for instance, the 1882 portion of the born attribute's value, I think the spec's authors might have tinkered with the wording of the paragraph a little more.
The other XPath-related question we might ask about ranges is how they behave when included in a location step followed by others that use axes. This is easy: navigating through a document from a range-type "context node" is identical to navigating through it from the range's start point. Refer back to Table 9-1 for this information, remembering that only the start point "counts" when orienting yourself along an axis from a range.
An important concept in understanding ranges as described in the XPointer spec is that of covering ranges. As the name implies, a covering range is the range that spans the entirety of some location. (And remember that locations in XPointer are basically the same as nodes in XPath, extended to include points and ranges.) Covering ranges are defined in terms of the location type in question; this may involve implicit start and end points, with their respective container nodes and indexes. Table 9-2 summarizes.
Table 9-2. Covering ranges
|Location type||Container of start and end points||Covering range's start point index||Covering range's end point index||Notes|
|Range||—||—||—||Identical to the range itself|
|Point||—||—||—||Start and end points of covering range are identical: the point itself|
|Attribute and namespace||Attribute or namespace location itself||0||Length of attribute or namespace location's string-value|
|Root||Root location itself||0||Number of children of root location|
|All others||Parent of the location in question||Number of preceding siblings of the location||Index of the start point, plus 1|
Determining the covering range of a point or range location, obviously, is pretty trivial. (Note, by the way, that the covering range of a point is a collapsed range, as defined earlier in the chapter.) The covering ranges of other location types might be more easy to understand by way of an example. Here's a simple but complete document including at least one of all other location types:
<?xml-stylesheet type="text/xsl" href="maginfo.xsl"?> <mag:magazine id="NY" xmlns:mag="http://www.example.com/magml"> <mag:name>The New Yorker</mag:name> <!-- Update: Brown hasn't been the editor for years. --> <mag:editor>Brown, Tina</mag:editor> </mag:magazine>
Table 9-3 breaks down the covering ranges for various locations in this document.
Table 9-3. Covering range examples
|Location||Container of startand end points||Index of start point||Index of end point|
|id attribute||The id attribute itself||0||2 (length of string-value "NY")|
|mag: namespace location||The namespace location itself||0||28 (length of string-value "http://www.example.com/magml")|
|Root||The root location itself||0||2 (number of children of root location — don't forget the PI!)|
|xml-stylesheet PI||Root (parent of the PI)||0 (the PI has no preceding siblings)||1 (index of the start point, plus 1)|
|Comment||The mag:magazine element||1 (there's one preceding sibling of the comment, the mag:name element)||2 (index of the start point, plus 1)|
|Text node, "The New Yorker"||The mag:name element||0 (no preceding siblings)||1 (index of the start point, plus 1)|
|mag:editor element||The mag:magazine element||2 (mag:name element and the comment are preceding siblings)||3 (index of the start point, plus 1)|
If you refer back to the document's source code, you should see that all this follows not only the spec's definition of a covering range, but also a common-sense version of that definition. For example, within its container — the root — the xml:stylesheet PI is "covered" by a range that starts at point 0 (that is, the start point's index) through point 1 (the end point's index, immediately following the PI).
Remember that we're talking here about covering ranges. The PI's covering range isn't the only range possible for it; any number of ranges could be set within its string-value, from the whole thing (type="text/xsl" href="maginfo.xsl") down to individual characters — indeed, individual points — within that string-value.
XPointer Extensions to Document Order
The XPointer specification extends XPath's concept of document order to accommodate the point and range location types. The need to do so might not be obvious; after all, "document order" seems to be one of those terms describing a physical, unambiguous reality. What could be more unambiguous than that a bit of document content appears before or after another bit?
But XPointer points and ranges toss a big handful of mud into these formerly transparent waters, even in the simplest documents. Consider an example:
<employee emp_id="73519"> <start_date type="probationary">1979-03-12</start_date> <start_date type="full_perm">1979-09-12</start_date> </employee>
The possible locations in this document number in the dozens, from individual character and node points through all the various ranges, attribute and text locations, on up to the full root location. The problem in determining the "order" of these locations is that some XPointer locations can be found within others. It's like you're looking at a wooden crate, filled with freshly picked corn: does a kernel on this ear of corn come before or after the husk on the same ear? does a slat of wood on one side of the crate precede or follow the crate as a whole? In the case of the above document, does the character point location between the b and a in probationary precede or follow the type attribute location for which probationary is the value? Does the range of characters from 03-12 through 1979-09 precede or follow the second start_date element location?
The questions seem absurd, yet they go to the heart of what is meant by document order in the first place. The XPointer xpointer( ) Scheme spec tries to put them to rest by carefully mapping out the various combinations of location types and how to determine which of a given pair of locations precedes or follows the other. To do so, it first defines a new term, immediately preceding node, which applies to any point location (either node or character type) in a document or external parsed entity. The immediately preceding node depends on the type of point location and its index value:
- If the point is a character point, the immediately preceding node is the point's container.
- If the point is a node point:
- When the index, n, is greater than 0, the immediately preceding node is the nth child of the node point's container.
- When the index equals 0, the immediately preceding node is the container itself — unless the container also has one or more attribute or namespace nodes. In the latter case, the immediately preceding node is the last such attribute or namespace node.
Note that determining the immediately preceding node for the first node point (index 0) in a container rather overturns a central principle of XML itself, which is that the order of attributes and namespaces cannot be relied on. For example, take a look at the following code fragment:
<place longitude="84.259337W" latitude="30.428563N"> <name>Tallahassee</name> </place>
What is the immediately preceding node of the node point between the place and name element's start tags (that is, the node point identified by the expression place/point( ))? Common sense might indicate that it's the latitude attribute — that is, the last attribute of the node point's container (the place element). This isn't necessarily true, though, because an XML parser is free to order an element's attributes in any way it wishes. A parser can be expected to impose some kind of order (e.g., alphabetical) on attributes, which might simplify constructing a lookup index for the document. If the parser does use alphabetical rather than document ordering of attributes, the immediately preceding node for this node point will be the longitude attribute.
The XPointer spec gets around this point by noting, parenthetically, "the order of attribute and namespace nodes is implementation dependent." The lesson here: if you need to determine the immediately preceding node of a 0-indexed node point, you'd better be sure you understand the behavior of any parser accessing the document to which you're pointing. It's unlikely that you'd know for sure about the parser's ordering of attribute and namespace nodes. The spec's authors needed to codify the immediately preceding node concept somehow; and the XML Recommendation painted them into something of a corner. In this case, though — for 0-indexed node points — it's a shame that the codification seems to imply a practical impossibility. (On the other hand, unless you're trying to work out for some reason the order of nodes within a document, you don't need to worry about the immediately preceding node at all.)
Table 9-4 summarizes how to determine if a location of a given type — node, point, or range — is "before" or "after" another according to XPointer's definition of document order.
Table 9-4. Document order and location types
|When comparing locations of type...||Their document order is equal if...||Location 1 is before location 2 if...||Location 1 follows location 2 if...|
|Node and node||(Per XPath)||(Per XPath)||(Per XPath)|
|Node and point||(N/A: never equal)||The node is before or equal to the point's immediately preceding node||The node follows the point's immediately preceding node|
|Node and range||(N/A: never equal)||The node is before the range's start point||The node follows the range's start point|
|Point and point||Their immediately preceding nodes are equal and their indexes are equal||The first point's immediately preceding node is before the second's, or if they share the same immediately preceding node and the first point's index is less than the second's||The first point's immediately preceding node follows the second's, or if they share the same immediately preceding node and the first point's index is equal to or greater than the second's|
|Point and range||The point, and the range's start and end points, are equal||The point is before or equal to the range's start point||The point is after the range's start point|
|Range and range||The two ranges' start points are equal and their end points are equal||The first range's start point is before the second's, or if they have the same start point but the first range's end point precedes the second's||The first range's start point follows the second's, or if they have the same start point but the first range's end point follows the second's|
XPointer Document Order Extensions: Examples
Let's look at how these extensions to XPath's definition of document order work in practice. I won't reconsider the node-versus-node case (corresponding to the first row in Table 9-4); if you need a refresher, refer back to Chapter 2.
Here is a brief sample document from which I'll pick arbitrary locations and show how to apply Table 9-4 to a real-world example.
<dictionary source="Welsh" target="English"> <word> <in>ffwlbart</in> <out>polecat</out> </word> <word> <in>rhaglaw</in> <out>governor</out> <out>lieutenant</out> </word> <word> <in>ymyl</in> <out>edge</out> <out>border</out> </word> </dictionary>
Here are the locations to be considered:
- The root node
- The target attribute
- The first out child of the second word element (corresponding to the string-value "governor")
- The node point between the dictionary element's start tag and the first word element's start tag
- The node point between the last two out elements (string-values "edge" and "border")
- The character point between the r and h in rhaglaw
- Extending from points 2a to 2b, above
- Extending from points 2c to 2b, above
Table 9-5 illustrates how an XPointer processor would interpret the document order of various combinations of these locations.
Table 9-5. XPointer document order examples
|Location 1||Location 2||Row in Table 9-4||Interpretation|
|Node 1a||Point 2a||Node and point||The node point's index within its container (the dictionary element) is 0. Therefore, the immediately preceding node — assuming simple document order is used by the parser — is the target attribute. Because the root node "precedes" the target attribute, the root node is considered to be before this point, in document order.|
|Node 1b||Range 3a||Node and range||Because the target attribute node is the range's start point, the target attribute is before the range, in document order.|
|Node 1c||Range 3b||Node and range||The range encompasses the out element node in question. The range's start point occurs before the out element, so the range is assumed to precede the node.|
|Point 2a||Point 2b||Point and point||As above, the immediately preceding node of Point 2a is the target attribute. The immediately preceding node of Point 2b is the next-to-last out element ("edge"). Therefore, Point 2a precedes Point 2b.|
|Point 2c||Range 3b||Point and range||The point and the range's start point are equal. Therefore, either the point and range are equal, or the point is before the range. The determining factor in this case is the range's end point, which follows the point; therefore, the point precedes the range.|
|Range 3a||Range 3b||Range and range||The two ranges overlap, sharing the same end point but with different start points. In this case, look at their start points. Whichever range has the first start point — 3a, here — is assumed to precede the other.|
Similar logic applies for any combination of nodes, points, and ranges.
Unsurprisingly — given the extensions to XPath elsewhere provided by XPointer — the XPointer spec defines a handful of additional functions for processing the two new location types (points and ranges). Any application claiming XPointer xpointer( ) Scheme conformance must make these functions available.
The word "must," although it seems to carry the force of law, is a notoriously slippery concept in practice. For starters, just consider what it might denote in the context of a W3C document whose final status can never be greater than "Recommendation."
As in Chapter 4, covering XPath functions, in this section I'll first present these functions briefly in Table 9-6, then examine each in greater detail. In that earlier chapter, each table included a column for the type of value the function returns; this column isn't needed in this case, because all eight functions return a location-set. (Most of them are passed a location-set as well, designated locset in their prototypes.)
Table 9-6. XPointer functions
|Returns a location-set consisting of the start points for all locations in locset|
|Returns a location-set consisting of the end points for all locations in locset|
|Returns a range for each location in locset|
string-range(locset, string, number1?, number2?)
|Returns a range for each location in locset, based on a search for the string argument in each location's string-value|
|Returns a covering range for each location in locset|
|Returns a range for the contents of each location in locset|
|Establishes the document in which the XPointer appears as the context for further location steps|
|Used with XPointer-based XLinks to identify the location from which the link traversal began|
Exactly how the start-point( ) function behaves varies according to the type of each location in locset.
A reminder: the term "location-set" is the same as XPath's "node-set," extended to cover points and ranges. As with XPath functions, a passed location-set may include one or more locations, or even be empty. In the latter case, of course, the XPointer using the function will fail.
Not very surprisingly, if a given location is a point or range, start-point( ) returns that point or the range's start point, respectively. If a given location is the root location, an element, text, comment, or PI, the point returned is the one whose index is 0 within that container.
Surprisingly (to me, anyway), if a given location is an attribute or namespace location, "the pointer part in which the function appears fails." What this probably refers to is just that you can't identify the start point of an attribute or namespace location; still, it seems to fly in the face of examples elsewhere in the spec, which assert (for example) that a substring of an attribute value can constitute a range.
Here's a document fragment as an example:
<transaction type="deposit"> <account>1234-0987-65</account> <amount currency="USD">1009.46</amount> <source>cash</source> </transaction>
We could construct an XPointer into this source such as the following:
This XPointer locates the point within the account element whose index is 0 — that is, the node point immediately preceding the text node whose value is "1234-0987-65." We could also pass to the start-point( ) function a multimember location-set, as in:
The value returned would be a series of points, representing the start points of each child of the root transaction element. Because there are three such children — the account, amount, and source elements — we'd get back a location-set, in this case, of three node points: the points immediately following each child element's start tag. On the other hand:
locates the node point between the transaction and account element's start tags.
As you might expect, the end-point( ) function is the flip-side of start-point( ), returning — for each location in the passed location-set — an end point determined according to the location's type.
For point and range locations, the function returns that point or the range's end point, respectively. For attribute and namespace locations, as with start-point( ), the XPointer part containing the function call fails. How end-point( ) treats the other location types, though, is a little less generic.
- For the root and each element location, end-point( ) returns a point whose container is the root or that element, respectively, and whose index equals the number of children of the root or that element.
- For each text, comment, or PI location, end-point( ) returns a point whose container is that location, and whose index equals the length of the location's string-value.
Referring back to the previous XML code example, this XPointer:
locates the node point following the root transaction element's end tag. (The container is the root node. The 0-index point within that container is the node point preceding transaction's start tag; there's only one child of the root location, which is the transaction element, therefore the index of the point returned by end-point( ) is 1.)
returns a location-set consisting of three points: the character point at the very end of each of the source's three text locations. For example, the first text location's string-value is 1234-0987-65, 12 characters long; the point within that text location whose index is 12 is the character point following the digit 5.
To understand how this function works, you need to understand the start-point( ) and end-point( ) functions as well. (If you're not sure about them, check the descriptions above.) Its behavior also depends on the context location at the point of the call to range-to( ). The function returns a range whose start point is calculated as if you'd passed the context location to start-point( ), and whose end point is calculated as if you'd passed the locset argument to end-point( ). The effect, therefore, is to locate everything from the context location through that end point, inclusively.
Returning to the banking-transaction document, consider this XPointer:
At the time of the call to range-to( ), the context location is the account element. Its start point (as if calculated using start-point(//account)) is the node point immediately following that element's start tag (just before the digit 1). Thus, this XPointer returns everything between that start point and the end point of the amount element — that is, the node point just before amount's end tag. The result is a location-set (containing a single range) whose string-value is the concatenated string-values of the account and amount elements:
Note the similarity in behavior of the range-to( ) location to a user's selection of text in a GUI environment (at least insofar as selection of complete text nodes is concerned). If for no other reason, I suspect this similarity will make range-to( ) a very popular XPointer function.
As the spec points out, the range-to( ) function puts an additional spin on XPath's definition of a location step. XPath says that a location step can consist of either the full or abbreviated form of the axis-node test-predicate combination. In the XPointer universe, this definition is broadened to include one or more optional calls to range-to( ), each perhaps with a predicate of its own. Thus, with XPointer we may now see location paths such as the one highlighted here:
xpointer(//lawn[@type = 'zoysia']/sowing[@time = 'earliest']/range-to(following- sibling::sowing[@time = 'latest']))
The location steps preceding the call to range-to( ) locate a node-set restricted to all sowing elements with a time attribute of "earliest" that are children of lawn elements with a type of "zoysia"; this node-set thus establishes the context for the call to range-to( ) (which establishes a range from the "earliest" sowing through the "latest"). This call to range-to( ) thus excludes from the final range any text within the selected lawn element except the text that appears in that range of its child sowing elements.
In the examples above — as in the XPointer spec itself — the location-set passed to range-to( ) always consists of a single location. I've tried to wrap my mind around what happens if the passed location-set contains more than one location, but confess that it seems too complex to put into words (assuming that it's even a possible, let alone reasonable or desirable, thing to do).
string-range(locset, string, number1?, number2?)
This odd-looking beast of a function searches all locations passed as the first argument; each location's string-value is matched against the second argument, and if a match is found, a location-set consisting of a number of ranges is returned, one range for each discrete match for the string argument found within the range's string-values. By default, a range starts just before the matched string, and ends just after it, so the string-value of each range returned is the matched string, wherever they occur within the location-set. However, you can override these defaults using the optional third and fourth arguments. The number1 argument fixes the start point of the range (offset from the first character in the match); the number2 argument specifies the length of the returned range, in characters.
For instance, given a document such as this:
<people> <person> <name>Simpson,John</name> </person> <person> <name>Kirby,John</name> </person> <person> <name>Simpson,Mike</name> </person> </people>
You could use the string-range( ) function as in the following XPointer to return just those name elements whose string-values contained the string "Simpson":
xpointer(string-range(//name[contains(., "Simpson")], "Simpson")
This locates two ranges; both ranges consist of the string "Simpson" because by default — in the absence of the third and fourth arguments — the range(s) returned match the sought-for string exactly. You could also include the optional third and fourth arguments to return (for example) ranges consisting of the four characters following the comma:
xpointer(string-range(//name[contains(., "Simpson")], "Simpson", 8, 4))
This returns two ranges, comprising the strings "John" and "Mike."
Use the range( ) function when you want not a location-set of ranges per se, but of their covering ranges. For each location in locset, the function returns its covering range. Refer back to Section 188.8.131.52 to see how covering ranges are determined for the various location types.
The range-inside( ) function works similarly to the range( ) function, in that it may return a range for each location in locset. But range-inside( ) may also return non-range-type locations. Table 9-7 summarizes.
Table 9-7. range-inside( ) behavior, by location type
|When location is...||Location itself returned?||Container of range returned||Index of start point of range returned||Index of end point of range returned|
|Any other type||No||Location itself||0||If the location is a node that can have children (that is, the root node or an element node), then the number of children of that node; otherwise, the length of the location's string-value|
As always, this behavior will be easier to understand using a concrete example. So consider the following:
<neighborhood> <street> <name>Post Avenue</name> <address>101</address> <address>103</address> <address>109</address> </street> <street> <name>Mercy Lane</name> <address>1424A</address> <address>1424B</address> </street> </neighborhood>
And now consider the following XPointer:
Because the location-set passed to the function includes five members — the address elements — each of which is neither a point nor a range, we should expect this function to return a set of five range-type locations. The container for each of these locations is an address element, and the start point of each location within its container is at index 0 (that is, immediately following the element's start tag). The end point is the length of the corresponding address element's string-value — immediately following the 1, 3, 9, A, and B characters in the address elements. Thus, the function call returns the five ranges with the following string-values:
101 103 109 1424A 1424B
Now let's look at a different XPointer that uses range-inside( ):
Only one location is passed to range-inside( ) here — the second street element — so we'll get back a single range. The container for the range is that street element itself, and the start point is set at index 0 (the node point immediately following the second street element's start tag). The end point of this street element location is a node point — the one preceding that street element's end tag. Therefore, the index of the returned range's end point is set to the number of children of the street element, or 3 (one name element and two address elements) — setting the end point to the node point immediately preceding the second street element's end tag. Thus, the returned range is:
<name>Mercy Lane</name> <address>1424A</address> <address>1424B</address>
The here( ) function provides a convenient means to refer to the XML document or external parsed entity in which the XPointer itself appears. The location-set returned by the function has a single member, determined as follows:
- If the XPointer is in a text node inside an element node, the function returns that element node.
- Otherwise, the function returns whatever node directly contains the XPointer.
The spec is careful to say that an XPointer using the here( ) function must appear in an XML document or external parsed entity, otherwise, the XPointer fails.
Here's a sample XML document using two XPointers:
<code> <navel-gazing>xpointer(here( )/..)</navel-gazing> <looking-elsewhere xlink:href="xpointer(here( )/..)"/> </code>
The first XPointer, of course, appears in an element node — as the text node contained by the navel-gazing element. Therefore, the first XPointer locates the code element. The second XPointer occurs in the xlink:href attribute of the looking-elsewhere element; thus, this XPointer locates the parent of that attribute, or the looking-elsewhere element.
The only application in which you'll make use of the origin( ) function is when constructing an XPointer in an XLink context — specifically, when you need to identify the location at which a particular XLink's traversal begins. Complete coverage of XLink lies well outside this book's scope. In general, though, XLink provides for so-called "third party" and inbound links, in addition to the more familiar outbound-only links (such as XHTML's a elements with href attributes).
The particular problem that the origin( ) function addresses has to do with a series of XLinks in which succeeding links need to be relative to preceding ones. Without getting into the details of how the XLinks themselves are effected (syntactically or conceptually), an example of such a situation might be depicted something like the following pseudocode:
XLinkToChapter(xpointer([XPointer to a resource in some other document])) XLinkToChapter(xpointer(origin( )/following-sibling::*))
Again, I stress that this is not the way these XLinks are actually constructed. The point is that the second link (the one with the call to the origin( ) function) need not "know" what the first one located; by using a relative XPath expression starting with the location designated by origin( ), it automatically gets (in this case) the next sibling of whatever was located by the first link. (If the target resources of the two XPointers were in the same document where the XLinks themselves were located, you could replace the call to origin( ) with a call to here( ) to achieve the same effect.)
Note that the origin( ) function depends for its operation not just on an XPointer-aware processor, but also on some level of XLink awareness. At the time of this writing, the XML landscape is not yet exactly littered with XLink-aware applications.