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.
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.
Imagine this hypothetical user sees something like Figure 9-1 on her screen. For one reason or another, she decides to select the portion of this document depicted in Figure 9-2.
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 extendXPath?</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[1], 12, 6) | //comment( )[1] | substring(//p[1], 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.
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:
<storage>disk</storage>
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.
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:
xpointer(//storage[1]/point( )[1]
)
While the numbering of the storage
elements starts
at 1, the numbering for the points is zero-based.
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.
Axes |
Node-set locatable from a point |
|
None (node-set is empty) |
|
The point itself |
|
The point’s container node |
|
The point’s container node, that container’s parent, and so on (up the tree to the root node) |
|
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.
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).
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:
xpointer(//range( )[4])
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.
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.
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.
Location |
Container of startand end points |
Index of start point |
Index of end point |
|
The |
0 |
2 (length of string-value “NY”) |
|
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 (parent of the PI) |
0 (the PI has no preceding siblings) |
1 (index of the start point, plus 1) |
Comment |
The |
1 (there’s one preceding sibling of the comment, the
|
2 (index of the start point, plus 1) |
Text node, “The New Yorker” |
The |
0 (no preceding siblings) |
1 (index of the start point, plus 1) |
|
The |
2 ( |
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.
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( )[0]
)?
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.
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 |
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:
Nodes:
The root node
The target
attribute
The first out
child of the second
word
element (corresponding to the string-value
“governor”)
Points:
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
Ranges:
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.
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
|
Node 1b |
Range 3a |
Node and range |
Because the |
Node 1c |
Range 3b |
Node and range |
The range encompasses the |
Point 2a |
Point 2b |
Point and point |
As above, the immediately preceding node of Point 2a is the
|
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.)
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:
xpointer(start-point(//account))
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:
xpointer(start-point(/transaction/*))
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:
xpointer(start-point(/transaction))
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:
xpointer(end-point(/))
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.)
Similarly:
xpointer(end-point(//text( )))
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:
xpointer(//account/range-to(account))
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:
1234-0987-651009.46
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).
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 9.2.2.4 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.
When location is... |
Location itself returned? |
Container of range returned |
Index of start point of range returned |
Index of end point of range returned |
Point |
Yes |
(N/A) |
(N/A) |
(N/A) |
Range |
Yes |
(N/A) |
(N/A) |
(N/A) |
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:
xpointer(range-inside(//address))
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( )
:
xpointer(range-inside(//street[2]))
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.
18.217.144.32