Copyright © 2003 ISOGEN International, LLC. All Rights Reserved.
XIndirect is a simple mechanism for using XML to represent indirect addresses in order to augment the core functionality of XLink and XPointer without requiring either of those specifications to themselves require support for indirect addresses. The facilities defined are specifically designed to meet the requirements for systems that support the authoring and management of complex systems of documents.
This document is a Note that is the result of an acknowledged Member Submission, made available by the W3C for discussion only. Please read the Submission request and W3C Staff Comment on this request. Publication of this Note by W3C indicates no endorsement of its content by W3C, nor that W3C has, is, or will be allocating any resources to the issues addressed by the Note. The list of acknowledged W3C Submissions is available at the W3C Web site.
A list of current W3C technical documents can be found at the Technical Reports page.
The design described in this note has been implemented, as demonstrated in Appendix A Sample XIndirect Implementation. Additional implementations are encouraged in order to refine the design as necessary.
Please send comments to W. Eliot Kimber, eliot@isogen.com.
1 Introduction
2 Processing Model
3 Data Model
3.1 Linker, Pointer, And Resource Data Types
3.2 Indirector Data Type
3.3 Location Path
4 XML Representation
4.1 The indirector Element
4.2 Indirector Treatment Attributes
4.3 The indirectorset Element
5 XIndirector Processor
Acknowledgments
References
A Sample XIndirect Implementation (Informative)
A.1 XIndirect Test Style Sheet
A.2 XIndirect Test Documents
A.3 Running the Tests
The XLink and XPointer specifications[XLINK][XPTR] provide a complete solution for the delivery of XML-based[XML] hyperdocuments. XLink and XPointer explicitly avoid indirect addresses. This is the appropriate design choice for delivery. In a delivery environment it is essential to avoid any unnecessary processing or complexity. Indirection adds significant potential complexity to the processing of hyperdocuments. Indirection is not required for delivery because the documents as delivered can reflect a more-or-less static data set in which all pointers can be hardened to direct references. This is especially true when the delivery context may be an essentially unbounded system, such as the World Wide Web.
However, for authoring, indirection is required in order to make it possible and practical to manage pointers as new versions of documents are created. Authoring systems are usually closed systems with a much smaller local scope than the intended delivery environment. In such closed systems the use of indirect addressing is both practical to implement and necessary in order to solve certain problems in pointer maintenance for versioned hyperdocuments.
This document proposes a simple mechanism for using XML to represent indirect addresses. The facilities defined in this Note augment the core functionality of XLink and XPointer without requiring either of those specifications to themselves require support for indirect addresses. This lets document and management system designers decide when indirection is necessary.
The data model diagrams used in this document use the static data model graphical elements of the Unified Modeling Language (UML)[UML].
This document uses the convention that names of abstract data types defined in the UML data models are capitalized (e.g., "Indirector").
The XLink specification defines essentially three kinds of things: "linkers" (a term coined for use in this document), resources, and pointers to resources. [Definition: A "linker" is a thing that utters a pointer in order to address zero or more resources.] The term "linker" is used here because the more correct term "pointer," in the sense of "the thing doing the pointing," conflicts with the term "pointer" in the sense of "an object that can be interpreted as the address of another object." The use of the term "linker" here does not limit the applicability of the indirection facility to only those things that are semantically hyperlinks as defined by specifications such as XLink, HTML[HTML], and HyTime[HYTIME]. In fact, a Linker, as defined here, may be a subcomponent of a complete hyperlink (e.g., the XLink locator element is, abstractly, an XIndirect Linker). There are other uses of pointing that are not, strictly speaking, hyperlinks, such as XInclude. The XIndirect facility is intended to be used, and is usable with, any use of pointers regardless of the semantic of the thing doing the pointing.
[Definition: A "resource" is a thing that can be addressed.] [Defintion: A pointer is a construct that, when interpreted in terms of some data model, "returns" zero or more resources.] In XLink, because it does not define a semantic for indirection, the resources pointed to by the pointer uttered by the linker are, by definition, the resources intended by the creator of the linker as the target of the link.
To represent indirection a fourth kind of thing is needed: indirectors. [Definition: An indirector is simply a resource that itself specifies a pointer. When addressed, the indirector is interpreted not as a resource but as pointer to zero or more resources. By definition, indirectors have no semantic other than indirection.]
The semantic of an indirector is that, by default, when an indirector is addressed, the indirector's pointer is resolved and the result of that address is returned as the result of the first address. This means that, under normal circumstances, the use of indirection is transparent to the thing doing the initial pointing. The result of pointing to an indirector is a "compound address" composed of two or more steps.
An indirector is explicitly not a Linker in that an indirector, by definition, has no stronger semantic than "don't look at me, look over there." In particular, the use or non-use of indirectors cannot change the meaning of the linker that uses the address. That is, the hyperdocument constructed using indirect addresses must be semantically identical to the same hyperdocument constructed using direct addresses such that any or all indirect addresses can be replaced with the equivalent direct addresses without affecting the semantic interpretation of the hyperdocument in any way.
Note that there may be processing circumstances when indirectors need to be treated as resources in their own right. Thus it must be possible to say, for a given pointer, whether or not any indirectors it addresses are to be treated as indirectors or as resources.
In addition, there may be processing circumstances when the set of indirectors that make up a compound address need to be made visible. Thus indirection-aware processors should provide facilities for inspecting the indirectors that make up a given compound address.
The data model for XIndirect consists of four types: Linker, Pointer, Resource, and Indirector.
The Linker data type represents an object that ultimately references
zero or more member resources, using a Pointer, as shown in Figure 1.
A Resource is anything that can be addressed. A pointer is a construct that can be
interpreted as an address (e.g., an href
attribute).
The Indirector data type represents an object that serves only to point to another set of resources in order to establish an indirection, as shown in Figure 2.
Because Indirectors are themselves resources, the direct referents of an Indirector may be any combination of non-Indirector or Indirector resources.
Because Pointers in many addressing systems may address multiple resources (i.e., XPointers), the possibility for fan-out is inherent in this model. However, from the point of view of the initial linker, the result of a fanout is a flat list or set of resources. Lists are constructed in the order Indirectors occur or are addressed in document order. Whether the final result is a list or a set is a function of the semantics of the individual addresses used.
Figure 3 is an instance diagram showing a typical system of indirect addresses, starting from a linker with a single pointer. (In this diagram, the Pointer objects have been omitted but are implied by the associations labeled "direct_referent".)
[Definition: A non-indirect resource is a resource that is not interpreted as an Indirector.]
[Definition: A location path is a non-indirect pointer followed by a sequence of one or more indirectors, terminating in set of zero or more non-indirect resources.]
A given Indirector may point to another Indirector. When it does so, it forms a multi-step location path. Each Indirector forms a single step in the location path. The path extends from the initial reference by the Linker to the non-indirect Resources addressed by the last Indirector in the path.
If a Linker or Indirector addresses multiple Indirectors it creates a set of location paths, one for each terminal Indirector.
In Figure 3 there are two location paths rooted at Linker linker1. The first path consists of Indirector indir1 and Indirector indir2, terminating with Resource res1. The second path consists of Indirector indir1 and Indirector indir3, terminating with Resource res2.
From the point of view of linker1, the effective set of resources addressed is res1 and res2.
It is an XIndirector error for an Indirector to occur twice in the same location path (which would create a cycle). It is not an error for the same Indirector to occur twice in a commonly-rooted set of location paths as long as it occurs at most once in any single location path within the set.
This section defines an XML-based representation syntax for indirectors in XML documents. It is designed to be as simple as possible. It also uses naming conventions already in wide use in other XML specifications, in particular, the use of "href" as the name of the pointer attribute.
NOTE: The location address facility defined by the HyTime standard (ISO/IEC 10744:1997) also provides a conforming XML representation syntax for the indirector abstract data and processing models.
NOTE: The element type declarations used below
should be interpreted as "meta" element type declarations in
that they only apply to elements within the XIndirect name space.
They in no way constrain either where XIndirect elements may appear
in non-XIndirect elements
nor what other elements may appear within XIndirect elements.
For example, the declared meta content model of the indirector
element
is "EMPTY", meaning that it cannot contain any other XIndirect-defined
elements. However, it does not constrain the presence of any other
content. That is, an XIndirect processor will ignore, for the purpose
of apply XIndirect semantics, any content of indirector
elements. However,
such content may be used for other purposes.
The indirector
element represents an Indirector object. The required
href
attribute specifies the indirector's pointer.
Indirector elements may occur anywhere, including as the document
element of documents consisting of a single indirector
element.
There is no significance with respect to this specification of the
context in which indirector
elements occur.
NOTE: Nesting of indirector
elements is disallowed in order
to avoid potential confusion about the significance of such nesting and
to provide for future refinements in which such nesting would have
specific implications.
The indirector
element may have non-XIndirect
content, for example, an
indication of the local purpose of the indirector, application-specific
metadata, etc. Any such content is ignored by XIndirect processors
for the purposes of interpreting the indirector
element as
an Indirector.
Attributes of the indirector
element:
href
id
indirector
element within the
scope of the XML document that contains it. The indirector
element may also take the
indirector treatment attributes.
<!ELEMENT indirector EMPTY > <!ATTRIBUTE indirector href CDATA #REQUIRED id ID #IMPLIED %indirector-treatment-atts; >
Because Indirectors are also resources it is sometimes desirable or
necessary to treat Indirectors as resources instead of as indirections.
The indirector-treatment=
and max-hops=
attributes allow pointers to indicate how any addressed Indirectors
are to be treated. These attributes may be specified on non-XIndirect
Linker elements to allow them to directly address Indirectors as
resources. Linker elements can also address Indirectors as
resources indirectly by pointing to Indirectors that then
point to the intended target Indirectors as resources.
indirector-treatment
If a Linker has multiple pointers, the indirector-treatment=
attribute applies to all of them. If there is a need to have different
indirector treatment behavior for different pointers for a single Linker, an intermediate
level of indirection must be used.
max-hops
In the context of resolving a given location path, the
first non-zero max-hops=
value encountered in a location path
governs ultimate resolution, such that
subsequent non-zero max-hops=
values are ignored for that
resolution instance.
<!ENTITY % indirector-treatment-atts 'indirector-treatment (as-indirector | as-resource) "as-indirector" max-hops CDATA "0" '>
The indirectorset
element contains zero or more indirector
elements. It is
provided as a convenience for grouping sets of indirectors together.
It has no defined semantics other than containment. The use of
indirectorset
to contain indirector
elements does not affect the
interpretation of the indirectors in any way. The indirectorset
element may be used as a document element or as a subelement within
a larger document. The indirectorset
element may contain
any non-XIndirect content.
<!ELEMENT indirectorset (indirector*) >
An XIndirector processor is a computer system component that augments normal
XPointer resolution by recognizing and interpreting indirector
elements
in order to resolve complex addresses into lists of resources as though
those resources had been addressed by a single, direct, address.
This could be implemented as a direct extension to XPointer resolution or
as a separate processing stage or layer.
The following XSLT style sheet provides a test implementation of the XIndirect facility.
It uses XSLT, extended using the EXSLT[EXSLT] function mechanism, as well
as the Saxon-defined "evaluate()" function, to
resolve indirectors and produce a "debug" report showing both the ultimate results of each non-indirect pointer
as well as the location paths represented by the indirector
elements in the test
document set. A set of simple test documents is also provided.
This style sheet processes either of the documents shown in A.2 to generate a debug report. It's output is an HTML document. It requires the use of an XSLT 1.0 implementation that implements the "functions" and "common" modules of EXSLT (e.g., Saxon 6.5). It uses a partial but fairly complete implementation of XPointer to do pointer resolution.
<?xml version='1.0'?> <xsl:stylesheet version="1.0" xmlns:xsl="http://www.w3.org/1999/XSL/Transform" xmlns:xlink="http://www.w3.org/TR/xlink" xmlns:xindr="http://www.isogen.com/papers/xindirection.xml" xmlns:xindrf="http://www.isogen.com/functions/xindirection" xmlns:saxon="http://icl.com/saxon" xmlns:func="http://exslt.org/functions" xmlns:fcommon="http://exslt.org/common" extension-element-prefixes="func xindrf" > <!-- This style sheet tests the XIndirect reference implementation in xindirect-functions.xsl. It is designed to be run against the simple test documents testdoc-01.xml and testdoc-02.xml provided with this style sheet. It produces a "debug" rendering of the hyperdocuments showing both the ultimate results of resolving indirect addresses as well as all intermediate results. Author: W. Eliot Kimber, eliot@isogen.com --> <!-- XIndirection function definitions --> <func:function name="xindrf:resolve-xpointer-url"> <!-- Given an element that exhibits an href attribute, attempts to resolve the URL and XPointer (if present) into a result node list. If there is no fragment identifier, acts as though the fragment identifier "#/" had been specified, returning the document root. --> <xsl:param name="pointer-node"/><!-- The Element node that exhibits the XPointer to be resolved --> <xsl:param name="indirector-treatment">as-indirector</xsl:param> <xsl:variable name="indirector-treatment-str" select="string($indirector-treatment)"/> <xsl:variable name="href" select="$pointer-node/@href"/> <xsl:choose> <xsl:when test="starts-with($href,'#')"> <xsl:variable name="fragid"> <xsl:value-of select="substring($href, 2)"/> </xsl:variable> <xsl:variable name="xpointer" select="xindrf:fragid2xpointer($fragid)"/> <!-- NOTE: error checking and reporting is done by resolve-xpointer --> <xsl:variable name="rns" select="xindrf:resolve-xpointer($pointer-node, $xpointer, $indirector-treatment-str)"/> <xsl:choose> <xsl:when test="string($rns) = ''"> <func:result select="/.."/> </xsl:when> <xsl:when test="fcommon:object-type($rns) != 'node-set'"> <func:result select="/.."/> </xsl:when> <xsl:otherwise> <func:result select="$rns"/> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:variable name="url"> <xsl:variable name="cand-url" select="substring-before($pointer-node/@href, '#')"/> <xsl:choose> <xsl:when test="$cand-url = ''"> <xsl:value-of select="$href"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$cand-url"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="cand-xpointer"> <xsl:value-of select="substring-after($href, '#')"/> </xsl:variable> <xsl:variable name="xpointer"> <xsl:choose> <xsl:when test="$cand-xpointer = ''"> <xsl:value-of select="string('/')"/><!-- Return the document element of the target document --> </xsl:when> <xsl:otherwise> <xsl:value-of select="xindrf:fragid2xpointer($cand-xpointer)"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="location-source-node" select="document($url, $pointer-node)"/> <xsl:variable name="rns" select="xindrf:resolve-xpointer($location-source-node, $xpointer, $indirector-treatment-str)"/> <xsl:choose> <xsl:when test="string($rns) = ''"> <func:result select="/.."/> </xsl:when> <xsl:when test="fcommon:object-type($rns) != 'node-set'"> <func:result select="/.."/> </xsl:when> <xsl:otherwise> <func:result select="$rns"/> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </func:function> <func:function name="xindrf:resolve-xpointer"> <!-- Resolves an xpointer in the context of some location source node. The location source is either the pointer, if the URL was just an XPointer, or it's the document element of the document addressed by the resource part of the URL. --> <xsl:param name="location-source-node"/> <xsl:param name="xpointer"/> <xsl:param name="indirector-treatment-str"/> <xsl:for-each select="$location-source-node"> <!-- Setting the context to the pointer node so that relative URLs are resolved relative to the pointer node by saxon:evaluate() --> <xsl:choose> <xsl:when test="$xpointer != ''"> <xsl:variable name="direct-result-set" select="saxon:evaluate($xpointer)"/> <xsl:choose> <xsl:when test="string($direct-result-set) = ''"> <xsl:message>XIndirect warning: XPointer "<xsl:value-of select="$xpointer"/>" did not address any nodes.</xsl:message> </xsl:when> <xsl:when test="fcommon:object-type($direct-result-set) != 'node-set'"> <xsl:message>XIndirect warning: XPointer "<xsl:value-of select="$xpointer"/>" did not address any nodes.</xsl:message> <func:result select="/.."/> </xsl:when> <xsl:otherwise> <func:result select="xindrf:resolve-indirectors($direct-result-set, $indirector-treatment-str)"/> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <xsl:message>XIndirect error: $xpointer value is '' in resolve-xpointer.</xsl:message> <func:result select="/.."/> </xsl:otherwise> </xsl:choose> </xsl:for-each> </func:function> <func:function name="xindrf:resolve-indirectors"> <!-- Given a node set of potential pointers, recurses through the list, resolving any indirections. The key to this function is the use of the node-set-union operator (|) to recursively construct the result node list. --> <xsl:param name="pointer-node-set" select="/.."/> <xsl:param name="indirector-treatment">as-indirector</xsl:param> <xsl:variable name="indirector-treatment-str" select="string($indirector-treatment)"/> <xsl:choose> <xsl:when test="$pointer-node-set"> <xsl:variable name="car" select="$pointer-node-set[1]"/> <xsl:variable name="cdr" select="$pointer-node-set[position() > 1]"/> <xsl:choose> <xsl:when test="$car[self::xindr:indirector] and ($indirector-treatment-str != 'as-resource')"> <xsl:variable name="rns" select="xindrf:resolve-xpointer-url($car, $car/@indirector-treatment) | xindrf:resolve-indirectors($cdr, $indirector-treatment-str)"/> <func:result select="$rns"/> </xsl:when> <xsl:otherwise> <xsl:variable name="rns" select="$car | xindrf:resolve-indirectors($cdr, $indirector-treatment-str)"/> <func:result select="$rns"/> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <func:result select="/.."/> </xsl:otherwise> </xsl:choose> </func:function> <func:function name="xindrf:fragid2xpointer"> <xsl:param name="fragid"/> <!-- Given a fragment identifier string, attempts to interpret it as an XPointer. --> <!-- NOTE: does not: - Handle multi-part XPointers: "#xpointer(foo)xpointer(bar)" - Skip non-xpointer schemes Doing this would require more sophisticated string processing than I can reasonably do in XSLT. --> <xsl:choose> <xsl:when test="starts-with($fragid, 'xpointer(')"> <xsl:variable name="first-part" select="substring-after($fragid, 'xpointer(')"/> <xsl:variable name="len" select="(string-length($first-part) - 1)"/> <xsl:variable name="xpointer" select="substring($first-part,1,$len)"/> <func:result select="$xpointer"/> </xsl:when> <xsl:when test="not(contains($fragid, '/')) and not(contains($fragid, '[')) and not(contains($fragid, '*')) and not(contains($fragid, '@'))"> <!-- Probably a bare name --> <func:result select="concat('id(', $fragid, ')')"/> </xsl:when> <xsl:when test="contains($fragid, '/') and not(contains($fragid, '[')) and not(contains($fragid, '*')) and not(contains($fragid, '@'))"> <!-- Probably a child sequence --> <xsl:variable name="barename" select="substring-before($fragid, '/')"/> <xsl:choose> <xsl:when test="$barename = '' and contains(translate($fragid, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'), '^')"> <func:result select="xindrf:xpointer-error($fragid)"/> </xsl:when> <xsl:when test="$barename = ''"> <xsl:message>fragid='<xsl:value-of select="$fragid"/>'</xsl:message> <xsl:variable name="childseq" select="xindrf:build-child-sequence($fragid)"/> <func:result select="$childseq"/> </xsl:when> <xsl:otherwise> <xsl:variable name="idref" select="concat('id(', $barename, ')')"/> <xsl:variable name="xpointer-childseq" select="substring($fragid, (string-length($barename) + 1))"/> <xsl:choose> <xsl:when test="contains(translate($xpointer-childseq, 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ', '^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^'), '^')"> <func:result select="xindrf:xpointer-error($fragid)"/> </xsl:when> <xsl:otherwise> <func:result select="concat('id(', $barename, ')', xindrf:build-child-sequence($xpointer-childseq))"/> </xsl:otherwise> </xsl:choose> </xsl:otherwise> </xsl:choose> </xsl:when> <xsl:otherwise> <func:result select="xindrf:xpointer-error($fragid)"/> </xsl:otherwise> </xsl:choose> </func:function> <func:function name="xindrf:build-child-sequence"> <xsl:param name="xptr-childseq"/> <xsl:choose> <xsl:when test="not(starts-with($xptr-childseq, '/'))"> <func:result select="xindrf:xpointer-error($xptr-childseq)"/> </xsl:when> <xsl:otherwise> <xsl:variable name="temp" select="substring($xptr-childseq, 2)"/><!-- strip leading "/" --> <func:result select="xindrf:construct-child-sequence($temp)"/> </xsl:otherwise> </xsl:choose> </func:function> <func:function name="xindrf:construct-child-sequence"> <xsl:param name="xptr-child-seq"/> <xsl:param name="xpath-child-seq"/> <xsl:variable name="child-num"> <xsl:choose> <xsl:when test="contains($xptr-child-seq, '/')"> <xsl:value-of select="concat('/*[', substring-before($xptr-child-seq, '/'), ']')"/> </xsl:when> <xsl:otherwise> <xsl:value-of select="$xptr-child-seq"/> </xsl:otherwise> </xsl:choose> </xsl:variable> <xsl:variable name="rest" select="substring-after($xptr-child-seq, '/')"/> <xsl:choose> <xsl:when test="$rest = ''"> <func:result select="$xpath-child-seq"/> </xsl:when> <xsl:otherwise> <func:result select="xindrf:construct-child-sequence($rest, concat($xpath-child-seq, $child-num))"/> </xsl:otherwise> </xsl:choose> </func:function> <func:function name="xindrf:xpointer-error"> <!-- Reports an XPointer error and returns "/.." --> <xsl:param name="fragid"/> <xsl:message >XPointer error: fragment identifier "<xsl:value-of select="$fragid"/>" is not a valid XPointer. Returning "/.." as XPath to resolve (empty node set)</xsl:message> <func:result select="concat('/', '..')"/> </func:function> <xsl:output method="xml" indent="no" omit-xml-declaration="no" encoding="UTF-8" /> <!-- End of XIndirect function definitions --> <xsl:template match="/"> <html> <body> <div> <hr/> <h2>Input Document</h2> <pre> <xsl:apply-templates mode="echo-markup"/> </pre> </div> <div> <hr/> <h2>Debug Report</h2> <xsl:apply-templates select="//links"/> <xsl:apply-templates select="//xindr:indirectorset"/> <xsl:apply-templates select="//paras"/> </div> </body> </html> </xsl:template> <xsl:template match="links"> <div> <h2>Links</h2> <table border="1" width="100%"> <xsl:apply-templates/> </table> </div> </xsl:template> <xsl:template match="xlink:simple"> <tr> <td><a name="{generate-id()}" /><xsl:text>[</xsl:text ><xsl:value-of select="generate-id()" /><xsl:text>] Initial pointer: </xsl:text> <xsl:value-of select="@href"/><xsl:text>: </xsl:text> <xsl:apply-templates/> <br/><xsl:text>Direct targets: </xsl:text> <xsl:apply-templates select="xindrf:resolve-xpointer-url(., 'as-resource')" mode="generate-link-to"/> </td> <td> <xsl:text>Ultimate targets: </xsl:text> <xsl:variable name="members" select="xindrf:resolve-xpointer-url(.)"/> <xsl:for-each select="$members"> <br/> <a href="#{generate-id()}" ><xsl:value-of select="generate-id()" /></a>: <code><xsl:apply-templates select="." mode="echo-markup"/></code> </xsl:for-each> </td> </tr> </xsl:template> <xsl:template match="paras"> <div> <h2>Paragraphs</h2> <xsl:apply-templates/> </div> </xsl:template> <xsl:template match="para"> <p ><a name="{generate-id()}" /><xsl:text>[</xsl:text ><xsl:value-of select="generate-id()" /><xsl:text >]</xsl:text ><xsl:apply-templates /></p> </xsl:template> <xsl:template match="xindr:indirectorset"> <div> <h2>Indirectors</h2> <table border="1" width="100%"> <xsl:apply-templates/> </table> </div> </xsl:template> <xsl:template match="xindr:indirector"> <xsl:variable name="direct-target" select="xindrf:resolve-xpointer-url(., 'as-resource')"/> <tr> <td><a name="{generate-id()}" /><xsl:text>[</xsl:text ><xsl:value-of select="generate-id()" /><xsl:text>] Pointer: '</xsl:text ><xsl:value-of select="@href" /><xsl:text>'. </xsl:text ><br/><xsl:text>Comment: </xsl:text ><xsl:apply-templates/> </td> <td> <xsl:text>Direct targets: </xsl:text > <xsl:apply-templates select="$direct-target" mode="generate-link-to"/> </td> </tr> </xsl:template> <xsl:template match="*" mode="generate-link-to"> <br/> <a href="#{generate-id()}" ><xsl:value-of select="generate-id()" /></a>: <code><xsl:apply-templates select="." mode="echo-markup"/></code> </xsl:template> <xsl:template name="echo-element-markup"> <xsl:text><</xsl:text><xsl:value-of select="name()"/> <xsl:for-each select="./attribute::*"> <xsl:text> </xsl:text><xsl:value-of select="name()"/>=<xsl:value-of select="."/> </xsl:for-each> <xsl:text>></xsl:text> <xsl:apply-templates mode="echo-markup"/> <xsl:text><</xsl:text><xsl:value-of select="name()"/><xsl:text>></xsl:text> </xsl:template> <xsl:template match="*" mode="echo-markup"> <xsl:call-template name="echo-element-markup"/> </xsl:template> </xsl:stylesheet>
This test set consists of two documents, testdoc-01.xml and testdoc-02.xml. The testdoc-01.xml document is completely self contained. It serves to demonstrate both different configurations of pointers and indirector as well as failure and exception conditions. The testdoc-02.xml document demonstrates cross-document indirect links from itself to testdoc-01.xml.
These documents use XML IDs but do not use the id() function in order to avoid problems with DTD-unaware process (including processing of intermediary result trees within an XSLT transform. The //*[@id='foo'] pattern is reliable in all processing contexts as long as all ID-type attributes have the name "id", which is a common convention and the convention used in these samples.
<?xml version="1.0"?> <xindrtest xmlns:xindr="http://www.isogen.com/papers/xindirection.xml" xmlns:xlink="http://www.w3.org/TR/xlink" > <links> <xlink:simple href="#xpointer(//*[@id='addr-01'])">indirect link to para 1</xlink:simple> <xlink:simple href="#xpointer(//*[@id='addr-02'])">indirect link to para 2</xlink:simple> <xlink:simple href="#xpointer(//*[@id='addr-03'])">double indirect link to para 2</xlink:simple> <xlink:simple href="#xpointer(//*[@id='addr-04'])">2nd double indirect link to para 2</xlink:simple> <xlink:simple href="#xpointer(//para[position() < 3])">direct link to both the paras</xlink:simple> <xlink:simple href="#xmlns(xindr=http://www.isogen.com/papers/xindirection.xml) xpointer(//xindr:indirector[position() < 3])">link to paras 1 and 2</xlink:simple> <xlink:simple href="#xmlns(xindr=http://www.isogen.com/papers/xindirection.xml) xpointer(//xindr:indirector[position() < 4])">link to paras 1, 2, and 3</xlink:simple> <xlink:simple href="#xpointer(//para/@foo)">direct link to foo attribute of para 1</xlink:simple> <xlink:simple href="#xpointer(//para/@foo='bar')">invalid xpointer</xlink:simple> <xlink:simple href="#an-id-value">invalid xpointer (bare name with no DTD)</xlink:simple> <xlink:simple href="#//*[@id='addr-01']">Invalid URL (bare XPath as fragment ID)</xlink:simple> <xlink:simple href="#/foo/bar/baz">Invalid XPointer (bare XPath)</xlink:simple> </links> <paras> <para foo="bar">This is the first para</para> <para>This is the second para</para> <para>This is the third para</para> </paras> <xindr:indirectorset> <xindr:indirector id="addr-01" href="#xpointer(/*/paras/para[1])">pointer to para 1</xindr:indirector> <xindr:indirector id="addr-02" href="#xpointer(/*/paras/para[2])">pointer to para 2</xindr:indirector> <xindr:indirector id="addr-05" href="#xpointer(/*/paras/para[3])">pointer to para 3</xindr:indirector> <xindr:indirector id="addr-03" href="#xmlns(xindr=http://www.isogen.com/papers/xindirection.xml) xpointer(../xindr:indirector[2])">pointer to indirector "addr-02"</xindr:indirector> <xindr:indirector id="addr-04" href="#xmlns(xindr=http://www.isogen.com/papers/xindirection.xml) xpointer(//xindr:indirectorset/xindr:indirector[2])">2nd pointer to indirector "addr-02"</xindr:indirector> </xindr:indirectorset> </xindrtest>
<?xml version="1.0"?> <xindrtest xmlns:xindr="http://www.isogen.com/papers/xindirection.xml" xmlns:xlink="http://www.w3.org/TR/xlink" > <links> <xlink:simple href="./testdoc-01.xml">direct link to doc element of doc 1</xlink:simple> <xlink:simple href="#xpointer(//*[@id='addr-02'])">indirect link to doc element of doc 1</xlink:simple> <xlink:simple href="./testdoc-01.xml#xpointer(//*[@id='addr-01'])">indirect link to para 1 in doc1</xlink:simple> <xlink:simple href="#xpointer(//*[@id='addr-01'])">indirect link to para 1 in doc1 through indirector in this doc.</xlink:simple> </links> <xindr:indirectorset> <xindr:indirector id="addr-01" href="./testdoc-01.xml#xpointer(/*/paras/para[1])">pointer to para 1 in doc1</xindr:indirector> <xindr:indirector id="addr-02" href="./testdoc-01.xml">pointer to doc elem of doc 1</xindr:indirector> </xindr:indirectorset> </xindrtest>
To run the tests, simply use Saxon to apply the test-xindirect style sheet against either of the test documents. The output is HTML:
c:\> saxon testdoc-01 test-xindirect.xsl > test-result-01.html
There should be no error messages. To verify the output, examine the resulting HTML document.