XPointers are a non-XML syntax for identifying locations inside XML documents. An XPointer is attached to the end of the URI as its fragment identifier to indicate a particular part of an XML document rather than the entire document. XPointer syntax builds on the XPath syntax used by XSLT and covered in Chapter 9. To the four fundamental XPath data types Boolean, node-set, number, and string XPointer adds points and ranges, as well as the functions needed to work with these types. It also adds some shorthand syntax for particularly useful and common forms of XPath expressions.
A URL that identifies a document typically looks something like http://java.sun.com:80/products/jndi/index.html. The scheme, http in this example, tells you what protocol the application should use to retrieve the document. The authority, java.sun.com:80 in this example, tells you from which host the application should retrieve the document. The authority may also contain the port to connect to that host and the username and password to use. The path, /products/jndi/index.html in this example, tells you which file in which directory to ask the server for. This may not always be a real file in a real filesystem, but it should be a complete document the server knows how to generate and return. All of this you're already familiar with, and XPointer doesn't change any of it.
You probably also know that some URLs contain fragment identifiers that point to a particular named anchor inside the document the URL locates. This is separated from the path by the sharp sign #. For example, if we were to add the fragment download to the previous URL, then it would become http://java.sun.com:80/products/jndi/index.html#download. When a web browser follows a link to this URL, it looks for a named anchor in the document at http://java.sun.com:80/products/jndi/index.html with the name download such as this one:
It would then scroll the browser window to the position in the document where the anchor with that name is found. This is a simple and straightforward system, and it works well for HTML's simple needs. However, it has one major drawback: to link to a particular point of a particular document, you must be able to modify the document to which you're linking in order to insert a named anchor at the point to which you want to link. XPointer endeavors to eliminate this restriction by allowing you to specify where you want to link to using full XPath expressions as fragment identifiers. Furthermore, XPointer expands on XPath by providing operations to select particular points in or ranges of an XML document that do not necessarily coincide with any one node or set of nodes. For instance, an XPointer can describe the range of text currently selected by the mouse.
The most basic form of XPointer is simply an XPath expression often, though not necessarily, a location path enclosed in the parentheses of xpointer( ). For example, these are all acceptable XPointers:
xpointer(/) xpointer(//first_name) xpointer(id('sec-intro')) xpointer(/people/person/name/first_name/text( )) xpointer(//middle_initial[position( )=1]/../first_name) xpointer(//profession[.="physicist"]) xpointer(/child::people/child::person[@index<4000]) xpointer(/child::people/child::person/attribute::id)
Not all of these XPointers necessarily refer to a single element. Depending on which document the XPointer is evaluated relative to, an XPointer may identify zero, one, or more than one node. Most commonly the nodes identified are elements, but they can also be attribute nodes or text nodes, as well as points or ranges.
If you're uncertain whether a given XPointer will locate something, you can back it up with an alternative XPointer. For example, this XPointer looks first for first_name elements. However, if it doesn't find any, it looks for last_name elements instead:
The last_name elements will be found only if there are no first_name elements. You can string as many of these XPointer parts together as you like. For example, this XPointer looks first for first_name elements. If it doesn't find any, it then seeks out last_name elements. If it doesn't find any of those, it looks for middle_initial elements. If it doesn't find any of those, it returns an empty node set:
No special separator character or whitespace is required between the individual xpointer( ) parts, though whitespace is allowed. This XPointer means the same thing:
xpointer(//first_name) xpointer(//last_name) xpointer(//middle_initial)
Obviously, what an XPointer points to depends on to which document it's applied. This document is specified by the URL to which the XPointer is attached. For example, if you wanted a URL that pointed to the first name element in the document at http://www.cafeconleche.org/people.xml, you would type:
If the XPointer uses any characters that are not allowed in URIs for instance, the less than sign <, the double quotation mark ", or non-ASCII letters like then these must be hexadecimally escaped as specified by the URI specification before the XPointer is attached to the URI. That is, each such character is replaced by a percent sign followed by the hexadecimal value of each byte in the character in the UTF-8 encoding of Unicode. Thus, < would be written as %3C, " would be written as %22, and would be written as %C3%A9.
In HTML, the URLs used in a elements can contain an XPointer fragment identifier. For example:
<a href = "http://www.cafeconleche.org/people.xml#xpointer(//name)"> The name of a person </a>
If a browser followed this link, it would likely load the entire document at http://www.cafeconleche.org/people.xml and then scroll the window to the beginning of the first name element in the document. However, no browsers yet support XPointer, so the exact behavior is open for debate. In some situations it might make sense for the browser to show only the specific element node(s) the XPointer referred to rather than the entire document.
Since XPath can only locate nodes in a well-formed XML document, XPointers can only point into XML documents. You can't use them to link into nonwell-formed HTML, plain-text files, or other non-XML documents. However, linking from HTML documents is perfectly fine, as is printing XPointers in books, painting them on the sides of buildings, or communicating them by any means by which text can be communicated.
XPointers are more frequently used in XLinks. For example, this simple link points to the first book child of the bookcoll child of the testament root element in the document at the relative URL ot.xml:
<In_the_beginning xlink:type="simple" xlink:href="ot.xml#xpointer(/testament/bookcoll/book[position( )=1])"> Genesis </In_the_beginning>
In extended links, an XPointer can help identify both the starting and ending resources of an arc. For example, this extended XLink establishes an arc between the last v element in the document at the relative URL ot.xml and the first v element of the document at the relative URL nt.xml. Then it establishes a link from the first v element of nt.xml to the last v element of ot.xml:
<Bible xlink:type="extended" xmlns:xlink="http://www.w3.org/1999/xlink"> <testament xlink:type="locator" xlink:label="ot" xlink:href="ot.xml#xpointer(//v[position()=last( )])"/> <testament xlink:type="locator" xlink:label="nt" xlink:href="nt.xml#xpointer(//v[position( )=1])" /> <next xlink:from="ot" xlink:to="nt"/> <previous xlink:from="nt" xlink:to="ot"/> </Bible>
Links can even be purely internal; that is, they can link from one place in the document to another place in the same document. The slide element shown in this example contains simple XLinks that point to the first and last slide elements in the document:
<slide xmlns:xlink="http://www.w3.org/1999/xlink"> <point>Acme Wonder Goo is a delicious dessert topping!</point> <point>Acme Wonder Goo is a powerful floor cleaner!</point> <point>It's two products in one!</point> <first xlink:type="simple" xlink:href="#xpointer(//slide[position( )=1])"> Start </first> <last xlink:type="simple" xlink:href="#xpointer(//slide[position()=last( )]))"> End </last> </slide>
When the XPath expressions used in an XPointer are themselves relative, the context node is the root node of the entity that contains the XPointer.
XPointers provide a number of convenient extensions to XPath. One of the simplest is the bare name. A bare name XPointer is similar to an HTML named anchor; that is, a bare name XPointer identifies the element at which it's pointing by its name. However, this name is supplied by an ID type attribute of the element being pointed at rather than by a special a element with a name attribute. To link to an element with a bare name, append the usual fragment separator # to the URL followed by the ID of the element to which you're linking. For example, the URL http://www.w3.org/TR/1999/REC-xpath-19991116.xml#NT-AbsoluteLocationPath links to the element in the XPath 1.0 specification that has an ID type attribute with the value NT-AbsoluteLocationPath.
The ID attribute is an attribute declared to have ID type in the document's DTD. It does not have to be named ID or id. Bare names cannot be used to link to elements in documents that don't have DTDs because such a document cannot have any ID type attributes.
For example, suppose you wanted to link to the Motivation and Summary section of the Namespaces in XML recommendation at http://www.w3.org/TR/1999/REC-xml-names-19990114/xml-names.xml. A quick peek at the source code of this document reveals that it has an id attribute with the value sec-intro and that indeed this attribute is declared to have ID type in the associated DTD. Its start-tag looks like this:
Therefore, http://www.w3.org/TR/1999/REC-xml-names-19990114/xml-names.xml#sec-intro is a URL that points to this section. The name does not need to be (and indeed should not be) enclosed in xpointer( ) to make this work. Just the ID value is sufficient. This is basically just a convenient shorthand for an XPointer using an XPath expression using the id( ) function. The same URL could just as easily have been written as http://www.w3.org/TR/1999/REC-xml-names-19990114/xml-names.xml#xpointer(id(sec-intro)).
Another very common form of XPointer is one that descends exclusively along the child axis, selecting elements by their position relative to their siblings. For example, xpointer(/child::*[position() = 1]/child::*[ position( ) = 2]/child::*[position( ) = 3]) selects the third child element of the second child element of the root element of the document. Since this is so common, XPointer allows you to abbreviate this syntax by providing only the numbers of the child elements separated by forward slashes. This is called a child sequence. For example, the previous XPointer could be rewritten as a child sequence in the much more compact form /1/2/3. A child sequence should not be enclosed in xpointer( ) as a normal XPath expression would.
For example, the Motivation and Summary section of the Namespaces in XML recommendation at http://www.w3.org/TR/1999/REC-xml-names-19990114/xml-names.xml is given as a div element. It so happens that this div element is the first child element of the second child element of the root element. Therefore, the URL http://www.w3.org/TR/1999/REC-xml-names-19990114/xml-names.xml#/1/2/1 points to this section.
Since XPointers may appear in places that are not XML documents (HTML documents, database fields, magazine pages, etc.), they require their own mechanism for binding namespace prefixes to namespace URIs. This is done by placing one or more xmlns parts before the xpointer part. The syntax is xmlns(prefix=URI). For example, this XPointer maps the svg prefix to the http://www.w3.org/2000/svg namespace and then searches out all rect elements in that namespace:
As with most other uses of namespaces, only the URI matters in an XPointer, not the prefix. The previous XPointer finds all rect elements in the http://www.w3.org/2000/svg namespace regardless of what prefix they use or whether they're in the default namespace.
There is no way to define a default, unprefixed namespace for an XPointer. However, prefixed names in an XPointer can refer to unprefixed but namespace-qualified elements in the targeted document. For example, this XPointer finds the third div element in an XHTML document:
It uses the prefix html to identify the XHTML namespace, even though XHTML documents never use prefixes themselves.
More than one namespace prefix can be used simply by adding extra xmlns parts. For example, this XPointer seeks out svg elements in XHTML documents by declaring one prefix each for the SVG and XHTML namespaces:
xmlns(svg=http://www.w3.org/2000/svg) xmlns(h=http://www.w3.org/1999/xhtml) xpointer(/h:html//svg:svg)
If an XPointer is included in an XML document, the namespace bindings established by that document do not apply to the XPointer. Only the bindings established by the xmlns parts apply to the XPointer. If the xpointer parts contain XPath expressions that refer to elements or attributes in a namespace, then they must be preceded by xmlns parts declaring the namespaces.
XPaths, bare names, and child sequences can only point to entire nodes or sets of nodes. However, sometimes you want to point to something that isn't a node, such as the third word of the second paragraph or the year in a date attribute that looks like date="01/03/1950". XPointer adds points and ranges to the XPath syntax to make this possible. A point is the position preceding or following any tag, comment, processing instruction, or character in the #PCDATA. Points can also be positions inside comments, processing instructions, or attribute values. Points cannot be located inside an entity reference, though they can be located inside the entity's replacement text. A range is the span of parsed character data between two points. Nodes, points, and ranges are collectively called locations; a set that may contain nodes, points, and ranges is called a location set. In other words, a location is a generalization of the XPath node that includes points and ranges, as well as elements, attributes, namespaces, text nodes, comments, processing instructions, and the root node.
A point is identified by its container node and a non-negative index into that node. If the node contains child nodes that is, if it's a document or element node then there are points before and after each of its children (except at the ends, where the point after one child node will also be the point before the next child node). If the node does not contain child nodes that is, if it's a comment, processing instruction, attribute, namespace, or text node then there's a point before and after each character in the string value of the node, and again the point after one character will be the same as the point before the next character.
Consider the document in Example 11-1. It contains a novel element that has seven child nodes, three of which are element nodes and four of which are text nodes containing only whitespace.
<?xml version="1.0"?> <?xml-stylesheet type="text/css" value="novel.css"?> <!-- You may recognize this from the last chapter --> <novel copyright="public domain"> <title>The Wonderful Wizard of Oz</title> <author>L. Frank Baum</author> <year>1900</year> </novel>
There are eight points directly inside the novel element numbered from 0 to 7, one immediately after and one immediately before each tag. Figure 11-1 identifies these points.
Inside the text node child of the year element, there are five points:
Point 0 between <year> and 1
Point 1 between 1 and 9
Point 2 between 9 and 0
Point 3 between 0 and 0
Point 4 between 0 and </year>
Notice that the points occur between the characters of the text rather than on the characters themselves. Points are zero-dimensional. They identify a location, but they have no extension, not even a single character. To indicate one or more characters, you need to specify a range between two points.
XPointer adds two functions to XPath that make it very easy to select the first and last points inside a node, start-point( ) and end-point( ). For example, this XPointer identifies the first point inside the title element, that is, the point between the title node and its text node child:
This XPointer indicates the point immediately before the </author> tag:
If there were multiple title and author elements in the document, then these functions would select multiple points.
This XPointer points to the point immediately before the letter T in "The Wonderful Wizard of Oz":
This point falls immediately after the point indicated by xpointer(start-point(//title)). These are two different points, even though they fall between the same two characters (> and T) in the text.
To select points other than the start-point or end-point of a node, you first need to form a range that begins or ends with the point of interest using string-range( ) and then use the start-point or end-point function on that range. We take this up in the next section.
A range is the span of parsed character data between two points. It may or may not represent a well-formed chunk of XML. For example, a range can include an element's start-tag but not its end-tag. This makes ranges suitable for uses such as representing the text a user selected with the mouse. Ranges are created with four functions XPointer adds to XPath:
The range( ) function takes as an argument an XPath expression that returns a location set. For each location in this set, the range( ) function returns a range exactly covering that location; that is, the start-point of the range is the point immediately before the location, and the end-point of the range is the point immediately after the location. If the location is an element node, then the range begins right before the element's start-tag and finishes right after the element's end-tag. For example, consider this XPointer:
When applied to Example 11-1, it selects a range exactly covering the single title element. If there were more than one title element in the document then it would return one range for each such title element. If there were no title elements in the document, then it wouldn't return any ranges.
Now consider this XPointer:
If applied to Example 11-1, it returns three ranges, one covering each of the three child elements of the novel root element.
The range-inside( ) function takes as an argument an XPath expression that returns a location set. For each location in this set, it returns a range exactly covering the contents of that location. For anything except an element node this will be the same as the range returned by range( ). For an element node, this range includes everything inside the element, but not the element's start-tag or end-tag. For example, when applied to Example 11-1, xpointer(range-inside(//title)) returns a range covering The Wonderful Wizard of Oz but not <title>The Wonderful Wizard of Oz</title>. For a comment, processing instruction, attribute, text, or namespace node, this range covers the string value of that node. For a range, this range is the range itself. For a point, this range begins and ends with that point.
The range-to( ) function is evaluated with respect to a context node. It takes a location set as an argument that should return exactly one location. The start-points of the context nodes are the start-points of the ranges it returns. The end-point of the argument is the end-point of the ranges. If the context node set contains multiple nodes, then the range-to( ) function returns multiple ranges.
For instance, suppose you want to produce a single range that covers everything between <title> and </year> in Example 11-1. This XPointer does that by starting with the start-point of the title element and continuing to the end-point of the year element:
Ranges do not necessarily have to cover well-formed fragments of XML. For instance, the start-tag of an element can be included but the end-tag left out. This XPointer selects <title>The Wonderful Wizard of Oz:
It starts at the start-point of the title element, but it finishes at the end-point of the title element's text node child, thereby omitting the end-tag.
The string-range( ) function is unusual. Rather than operating on a location set including various tags, comments, processing instructions, and so forth, it operates on the text of a document after all markup has been stripped from it. Tags are more or less ignored.
The string-range( ) function takes as arguments an XPath expression identifying locations and a substring to try to match against the XPath string value of each of those locations. It returns one range for each match, exactly covering the matched string. Matches are case sensitive. For example, this XPointer produces ranges for all occurrences of the word "Wizard" in title elements in the document:
If there are multiple matches, then multiple ranges are returned. For example, this XPointer returns two ranges when applied to Example 11-1, one covering the W in "Wonderful" and one covering the W in "Wizard":
You can also specify an offset and a length to the function so that strings start a certain number of characters from the beginning of the match and continue for a specified number of characters. The point before the first character in the string to search is 1. For example, this XPointer selects the first four characters after the word "Wizard" in title elements:
xpointer(string-range(//title, "Wizard", 7, 4))
Nonpositive indices work backwards in the document before the beginning of the match. For example, this XPointer selects the first four characters before the word "Wizard" in title elements:
xpointer(string-range(//title, "Wizard", -3, 4))
If the offset or length causes the range to fall outside the document, then no range is returned.
Since string ranges can begin and end at pretty much any character in the text content of a document, they're the way to indicate points that don't fall on node boundaries. Simply create a string range that either begins or ends at the position you want to point to, and then use start-point( ) or end-point( ) on that range. For example, this XPointer returns the point immediately before the word "Wizard" in the title element in Listing 11-1:
Normally, an XPointer is a fragment identifier attached to a URL. The root node of the document the URL points to is the context location for the XPointer. However, XPointers can also be used by themselves without explicit URLs in XML documents. By default, the context node for such an XPointer is the root node of the document where the XPointer appears. However, either the here( ) or the origin( ) function can change the context node for the XPointer's XPath expression.
The here( ) function is only used inside XML documents. It refers to the node that contains the XPointer or, if the node that contains the XPointer is a text node, the element node that contains that text node. here( ) is useful in relative links. For example, these navigation elements link to the page elements preceding and following the pages in which they're contained.
<page> content of the page... <navigation xlink:type="simple" xlink:href="#xpointer(here( )/../../preceding-sibling::page)"> Previous </navigation> <navigation xlink:type="simple" xlink:href="#xpointer(here( )/../../following-sibling::page)"> Next </navigation> </page>
In these elements, the here( ) function refers to the xlink:href attribute nodes that contain the XPointer. The first .. selects the navigation parent element. The second .. selects its parent page element, and the final location step selects the previous or next page element.
The origin( ) function is useful when the document has been loaded from an out-of-line link. It refers to the node from which the user is initiating traversal, even if that is not the node that defines the link. For example, consider an extended link like this one. It has many novel elements, each of which is a locator that shares the same label:
<series xlink:type="extended" xmlns:xlink="http://www.w3.org/1999/xlink"> <!-- locator elements --> <novel xlink:type="locator" xlink:label="oz" xlink:href="ftp://archive.org/pub/etext/etext93/wizoz10.txt"> <title>The Wonderful Wizard of Oz</title> <year>1900</year> </novel> <novel xlink:type="locator" xlink:label="oz" xlink:href="ftp://archive.org/pub/etext/etext93/ozland10.txt"> <title>The Marvelous Land of Oz</title> <year>1904</year> </novel> <novel xlink:type="locator" xlink:label="oz" xlink:href="ftp://archive.org/pub/etext/etext93/wizoz10.txt"> <title>Ozma of Oz</title> <year>1907</year> </novel> <!-- many more novel elements... --> <sequel xlink:type="locator" xlink:label="next" xlink:href="#xpointer(origin( )/following-sibling::novel)" /> <next xlink:type="arc" xlink:from="oz" xlink:to="next" /> </series>
The sequel element uses an XPointer and the origin( ) function to define a locator that points to the following novel in the series. If the user is reading The Wonderful Wizard of Oz, then the sequel element locates The Marvelous Land of Oz. If the user is reading The Marvelous Land of Oz, then that same sequel element locates Ozma of Oz, and so on. The next element defines links from each novel (since they all share the label oz) to its sequel. The ending resource changes from one novel to the next.