Using a module more than once in the same document

You can include the same content in more than one place in a document as long as it is valid. One thing that can make a document invalid is duplicate ID names. If any duplicated content has an ID attribute on any of the elements in it, then you will have duplicate ID names and your document will not be valid. One drastic solution is to eliminate all ID values from modular doc. But that prevents you from forming cross references to that content. If you use IDs to form HTML filenames in your output, then you will not have that feature for such modules and they will have generated filenames.

If you absolutely must have duplicated content with IDs, then you have to figure out how to get different ID values. The following is one example of how XInclude can give you a different ID value if you are using xsltproc.

  1. Let's say your modular file has a single section you want to include twice, but it has an ID value.

    <?xml version="1.0"?>
    <!DOCTYPE section SYSTEM "docbook.dtd">
    <section id="original-id">
     <para>
     blah blah
     </para>
    </section>
    
  2. Form your first XInclude normally:

    <xi:include  href="module.xml"  
          xmlns:xi="http://www.w3.org/2001/XInclude"/>

    This pulls in the entire <section id="original-id"> element, including its children.

  3. Put your second XInclude inside its own section element with a different ID value. And avoid the original ID value by selecting the children of the section element with an XPointer expression. The following is the complete example with both includes.

    <book>
    <chapter>
      blah blah
      <xi:include href="module.xml" 
                  xmlns:xi="http://www.w3.org/2001/XInclude"/> 
    </chapter>
    <appendix>
      blah blah
      <section id="appendix-id">
        <xi:include  href="module.xml"
                     xpointer="xpointer(/section/node())"  
                     xmlns:xi="http://www.w3.org/2001/XInclude"/>
      </section>
    </appendix>
    </book>
    

    When processed with xsltproc, this example results in the second instance being <section id="appendix-id">, containing the same set of child elements, comments, and text nodes, and processing instructions (all are selected by the node() syntax). Since the new section wrapper has a different ID value, it will validate. You can even form a cross reference to either of the instances of the section. Just make sure none of the children have ID values or it will still not validate. Only xsltproc supports the xpointer() scheme used in this example. See this Note for background on xpointer().

Reusing content at different levels

Sometimes you need to reuse content at a different level from its original source. For example, a chapter in one book might need to be reused as a section in another book. You can reuse content with XInclude, but you cannot always place the same element in a new location. For example, if you are reusing a chapter as a section, you cannot just XInclude a chapter element inside another chapter, as it will not be valid and will not format properly.

The XInclude xpointer() scheme described in the previous section can be used for this purpose. The idea is to create a new container element, and then XInclude all the content of another element into it, without bringing along the other element itself. The section element in the following example does that.

<chapter id="container-chap">
  ...
  <section id="chapter-as-section">
    <xi:include  href="userguide.xml"
                 xpointer="xpointer(//chapter[@id = 'intro']/node() )"
                 xmlns:xi="http://www.w3.org/2001/XInclude"/>
 </section>
  ...
</chapter>

The path //chapter[@id = 'intro']/node() finds the chapter in userguide.xml whose id attribute is intro, and selects all the content of the element. The node() syntax in the path selects child elements, text nodes, processing instructions, and comments.

The content to be included must fit into its new location. You may have to alter the included chapter a bit to do this. For example, if the title is in a chapterinfo element, then the XSL templates processing the new section will not look for the title there.

Only xsltproc supports the xpointer() scheme used in this example. See this Note for background on xpointer()

Using modified id values

Another approach to the problem of duplicate id values on repeated XInclude elements is to modify the id values in the output. You can modify the template named object.id, which is used to assign id values for all output elements. It usually just copies an input element's id value, or generates a string if it does not have an id attribute. But you can customize the template to solve this problem.

This solution does not modify the id attributes in the source file, so the file will still be invalid if it has duplicate ids. But if you do not need to validate your resolved files, then this solution will at least let you build output and have functioning tables of contents.

The object.id template is used to generate the output link id references for any element as well as the id attribute itself. As long as it produces consistent output for the same element, your links should work.

In this customization, the template counts the number of preceding elements with the same id value. If the count is greater than zero, then it appends the count to the output id value. The customization also works with DocBook 5 documents that use xml:id.

<xsl:template name="object.id">
  <xsl:param name="object" select="."/>

  <xsl:variable name="id" select="@id"/>
  <xsl:variable name="xid" select="@xml:id"/>

  <xsl:variable name="preceding.id"
        select="count(preceding::*[@id = $id])"/>

  <xsl:variable name="preceding.xid"
        select="count(preceding::*[@xml:id = $xid])"/>

  <xsl:choose>
    <xsl:when test="$object/@id and $preceding.id != 0">
      <xsl:value-of select="concat($object/@id, $preceding.id)"/>
    </xsl:when>
    <xsl:when test="$object/@id">
      <xsl:value-of select="$object/@id"/>
    </xsl:when>
    <xsl:when test="$object/@xml:id and $preceding.xid != 0">
      <xsl:value-of select="concat($object/@id, $preceding.xid)"/>
    </xsl:when>
    <xsl:when test="$object/@xml:id">
      <xsl:value-of select="$object/@xml:id"/>
    </xsl:when>
    <xsl:otherwise>
      <xsl:value-of select="generate-id($object)"/>
    </xsl:otherwise>
  </xsl:choose>
</xsl:template>

With this template in place, when the id of a repeated element is usage, for example, then the first instance in the output will be usage. But the second instance will instead be usage1, the third instance usage2, etc. This process ensures that each output id is unique.

Note that any instances of xref or link or olink that reference the original id value will not be altered. They will always point to the first instance in the output.