XInclude Support

(1Q18)


eXist-db comes with partial support for the XInclude standard. By default, eXist-db's XML serializer will scan all XML fragments for XInclude tags.

Introduction

The XInclude processor is implemented as a filter in between the serializer's output event stream and the receiver. If it finds an XInclude element, it will try to expand it. XInclude processing is therefore applied whenever eXist-db serializes an XML fragment,whether it's a document, the result of an XQuery or an XSLT stylesheet.

eXist-db's does not support the full XInclude standard:

  • You cannot include raw text, only XML.

  • XPointers are restricted to XPath. Additional features defined in the XPointer spec (points and locations) are not supported. However, you can use XPath functions to partly work around this and substitute XPointer's important string-range() function.

eXist-db expands XIncludes at serialization, which means that the query engine will see the XInclude tags before they are expanded. You therefore cannot query across XIncludes, unless you create your own code (e.g. an XQuery function) for it.

DTD entity declarations can be used for some of the things that XInclude can be used for. In general, however, XInclude is more powerful (with the exception that entity declarations are able include raw text).

The XInclude namespace (in the examples below bound to the prefix xi:) is:

http://www.w3.org/2001/XInclude

Including entire documents

To include an entire document, simply specify its path in the href attribute of an <xi:include> element.

For example, one can include a standard disclaimer, stored in the file short-disclaimer.xml, as follows: <xi:include href="/db/apps/doc/data/disclaimer-short.xml"/>

If the URI in the href attribute specifies a known scheme (like http: or file:), eXist-db will try to load it as an external resource. For example: <xi:include href="http://localhost:8080/exist/rest/db/apps/doc/data/disclaimer-short.xml"/>

If no scheme is specified, the XInclude processor will first try to load the referenced document from the database (relative to the current collection), and if that fails, from the file system.

If the document that contains the XInclude has been constructed in an XQuery (see Including XQuery results), relative file system paths will be resolved relative to the main XQuery module source file.

Selecting parts of a resource

The xpointer attribute of the <xi:include> element can be used to identify which part of the resource to include.

Selection by identifier

If the XPointer contains a simple value it is interpreted as an identifier. This will select the element in the target document that has a matching attribute of type identifier (ID) (usually xml:id).

For example, assume an XInclude target document disclaimer.xml looks like this:

<disclaimer>
  <p xml:id="p1">
    First disclaimer ...
  </p>
  <p xml:id="p2">
    Second disclaimer ...
  </p>
</disclaimer>

the following XInclude selects the element from disclaimer.xml with an identifier attribute with value p1: <xi:include href="disclaimer.xml" xpointer="p1"/>

When there are multiple instances of the same identifier attribute, only the first instance will be used.

Selecting by XPath expression

We can also use an XPath expression to select fragments. For this the xpointer attribute must contain an XPointer, which consists of so called "schemes". An XPath expression can be passed through the xpointer() XPointer scheme.

The following expression includes the first stage direction in Shakespeare's Macbeth:

<xi:include href="/db/apps/demo/data/macbeth.xml" xpointer="xpointer(//PLAY/ACT[1]/SCENE[1]/STAGEDIR[1])"/>

Selecting by a search expression

XIncludes can also perform searches, for instance using a full-text search:

<xi:include href="/db/apps/demo/data/hamlet.xml" xpointer="xpointer(//SPEECH[ft:query(., '" slings="slings" and="and" arrows"')/LINE[1])"="arrows"')/LINE[1])""/>

The XPath expression will be applied to the entire collection if the path in href points to a collection and not a single document:

<xi:include href="/db/apps/demo/data" 
  xpointer="xpointer(//SPEECH[ft:query(., '"cursed spite"')]/LINE[1])"/>

In all cases, only the first hit is retrieved.

Namespace mappings

All namespace/prefix mappings declared in the source document are passed to the query context. If necessary you can declare additional namespace mappings using xmlns(), like this:

<xi:include href="disclaimer.xml" xpointer="xpointer(//comment:comment)
  xmlns(comment=http://test.org)"/>

Including XQuery results

It is possible to include the result of an XQuery script by XInclude. If the target of an XInclude reference points to an XQuery document stored in the database (binary resource with MIME type application/xquery), the XInclude processor will attempt to compile and execute this query and use its results.

For example:<xi:include href="display-collection.xq"/>

The XInclude processor declares two variables in the XQuery's static context:

$xinclude:current-doc

The name of the document which XInclude-s the query (without its collection path)

$xinclude:current-collection

the collection in which the current document resides

You can also pass additional parameters to the XQuery script: <xi:include href="testvars.xq?var1=Hello&var2=World"/>

The parameters var1 and var2 will be available to the XQuery as an external global variable. The XQuery needs to declare them:

declare variable $var1 external;
declare variable $var2 external;

Error Handling

An error is raised if you try to XInclude a resource which does not exist. You can specify a fallback to avoid this. The result of the XInclude will be the content of the <xi:fallback> element:

<xi:include href="I-do-not exist.xml">
  <xi:fallback>
    <p>
      The included document was not found!
    </p>
  </xi:fallback>
  <xi:include>

A fallback element itself cannot contain XInclude elements.

Implementing XPointer string-range()

One reason why the XPointer spec has hardly seen any implementation is that it operates with "points" and "locations" which have no equivalent in the XPath Data Model. However, a major use case for the XPointer specification is to allow pointing at a range of characters inside an element. Luckily, we can work around the absence of this using the XPath functions string-join() and substring().

In the following example, a range of characters in the text node of a <LINE> element is extracted, 20 characters long, starting at the 22nd character:

<xi:include href="/db/apps/demo/data" xpointer="xpointer(
      substring(string-join(//PLAY/ACT[3]/SCENE[1]/SPEECH[19]/LINE[1],''), 22, 20)
    )"/>

Since only string contents is involved, these ranges can also work on text nodes belonging to different elements. In the following example, parts of succeeding lines are extracted from the SPEECH element:

<xi:include href="/db/apps/demo/data" xpointer="xpointer(
      substring(string-join(//PLAY/ACT[3]/SCENE[1]/SPEECH[19], ''), 202, 24)
    )"/>