Posted on by & filed under programming, publishing, Tech, xslt.

Twice in the past couple of weeks I’ve needed to solve this problem:

We have content provided as individual displayable pages, but we need a document for those pages to refer to, with a table of contents so that a user can navigate between them. In one case it was a set of articles in a journal issue, in another it was entries in an encyclopedia. I solved both of these problems with the same general XSLT structure, so I thought I’d write it up for others to use.

You will need:

  • Some xml documents representing parts of a whole, including some identifying information for their source
  • XSLT 2.0’s doc() and doc-available() functions, pointing at your destination directory
  • A standardized file naming and xml structure convention for your source document
  • An ordering mechanism for your individual chapters

Determining the source URI and title

You’ll need the source URI while you’re processing your individual chapter, so you can insert a connection to the new source document, such as a dc:source element. Put this value in a top-level variable, we’ll want it later.

<!-- at top of stylesheet -->
<xsl:variable name="source-uri">/<xsl:value-of
select="/document/metadata/parent-uri"/>/source.xml</xsl:variable>

<!-- in document meta -->
<dc:source uri="{$source-uri}">

<xsl:apply-templates select="/document/metadata/parent-title">

</dc:source>

Processing the chapter

Whether you are creating a new source document or updating an existing one, you want the current chapter to appear in the source document’s Table of Contents. Create a new variable that contains the full reference item as needed in your XML format.

<xsl:variable name="content-ref">
<toc-entry uri="{/document/@uri}" sort="{/document/@sort}">
<title><xsl:apply-templates select="/document/metadata/title"/></title>
</toc-entry>
</xsl:variable>

Creating the source document

Now we need to have the XML create the source document. We only want to do this if the document doesn’t exist yet, so this is where we use doc-available(). And since we want to write to the file regardless, we want to save the new file to a variable.


<xsl:variable name="source-document">
<xsl:choose>
<xsl:when test="not(doc-available($source-uri))">
<document uri="{$source-uri}">
<title>

<xsl:apply-templates select="/document/metadata/parent-title">

</title>
<!-- any other source information from the document -->
<toc>
<xsl:copy-of select="$content-ref"/>
</toc>
</document>
</xsl:when>
<xsl:otherwise><!-- see below --></xsl:otherwise>
</xsl:choose>
</xsl:variable>

Updating the table of contents

This works great for the first file you process, but we want this source document to refer to each of the documents. So in the cases where the source document exists, we want to update it. We’ll use a copy template for most of the file, then some specific code to deal with the table of contents.


<!-- into the xsl:otherwise area above -->
<xsl:otherwise>
<xsl:apply-templates select="doc($source-uri)" mode="insert-content-ref">
<xsl:with-param name="content-ref" select="$content-ref"/>
</xsl:apply-templates>
</xsl:otherwise>


<!-- after that template -->
<xsl:template match="@*|node()" mode="insert-content-ref">
<xsl:param name="content-ref"/>
<xsl:copy>
<xsl:apply-templates select="@*|node()" mode="insert-content-ref">
<xsl:with-param name="content-ref" select="$content-ref"/>
</xsl:apply-templates>
</xsl:copy>
</xsl:template>


<xsl:template match="toc" mode="insert-content-ref">
<xsl:param name="content-ref"/>
<xsl:copy>
<xsl:copy-of select="./toc-entry[@sort &lt; $content-ref//@sort]"/>
<xsl:copy-of select="$content-ref"/>
<xsl:copy-of select="./toc-entry[@sort &gt; $content-ref//@sort]"/>
</xsl:copy>
</xsl:template>

Note that this replaces any toc-entry elements that have the same @sort as our $content-ref. This is good, because it allows reloads of the content not to continually inflate the TOC entry. It does require that the sort keys are unique, which is good practice anyway.

Writing the file

Once you have the new version of the source file, whether newly created or updated, you’ll want to write it. This is simple, since it’s stored as a variable.


<xsl:result-document href="{$source-uri}">
<xsl:copy-of select="$source-document"/>
</xsl:result-document>

 Limitations of this process

The XSL provided will certainly need to be updated to match your individual situation. More significantly, if new content is processed in which a chapter is removed, or the sort key is changed, spurious documents may remain in the source document unless it is removed and completely recreated. This also relies on storing that source document in memory as a variable. That should not be a problem, unless that document is truly huge.

Tags: documents, Processing, programming, XML, xsl, xslt,

Comments are closed.