xslt toc, table of contents generate

Table of Contents

1. Table of contents using generate-id
2. How to use a part of the input document twice
3. Creating a toc
4. Indenting Table of Contents

1.

Table of contents using generate-id

Ken Holman

For what you need, you need only go over the source tree twice, once for the table of contents and once for the body (or, of course, any other times you may wish to visit the source tree).

Each time around you use generate-id(.) in a different construct of the result tree, but because it is one run of the XSLT engine the value returned for a given node is guaranteed to be the same. As soon as you finish the run that guarantee is gone ... the next time around you must treat the value created by generate-id(.) as brand new, even if it coincidentally happens to be what it was before.

Note that the value generated for the result tree comes from the node in the source tree ... you aren't asking anything about the result tree.

I hope the example below helps.

XML Source

<?xml version="1.0"?>
<test>
  <q>blah 1</q>
  <q>blah 2</q>
</test>


XSL Stylesheet

<?xml version="1.0"?>
<xsl:stylesheet 
xmlns:xsl="http://www.w3.org/1999/XSL/Tranform"     
version="1.0">

<xsl:output indent="yes"/>

<xsl:template match="/">        <!--root rule-->
  <H4>Toc</H4>
  <xsl:for-each select="//q">
    <p><a href="#{generate-id(.)}">
           <xsl:value-of select="."/></a></p>
  </xsl:for-each>
  <body>
    <xsl:apply-templates/>
  </body>
</xsl:template>

<xsl:template match="q">
  <p><a name="{generate-id(.)}">
         <xsl:value-of select="."/></a></p>
</xsl:template>

</xsl:stylesheet>


Output


<H4>Toc</H4>
<p>
<a href="#N3">blah 1</a>
</p>
<p>
<a href="#N6">blah 2</a>
</p>
<body>
  <p>
<a name="N3">blah 1</a>
</p>
  <p>
<a name="N6">blah 2</a>
</p>
</body>

2.

How to use a part of the input document twice

David Carlisle

Q expansion: I don't understand how I can add the list of nodes that contains the information that are already used to make a previous part of the output tree? E.g. Having used

<title>...</title>
  <p> ... </p>

How can I re-use the input document to create a list of titles at the end of the document, linked to the titles?

You can go over the input tree as often as you like. Below I use apply-templates for the first, and for-each to go over again. If the formatting of the second pass were more complicated I would probably have used apply-templates again but with a different mode.

The following assumes you put <x> </x> around your example to make it a well formed document,

<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                version="1.0"
                >

<xsl:output method="xml" indent="yes" />

<xsl:template match="x">
<xsl:apply-templates/>
<list>
<xsl:for-each select="title">
  <p><a name="{@id}"/><xsl:value-of 
	select="."/></p>
</xsl:for-each>
</list>
</xsl:template>

<xsl:template match="title">
  <a href="#{@id}"><xsl:value-of 
	select="."/></a>
</xsl:template>


<xsl:template match="p">
  <p>
  <xsl:apply-templates/>
  </p>
</xsl:template>


</xsl:stylesheet>

3.

Creating a toc

DaveP

Q expansion I've a problem trying to figure out the xsl to generate a 'table of contents' for a document I'm working on. The xml goes something like...

<contents/>
<section><heading>A</heading>
	<heading>B</heading>
	<heading>C</heading>
</section>
<section><heading>D</heading>
	...
</section>

where the tag <contents/> is I wish to insert a table of contents, ie
1. A
1.1. B
1.2. C
2. D

and still generate the actual contents underneath...

1. A
blah blah blah
1.1. B
blah blah blah blah blah blah

xml

<doc>
<contents/>
<section><heading>A</heading>
	<heading>B</heading>
	<heading>C</heading>
</section>
<section><heading>D</heading>
	<heading>E</heading>
<heading>F</heading>
<heading>G</heading>
</section>
</doc>

xsl

<?xml version="1.0" ?> 
<xsl:stylesheet xmlns:xsl = "http://www.w3.org/1999/XSL/Transform"
  version="1.0">

<xsl:output method="html" encoding="utf-8"/>


<xsl:template match="/"> 
  <html><head></head>
  <body>
  <xsl:apply-templates/>
</body>

</html>
</xsl:template>

<xsl:template match="doc">
 <xsl:apply-templates/>
</xsl:template>

<xsl:template match="section" mode="toc">   
  <xsl:for-each select="heading">
  <p>   <xsl:number format="1.1.1. " level="multiple" from="s1" 
    count="section|heading"/>

      <a href="#{generate-id(.)}">
        <xsl:value-of select="."/>
      </a>
   </p>
</xsl:for-each>
  </xsl:template>

  <xsl:template match="contents">
    <xsl:apply-templates select="//section" mode="toc"/>
   
  </xsl:template>

  <xsl:template match="section">
    <xsl:for-each select="heading">
     <h3>  <a name="{generate-id(.)}">
        <xsl:value-of select="."/>
      </a> </h3></xsl:for-each>
  </xsl:template>


  <xsl:template match="*"><p>************</p></xsl:template>
</xsl:stylesheet> 

4.

Indenting Table of Contents

Don Bruey

You can write a named template that accepts as a parameter the value of your $level variable. Have it create the output you want for a single indentation, then call itself recursively for each remaining level. Probably you can find some similar things if you dig through the FAQ. Something like this:

 <xsl:template name="indent">
 	<xsl:param name="level"/>
 	<xsl:if test="$level &gt; 0" >
   		<!-- put your output here for one indentation level -->
 		<xsl:call-template name="indent">
    			<xsl:with-param name="level" 
                       select="$level - 1" />
 		</xsl:call-template>
 	</xsl:if>
 </xsl:template>

Jarno Elovirta offers, for indent proportional to depth,

  <xsl:template name="toc-indent">
    <xsl:call-template name="indent">
      <xsl:with-param name="level" select="count(ancestor::part)" />
    </xsl:call-template>
  </xsl:template>
  
  <xsl:template name="indent">
    <xsl:param name="level" />
    <xsl:if test="$level">
      <xsl:text>&#xA0;&#xA0;</xsl:text>
      <xsl:call-template name="indent">
        <xsl:with-param name="level" select="$level - 1" />
      </xsl:call-template>
    </xsl:if>
 </xsl:template>

and David Carlisle offers the minimal

 <xsl:template name="toc-indent">
   <xsl:for-each select="ancestor::part">&#160;</xsl:for-each>
 </xsl:template>