DocBook Transclusion

Jirka Kosek

08 Jan 2015

1. Introduction

This document describes the syntax, semantics, and processing model for the DocBook transclusion mechanism. Please be aware that this is a working draft – everything described below might change or disappear completely. This proposal addresses the issues described in Requirements for transclusion in DocBook. The DocBook TC welcomes feedback on this draft, especially from users and developers of DocBook authoring and processing tools. Please direct your comments to the DocBook mailing list by sending email to .

Note

Previous version of this draft proposed the new elements ref and def for implementing transclusions. Meanwhile DocBook TC decided that transclusions should rely as much as possible on standard technologies. XInclude 1.1 added new features that allow to implement transclusion features on top of the XInclude. As a result transclusions were completely redesigned to be layered on top of XInclude 1.1.

This resulted in a loss of some features. For example, it's no longer possible to locally redefine text replacement (see this example from the April 20, 2014 draft of this document).

The following namespace bindings are used as prefixes in this document:

trans

DocBook transclusion namespace (http://docbook.org/ns/transclusion)

db

DocBook namespace (http://docbook.org/ns/docbook)

xi

XInclude namespace (http://www.w3.org/2001/XInclude)

local

XInclude namespace for copying attributes without a namespace (http://www.w3.org/2001/XInclude/local-attributes)

2. Transclusion processing

The processing model for transclusion is simple and cosists of the following steps:

  1. Normal XInclude 1.1 processing on the input document.

  2. DocBook transclusion processing on the result to fix problems such as duplicate IDs and broken cross-references.

  3. Normal processing on the resulting document using the DocBook stylesheets or equivalent tools.

Transclusion processing is controlled by attributes from the http://docbook.org/ns/transclusion namespace. These attributes are typically inserted into a document by using the attribute copying feature of XInclude 1.1 [], which is described in section 4.3 Attribute Copying when processing XML of the XInclude 1.1 specification.

The transclusion processor copies documents node by node. For most nodes this is an identity transformation. The few exceptions are controlled by transclusion properties. Transclusion properties suffix and linkscope are defined for each node in the document as follows:

suffix
  • Defines a value to be appended to all ID values on the element (elements and attributes whose values should be treated as IDs are listed in the ID-list property)
  • Default is an empty string
  • Inherited
  • Can be changed by the trans:idfixup and trans:suffix attributes
linkscope
  • Defines how to correct ID references (elements and attributes whose values should be treated as ID references are listed in the IDREF-list property)
  • Allowed values are user, local, near, and global
  • Default value is near
  • Inherited
  • Can be changed by the trans:linkscope attribute

For each document type there are two properties, ID-list and IDREF-list, which control the transclusion process. The ID-list property defines the attributes and elements whose values should be treated as IDs. Similarly, the IDREF-list property defines the attributes and elements whose values should be treated as ID references. These properties are defined for DocBook 5.0 in Definition of ID-list and IDREF-list properties for DocBook 5.0. Transclusion processors are free to support the ID-list and IDREF-list properties with other document types, for example DocBook 4.x or TEI.

ID references identified in the IDREF-list are corrected as follows:

When linkscope=user

No adjustment is made.

When linkscope=local

The value of the suffix property is added to every ID reference as a suffix.

When linkscope=near

Each ID reference is adjusted to point to the closest element that has a matching ID.

To find the closest element, the parent element of the element that contains the ID reference is searched for a matching ID, followed by that element's descendants. If no matching ID is found, that element's parent is inspected in the same way. This continues until a match is found or the root element is reached.

When linkscope=global

Each ID reference is adjusted to point to the first element in document order that has a matching ID.

A matching ID does not mean exact string equality between ID and IDREF values. A matching ID means that the values of an ID and IDREF match after removing any added suffixes.

Transclusion properties can be set on any element using the following attributes:

trans:idfixup attribute
value none

The suffix property is set to an empty string.

value suffix

The suffix property is set to the concatenation of the inherited suffix value and the value specified in the trans:suffix attribute.

value auto

The suffix property is set to a unique value for each element.[1]

trans:suffix attribute

This attribute defines the value of the suffix property used when trans:idfixup="suffix".

It's an error to use this attribute when the attribute/value pair trans:idfixup="suffix" is not also on the element.

trans:linkscope attribute

Sets the value of the linkscope property. Permitted values are: user, local, near, and global.

During the transclusion process all attributes from the transclusion namespace are removed from the resulting document.

Definition of ID-list and IDREF-list properties for DocBook 5.0

In DocBook 5.0, the ID-list and IDREF-list properties contain the attributes shown here:

ID-list

xml:id

IDREF-list

linkend, linkends, otherterm, zone, startref, arearefs, targetptr, and endterm.

The href attribute in the XLink namespace is considered a member of IDREF-list when its value begins with #.

A. Using XInclude 1.1 features for your content

The most common transclusion scenario is reuse of shared strings (see https://docbook.org/docs/transclusion-requirements/transclusion-requirements.html#uc-1). With XInclude 1.0 this could only be done using XPointer schemes that were not very interoperable. With XInclude 1.1, doing this is much easier.

Let's assume we have defined a set of shared strings in separate document (see Example A.1, “Definitions stored in a separate document (definitions.001.xml)”).

Example A.1. Definitions stored in a separate document (definitions.001.xml)
<?xml version="1.0" encoding="UTF-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Placeholder for definitions</title>
  <para>
    <phrase xml:id="product-version">3.14</phrase>
    <phrase xml:id="product-name">FooWiz</phrase>
    <phrase xml:id="corp-name">ACME Inc.</phrase>
  </para>
</article>

If you transclude parts of this document as shown in Example A.2, “Transclusion with duplicate IDs”, you will end up with duplicate IDs. In this example, the same definition is included twice. Because the ID attributes are passed through unchanged, the ID value “product-name” occurs twice in the resulting document.

Example A.2. Transclusion with duplicate IDs
<?xml version="1.0" encoding="UTF-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0"
  xmlns:xi="http://www.w3.org/2001/XInclude">
  <info>
    <title>Transclusions demo</title>
  </info>
  <para>The latest version of
    <application><xi:include href="definitions.001.xml" xpointer="product-name"/></application> from
    <xi:include href="definitions.001.xml" xpointer="corp-name"/>
    is <xi:include href="definitions.001.xml" xpointer="product-version"/>.</para>
  <para>You can buy <xi:include href="definitions.001.xml" xpointer="product-name"/> in our on-line store.</para>
</article>

Result of transclusion:

<?xml version="1.0" encoding="UTF-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0">
  <info>
      <title>Transclusions demo</title>
  </info>
  <para>The latest version of
    <application>
         <phrase xml:base="definitions.001.xml" xml:id="product-name">FooWiz</phrase>
      </application> from
    <phrase xml:base="definitions.001.xml" xml:id="corp-name">ACME Inc.</phrase>
    is <phrase xml:base="definitions.001.xml" xml:id="product-version">3.14</phrase>.</para>
  <para>You can buy <phrase xml:base="definitions.001.xml" xml:id="product-name">FooWiz</phrase> in our on-line store.</para>
</article>

XInclude 1.1 has an attribute, set-xml-id, which can be used to change or remove the xml:id attribute on included content. Example A.3, “Using set-xml-id to remove the top-level ID during transclusion” uses this attribute to avoid duplicate IDs.

Example A.3. Using set-xml-id to remove the top-level ID during transclusion
<?xml version="1.0" encoding="UTF-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0"
  xmlns:xi="http://www.w3.org/2001/XInclude">
  <info>
    <title>Transclusions demo</title>
  </info>
  <para>The latest version of
    <application><xi:include href="definitions.001.xml" xpointer="product-name" set-xml-id=""/></application> from
    <xi:include href="definitions.001.xml" xpointer="corp-name" set-xml-id=""/>
    is <xi:include href="definitions.001.xml" xpointer="product-version" set-xml-id=""/>.</para>
  <para>You can buy <xi:include href="definitions.001.xml" xpointer="product-name" set-xml-id=""/> in our on-line store.</para>
</article>

Result of transclusion:

<?xml version="1.0" encoding="UTF-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0">
  <info>
      <title>Transclusions demo</title>
  </info>
  <para>The latest version of
    <application>
         <phrase xml:base="definitions.001.xml">FooWiz</phrase>
      </application> from
    <phrase xml:base="definitions.001.xml">ACME Inc.</phrase>
    is <phrase xml:base="definitions.001.xml">3.14</phrase>.</para>
  <para>You can buy <phrase xml:base="definitions.001.xml">FooWiz</phrase> in our on-line store.</para>
</article>

Another new XInclude 1.1 feature lets you override attributes on included content. Example A.4, “Overriding DocBook attributes on inclusion” shows how to use this feature to override effectivity attributes. This feature works by placing attributes from a specially defined namespace (http://www.w3.org/2001/XInclude/local-attributes) on the xi:include element. These attributes define the values that should be used to replace attributes of the same name, but no namespace, in the transcluded content.

Example A.4. Overriding DocBook attributes on inclusion

Consider section (section.001.xml), which is labeled for the Linux operating system (os="linux").

<?xml version="1.0" encoding="UTF-8"?>
<section xmlns="http://docbook.org/ns/docbook"
    version="5.0"
    os="linux">
    <title>Installation</title>
    <para>Text</para>
</section>

Now we want to include this section in another document and pretend that it's actually targeted at a BSD system.

<?xml version="1.0" encoding="UTF-8"?>
<article xmlns="http://docbook.org/ns/docbook"
    xmlns:xi="http://www.w3.org/2001/XInclude"
    xmlns:local="http://www.w3.org/2001/XInclude/local-attributes"
    version="5.0">
  <title>Sample article</title>
  <xi:include href="section.001.xml" local:os="bsd"/>
</article>

The value of the local:os attribute replaces the value of the os attribute during the XInclude process.

<?xml version="1.0" encoding="UTF-8"?>
<article xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Sample article</title>
  <section version="5.0" os="bsd" xml:base="section.001.xml">
      <title>Installation</title>
      <para>Text</para>
   </section>
</article>

B. Special ID/IDREF processing

Transcluded content can contain xml:id attributes. If one fragment is transcluded more than once, then the resulting document after transclusion will contain duplicate IDs. The same problem can occur if the same ID is used in two different transcluded modules. To overcome this problem, IDs and ID references can be adjusted during the transclusion process.

The trans:idfixup attribute on the xi:include element determines how IDs are adjusted during transclusion.

Of course, if IDs are adjusted then all corresponding ID references also have to be adjusted.

These adjustments are controlled by the trans:idfixup and trans:linkscope attributes. The following examples show the effect of using those two attributes. Each example transcludes the procedure shown in Example B.1, “Module with sample procedure”, which contains one internal link and one external link.

Example B.1. Module with sample procedure
<?xml version="1.0" encoding="UTF-8"?>
<procedure xmlns="http://docbook.org/ns/docbook" xml:id="paper-insert">
  <title>Inserting paper into printer</title>
  <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
  <step xml:id="s1"><para>Make sure that you have paper.</para></step>
  <step><para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1"/></para></step>
</procedure>

Example B.2, “Automatic ID/IDREF adjustment” transcludes this module twice to show how we can deal with the duplicate ID problem.

Example B.2. Automatic ID/IDREF adjustment
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      xmlns:trans="http://docbook.org/ns/transclude"
      version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
    <title>Buying printer</title>
    <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
    <title>Quick installation guide</title>
    <para>Carefully follow all procedures below.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="auto"/>
  </chapter>
  <chapter>
    <title>Maintenance</title>
    <para>Be friendly to your printer when you speak to it.</para>
    <para>If the green led is blinking, please add missing paper using the following procedure.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="auto"/>
  </chapter>
</book>

Result of transclusion:

<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
      <title>Buying printer</title>
      <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
      <title>Quick installation guide</title>
      <para>Carefully follow all procedures below.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert---d1e23">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1---d1e23">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1---d1e23"/>
            </para>
         </step>
      </procedure>
  </chapter>
  <chapter>
      <title>Maintenance</title>
      <para>Be friendly to your printer when you speak to it.</para>
      <para>If the green led is blinking, please add missing paper using the following procedure.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert---d1e56">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1---d1e56">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1---d1e56"/>
            </para>
         </step>
      </procedure>
  </chapter>
</book>

Specifying db:idfixup triggers the automatic ID/IDREF fixup. All IDs in the transcluded modules are automatically suffixed to prevent ID collisions. Then, IDREFs are fixed so that links point to the nearest possible target. In Example B.2, “Automatic ID/IDREF adjustment”, the link from step 2 to step 1 in the procedure always points to the same instance of the procedure. However, the “buy” link correctly points to the target in the main document.

Example B.3, “Global linkscope” uses trans:linkscope="global" for the second transclusion. The result is that the link from step 2 in the second procedure links to step 1 in the first procedure.

Example B.3. Global linkscope
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      xmlns:trans="http://docbook.org/ns/transclude"
      version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
    <title>Buying printer</title>
    <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
    <title>Quick installation guide</title>
    <para>Carefully follow all procedures below.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="auto"/>
  </chapter>
  <chapter>
    <title>Maintenance</title>
    <para>Be friendly to your printer when you speak to it.</para>
    <para>If green led is blinking, please add paper using the following procedure.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="auto" trans:linkscope="global"/>
  </chapter>
</book>

Result of transclusion:

<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
      <title>Buying printer</title>
      <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
      <title>Quick installation guide</title>
      <para>Carefully follow all procedures below.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert---d1e23">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1---d1e23">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1---d1e23"/>
            </para>
         </step>
      </procedure>
  </chapter>
  <chapter>
      <title>Maintenance</title>
      <para>Be friendly to your printer when you speak to it.</para>
      <para>If green led is blinking, please add paper using the following procedure.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert---d1e56">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1---d1e56">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1---d1e23"/>
            </para>
         </step>
      </procedure>
  </chapter>
</book>

Example B.4, “Local linkscope” uses db:linkscope="local" on the first transclusion. This means that every link from this transclusion must point to an ID inside the transcluded content; no external links are allowed. Because the transcluded content does contain a link to an external ID (“buy”), this example is broken, since the transclusion process rewrites “buy” to be “buy---d1e23”, for which there is no corresponding target.

This method of transclusion can be useful if you are transcluding foreign content and want to isolate its links from the rest of your document.

Example B.4. Local linkscope
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      xmlns:trans="http://docbook.org/ns/transclude"
      version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
    <title>Buying printer</title>
    <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
    <title>Quick installation guide</title>
    <para>Carefully follow all procedures below.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="auto" trans:linkscope="local"/>
  </chapter>
  <chapter>
    <title>Maintenance</title>
    <para>Be friendly to your printer when you speak to it.</para>
    <para>If the green led is blinking, please add paper using the following procedure.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="auto"/>
  </chapter>
</book>

Result of transclusion:

<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
      <title>Buying printer</title>
      <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
      <title>Quick installation guide</title>
      <para>Carefully follow all procedures below.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert---d1e23">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy---d1e23">buying one</link>.</para>  
         <step xml:id="s1---d1e23">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1---d1e23"/>
            </para>
         </step>
      </procedure>
  </chapter>
  <chapter>
      <title>Maintenance</title>
      <para>Be friendly to your printer when you speak to it.</para>
      <para>If the green led is blinking, please add paper using the following procedure.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert---d1e56">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1---d1e56">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1---d1e56"/>
            </para>
         </step>
      </procedure>
  </chapter>
</book>

You can manually assign the value of the suffix used in transcluded content. Example B.5, “Manually assigned suffix” shows how to do this.

Although suffixes seem to be a better approach for automatically generated IDs, prefixes may be a better approach for manually specified IDs (for example when HTML filenames are generated based on the xml:id value). Should we put the prefix option back in the specification?
Example B.5. Manually assigned suffix
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      xmlns:trans="http://docbook.org/ns/transclude"
      version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
    <title>Buying printer</title>
    <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
    <title>Quick installation guide</title>
    <para>Carefully follow all procedures below.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="suffix" trans:suffix="_install-proc"/>
  </chapter>
  <chapter>
    <title>Maintenance</title>
    <para>Be friendly to your printer when you speak to it.</para>
    <para>If the green led is blinking, please add paper using the following procedure.</para>
    <xi:include href="procedure.001.xml" trans:idfixup="suffix" trans:suffix="_maintain-proc"/>
  </chapter>
</book>

Result of transclusion:

<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
      <title>Buying printer</title>
      <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
      <title>Quick installation guide</title>
      <para>Carefully follow all procedures below.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert_install-proc">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1_install-proc">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1_install-proc"/>
            </para>
         </step>
      </procedure>
  </chapter>
  <chapter>
      <title>Maintenance</title>
      <para>Be friendly to your printer when you speak to it.</para>
      <para>If the green led is blinking, please add paper using the following procedure.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert_maintain-proc">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1_maintain-proc">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1_maintain-proc"/>
            </para>
         </step>
      </procedure>
  </chapter>
</book>

By default, XInclude does not do any postprocessing. Thus, the resulting document in Example B.6, “Disabling ID fixup” contains duplicate IDs and is not valid.

Example B.6. Disabling ID fixup
<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
    <title>Buying printer</title>
    <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
    <title>Quick installation guide</title>
    <para>Carefully follow all procedures bellow.</para>
    <xi:include href="procedure.001.xml"/>
  </chapter>
  <chapter>
    <title>Maintenance</title>
    <para>Be friendly to your printer when you speak to it.</para>
    <para>If green led is blinking, please add missing paper using the following procedure.</para>
    <xi:include href="procedure.001.xml"/>
  </chapter>
</book>

Result of transclusion:

<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
      <title>Buying printer</title>
      <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
      <title>Quick installation guide</title>
      <para>Carefully follow all procedures bellow.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1"/>
            </para>
         </step>
      </procedure>
  </chapter>
  <chapter>
      <title>Maintenance</title>
      <para>Be friendly to your printer when you speak to it.</para>
      <para>If green led is blinking, please add missing paper using the following procedure.</para>
      <procedure xml:base="procedure.001.xml" xml:id="paper-insert">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1"/>
            </para>
         </step>
      </procedure>
  </chapter>
</book>
Example B.7. Chaining suffixes

Suppose we have note in a separate file (note.001.xml):

<?xml version="1.0" encoding="UTF-8"?>
<note xmlns="http://docbook.org/ns/docbook" xml:id="note">
  <para>Important note.</para>
</note>

We transclude it into another file (procedure.002.xml) assigning a suffix manually:

<?xml version="1.0" encoding="UTF-8"?>
<procedure xmlns="http://docbook.org/ns/docbook" xml:id="paper-insert"
  xmlns:xi="http://www.w3.org/2001/XInclude"
  xmlns:trans="http://docbook.org/ns/transclude">
  <title>Inserting paper into printer</title>
  <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
  <step xml:id="s1"><para>Make sure that you have paper.</para></step>
  <step><para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1"/></para></step>
  <step>
    <xi:include href="note.001.xml" trans:idfixup="suffix" trans:suffix="_note001"/>
  </step>  
</procedure>

Then we transclude this procedure into a master document, again with a manually specified suffix:

<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook"
      xmlns:xi="http://www.w3.org/2001/XInclude"
      xmlns:trans="http://docbook.org/ns/transclude"
      version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
    <title>Buying printer</title>
    <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
    <title>Maintenance</title>
    <para>Be friendly to your printer when you speak to it.</para>
    <para>If the green led is blinking, please add paper using the following procedure.</para>
    <xi:include href="procedure.002.xml" trans:idfixup="suffix" trans:suffix="_procedure002"/>
  </chapter>
</book>

The result is that the ID in the transcluded note contains a chained suffix “note_procedure002_note001”:

<?xml version="1.0" encoding="UTF-8"?>
<book xmlns="http://docbook.org/ns/docbook" version="5.0">
  <title>Definitive Printer Guide</title>
  <chapter xml:id="buy">
      <title>Buying printer</title>
      <para>Grab money, go to shop, ...</para>
  </chapter>
  <chapter>
      <title>Maintenance</title>
      <para>Be friendly to your printer when you speak to it.</para>
      <para>If the green led is blinking, please add paper using the following procedure.</para>
      <procedure xml:base="procedure.002.xml" xml:id="paper-insert_procedure002">
         <title>Inserting paper into printer</title>
         <para>This procedure is for printer owners.
    If you don't have a printer, consider <link linkend="buy">buying one</link>.</para>  
         <step xml:id="s1_procedure002">
            <para>Make sure that you have paper.</para>
         </step>
         <step>
            <para>Insert paper into printer. If you don't have paper, consult <xref linkend="s1_procedure002"/>
            </para>
         </step>
         <step>
            <note xml:base="note.001.xml" xml:id="note_procedure002_note001">
               <para>Important note.</para>
            </note>
         </step>  
      </procedure>
  </chapter>
</book>

C. DocBook schema with support for transclusions

TBD

D. Sample transclusion processor written in XSLT 2.0

Please note that this sample transclusion processor is not yet feature complete. It supports only a subset of the proposal.

<?xml version="1.0" encoding="utf-8"?>
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="2.0"
                xmlns:f="http://docbook.org/xslt/ns/extension"
                xmlns:mp="http://docbook.org/xslt/ns/mode/private"
                xmlns:db="http://docbook.org/ns/docbook"
                xmlns:xs="http://www.w3.org/2001/XMLSchema"
                xmlns:ta="http://docbook.org/ns/transclude"
                xmlns:xia="http://www.w3.org/2001/XInclude/local-attributes"
                exclude-result-prefixes="f mp xs ta xia">

<!-- Remove for production files, pretty print can harm mixed content -->
<xsl:output indent="yes"/>

<xsl:template match="/">
  <xsl:variable name="adjusted" select="f:adjust-ids(/)"/>
  <xsl:variable name="result" select="f:adjust-idrefs($adjusted)"/>
  <xsl:sequence select="f:transclude-cleanup($result)"/>
</xsl:template>

<!-- Separator for auto generated suffixes -->
<xsl:param name="psep" select="'---'"/>

<!-- Function and mode for changing IDs based on provided suffix -->
<xsl:function name="f:adjust-ids" as="node()+">
  <xsl:param name="doc" as="node()+"/>

  <xsl:apply-templates select="$doc" mode="mp:transclude"/>
</xsl:function>

<xsl:template match="node()" mode="mp:transclude">
  <xsl:param name="idfixup" select="'none'" tunnel="yes"/>
  <xsl:param name="suffix" tunnel="yes"/>
  <xsl:copy>
    <xsl:copy-of select="@* except @xml:id"/>
    <xsl:if test="@xml:id">
      <xsl:choose>
        <xsl:when test="($idfixup = 'none')">
          <xsl:copy-of select="@xml:id"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="xml:id" select="concat(@xml:id, $suffix)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:if>
    <xsl:apply-templates mode="mp:transclude"/>
  </xsl:copy>
</xsl:template>

<xsl:template match="node()[@ta:*]" mode="mp:transclude">
  <xsl:param name="idfixup" select="'none'" tunnel="yes"/>
  <xsl:param name="suffix" tunnel="yes"/>

  <xsl:variable name="new-idfixup" select="if (@ta:idfixup) then @ta:idfixup else $idfixup"/>
  <xsl:variable name="linkscope" select="if (@ta:linkscope) then @ta:linkscope else 'near'"/>
  <xsl:variable name="new-suffix">
    <xsl:choose>
      <xsl:when test="$new-idfixup = 'auto'">
        <xsl:sequence select="concat($psep, generate-id(.))"/>
      </xsl:when>
      <xsl:when test="$new-idfixup = 'suffix'">
        <xsl:sequence select="concat($suffix, @ta:suffix)"/>
      </xsl:when>
      <xsl:otherwise></xsl:otherwise>
    </xsl:choose>
  </xsl:variable>

  <xsl:copy>
    <xsl:copy-of select="@* except @xml:id"/>
    <xsl:if test="@xml:id">
      <xsl:choose>
        <xsl:when test="($new-idfixup = 'none')">
          <xsl:copy-of select="@xml:id"/>
        </xsl:when>
        <xsl:otherwise>
          <xsl:attribute name="xml:id" select="concat(@xml:id, $new-suffix)"/>
        </xsl:otherwise>
      </xsl:choose>
    </xsl:if>
    <xsl:attribute name="ta:suffix" select="$new-suffix"/>
    <xsl:apply-templates mode="mp:transclude">
      <xsl:with-param name="idfixup" select="$new-idfixup" tunnel="yes"/>
      <xsl:with-param name="suffix" select="$new-suffix" tunnel="yes"/>
    </xsl:apply-templates>
  </xsl:copy>  
</xsl:template>

<!-- Function and mode for adjusting references to IDs -->
<xsl:function name="f:adjust-idrefs" as="node()+">
  <xsl:param name="doc" as="node()+"/>

  <xsl:apply-templates select="$doc" mode="mp:adjust-idrefs"/>
</xsl:function>

<xsl:template match="node()|@*" mode="mp:adjust-idrefs">
  <xsl:copy>
    <xsl:apply-templates select="@* | node()" mode="mp:adjust-idrefs"/>
  </xsl:copy>
</xsl:template>

<!-- FIXME: add support for @linkends, @zone, @arearefs -->
<!-- FIEMX: add support for xlink:href starting with # -->
<xsl:template match="@linkend | @endterm | @otherterm | @startref" mode="mp:adjust-idrefs">
  <xsl:variable name="idref" select="."/>

  <xsl:variable name="annotation" select="ancestor-or-self::*[@ta:linkscope][1]"/>
  <xsl:variable name="linkscope" select="($annotation/@ta:linkscope, 'near')[1]"/>
  <xsl:variable name="suffix" select="$annotation/@ta:suffix"/>

  <xsl:attribute name="{local-name(.)}">
    <xsl:choose>
      <xsl:when test="$linkscope = 'user'">
        <xsl:value-of select="$idref"/>
      </xsl:when>
      <xsl:when test="$linkscope = 'local'">
        <xsl:value-of select="concat($idref, $suffix)"/>
      </xsl:when>
      <xsl:when test="$linkscope = 'near'">
        <xsl:value-of select="f:nearest-matching-id($idref, ..)"/>
      </xsl:when>
      <xsl:when test="$linkscope = 'global'">
        <xsl:value-of select="f:nearest-matching-id($idref, root(.))"/>
      </xsl:when>
    </xsl:choose>
  </xsl:attribute>
</xsl:template>

<!-- Function searches nearest matching ID in a given context -->
<xsl:function name="f:nearest-matching-id" as="xs:string?">
  <xsl:param name="idref" as="xs:string"/>
  <xsl:param name="context" as="node()"/>

  <!-- FIXME: key() requires document-node() rooted subtree -->
  <!--  <xsl:variable name="targets" select="key('unprefixed-id', f:unprefixed-id($idref, $context), $context)"/> -->
  <xsl:variable name="targets" select="$context//*[@xml:id][f:unprefixed-id(@xml:id, .) eq f:unprefixed-id($idref, $context)]"/> 

  <xsl:choose>
    <xsl:when test="not($targets) and $context/..">
      <xsl:sequence select="f:nearest-matching-id($idref, $context/..)"/>
    </xsl:when>
    <xsl:when test="$targets">
      <xsl:sequence select="$targets[1]/string(@xml:id)"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:message>Error: no matching ID for reference "<xsl:value-of select="$idref"/>" was found.</xsl:message>
    </xsl:otherwise>
  </xsl:choose>
</xsl:function>

<!-- FIXME: type annotation should be without ?, find why it is called with empty sequence -->
<xsl:function name="f:unprefixed-id" as="xs:string?">
  <xsl:param name="id" as="xs:string?"/>
  <xsl:param name="context" as="node()"/>
  
  <xsl:variable name="suffix" select="$context/ancestor-or-self::*[@ta:suffix][1]/@ta:suffix"/>

  <xsl:sequence select="if ($suffix) then substring-before($id, $suffix) else $id"/>
</xsl:function>

<!--
<xsl:key name="unprefixed-id" match="*[@xml:id]" use="f:unprefixed-id(@xml:id, .)"/>
-->

<!-- Function and mode for removing transclusion attributes from the final output -->
<xsl:function name="f:transclude-cleanup" as="node()+">
  <xsl:param name="doc" as="node()+"/>
  
  <xsl:apply-templates select="$doc" mode="mp:transclude-cleanup"/>
</xsl:function>
  
<xsl:template match="node()" mode="mp:transclude-cleanup">
  <xsl:copy>
    <xsl:apply-templates mode="mp:transclude-cleanup"/>
  </xsl:copy>
</xsl:template>      

<xsl:template match="*" mode="mp:transclude-cleanup" priority="10">
  <xsl:element name="{name()}" namespace="{namespace-uri()}">
    <xsl:copy-of select="@* except @ta:*"/>
    <xsl:apply-templates mode="mp:transclude-cleanup"/>
  </xsl:element>
</xsl:template>      
  
</xsl:stylesheet>

[1] 

For example, XSLT-based implementations can use the generate-id() function to generate a unique suffix.