W3C

XML Indirection Facility

W3C Note 12 June 2003

This version:
http://www.w3.org/TR/2003/NOTE-XIndirect-20030612
Latest version:
http://www.w3.org/TR/XIndirect
Previous version:
This is the first version.
Editors:
W. Eliot Kimber, ISOGEN International, LLC, eliot@isogen.com

Abstract

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.

Status of this document

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.

Table of contents

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

Appendixes

A Sample XIndirect Implementation (Informative)
    A.1 XIndirect Test Style Sheet
    A.2 XIndirect Test Documents
    A.3 Running the Tests


1 Introduction

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.

1.1 Notation

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").

2 Processing Model

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.

3 Data Model

The data model for XIndirect consists of four types: Linker, Pointer, Resource, and Indirector.

3.1 Linker, Pointer, And Resource Data Types

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).

Linker data model diagram

Figure 1. Linker Data Type

3.2 Indirector Data Type

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.

Indirector data model diagram

Figure 2. Indirector Data Type

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.

3.3 Location Path

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.]

Location Path data model diagram

Figure 3. Location Path

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.

4 XML Representation

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.

4.1 The indirector Element

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
Specifies the Indirector's pointer. Currently the only recognized addressing syntax is XPointer.
id
The unique identifier of the 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;
>

4.2 Indirector Treatment Attributes

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
Indicates whether or not any indirectors addressed by the Linker or Indirector are to be treated as resources or as Indirectors. The possible values are "as-indirector" or "as-resource". When the value is "as-resource" the indirector is not interpreted as an indirector and is instead returned directly.

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
Specifies the maximum number of indirections to be processed from a Linker or Indirector in the construction of a single location path. A value of zero ("0") indicates that there is no defined limit (although XIndirect processors may impose their own limits).

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"
'>

4.3 The indirectorset Element

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*)
>

5 XIndirector Processor

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.


Acknowledgments

Thanks to Eve Maler for reviewing this document and providing invaluable feedback.

References

[EXSLT]
EXSLT project, exstl.org (see http://www.exslt.org/).
[HTML]
Dave Raggett, Arnaud Le Hors, and Ian Jacobs, editors, HTML 4.01 Specification. World Wide Web Consortium, 1999. (See http://www.w3.org/TR/1999/REC-html401-19991224/.)
[HYTIME]
ISO/IEC 10744:1997 (E). Information technology --Hypermedia/Time-based Structuring Language (HyTime). Geneva: International Organization for Standardization, 1997. (See http://www.ornl.gov/sgml/wg8/docs/n1920/.)
[UML]
Unified Modeling Language™ (UML™). Object Management Group, 2001. (See http://www.omg.org/technology/documents/formal/uml.htm.)
[XLINK]
Steve DeRose, Eve Maler, David Orchard, and Ben Trafford, editors. XML Linking Language (XLink). World Wide Web Consortium, 2000. (See http://www.w3.org/TR/xlink/.)
[XML]
Tim Bray, Jean Paoli, C.M. Sperberg-McQueen, and Eve Maler, editors. Extensible Markup Language (XML) 1.0 (Second Edition). World Wide Web Consortium, 1998. (See http://www.w3.org/TR/REC-xml.)
[XPTR]
Paul Grosso, Eve Maler, Jonathan Marsh, Norman Walsh editors. XPointer Framework. (See http://www.w3.org/TR/xptr-framework/.)
Paul Grosso, Eve Maler, Jonathan Marsh, Norman Walsh editors. XPointer element() Scheme. (See http://www.w3.org/TR/xptr-element/.)
Steven J. DeRose, Ron Daniel Jr., Eve Maler, Jonathan Marsh editors. XPointer xmlns() Scheme. (See http://www.w3.org/TR/xptr-xmlns/.)
Steven J. DeRose, Ron Daniel Jr., Eve Maler editors. XPointer xpointer() Scheme. (See http://www.w3.org/TR/xptr-xmlns/.)

Appendix A. Sample XIndirect Implementation (Informative)

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.

A.1 XIndirect Test Style Sheet

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>

A.2 XIndirect Test Documents

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.

testdoc-01.xml

<?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>

testdoc-02.xml

<?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>

A.3 Running the Tests

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.