Re: Complex table of content generation

Subject: Re: Complex table of content generation
From: Jeni Tennison <jeni@xxxxxxxxxxxxxxxx>
Date: Wed, 28 Jun 2000 21:36:22 +0100

I am a little concerned that I'm misunderstanding what you mean by
'hyperlink' in your example.  I will assume that you are producing HTML,
and that the details of each component are a named anchor, and the links to
them can be done through reference to the anchor, e.g.:

  <a name="C">Component = C</a>
    Parent = <a href="#C4">C4</a>
    Children = <a href="#C1">C1</a>, <a href="#C2">C2</a>,
               <a href="#C3">C3</a>, <a href="#C4">C4</a>

In other words, the 'hyperlinks' are all defined in the HTML that you're
producing - it's just a matter of producing HTML that contains the right
references to the right components.

>Here is an example of what I want: 
>1. Table of content (may be by alphabetical order)

The following template selects all the 'Component' elements and sorts them
alphabetically, producing HTML links to the details of the component
(assuming that the details are in the same document as the table of
contents).  It ignores all the SubComponent stuff, focussing on only the
Components - note that this relies on all the components, whether they have
subcomponents or nor, being defined within a 'Component' element.

[Note the spacing on these templates has been laid out for logic in the
stylesheet rather than spacing within the output - you probably have to
litter them with xsl:text to get precisely what you want...]

<xsl:template match="Components" mode="table-of-contents">
  <div id="contents">
    <xsl:for-each select="Component">
      <xsl:sort select="@name" order="ascending" />
      <a href="#{@name}"><xsl:value-of select="@name" /></a>

>2. List of all components (by logical order)

This is a little tricky because you need to define what makes up a logical
hierarchy under your scheme, and how that should be represented within your

The first question is 'how do you identify the top component?'.  There are
several options:
1. the one without a parent
2. the first in the alphabetical list
3. by human choice

In your example, you have chosen that component 'C' is the top of the
hierarchy, even though it is a subcomponent of component 'C4'.  So Option 1
cannot be what is being applied.  I think you are probably using Option 2,
but for the sake of extensibility, I implemented Option 3 - passing in the
name of the top component through a parameter:

<xsl:param name="top-component" select="'C'" />

The second question is 'how do you represent a logical hierarchy?'.  Within
your example, the general form is:

  top component, single subcomponent, all sub-subcomponents
  e.g. C, C3, C1.1 - C2 - C3 - C4

You have deliberately not gone any further than three levels down.  This
seems to be a fairly hard-wired decision, so I have similarly hard-wired it
into the template for doing the logical hierarchy.  But if you can express
a more general rule, then we can use that instead.

So, first a key.  This is just to provide a shortcut between a component
name and its subcomponents (in the case where you don't have the Component

<xsl:key name="super-sub" match="SubComponent" use="../@name" />

When you're designing keys for referencing, think 'what do I know?', 'what
do I want to know?' and 'how would I get *here* from *there*?'.  In this
case, we know the name of the component, we want to know its subcomponents,
and the way you get here from there is by finding the parent's @name

The following template gives the output that you desired, with HTML links.

<xsl:template match="Components" mode="logical-order">
  <div id="logical-order">
    <!-- select the top component, as defined by the parameter -->
    <xsl:for-each select="Component[@name=$top-component]">

      <!-- go through each subcomponent of the top component -->
      <xsl:for-each select="SubComponent">

        <!-- each composition is numbered according to the position()
             of the subcomponent within the list - may be another rule
             to use here? -->
        Composition <xsl:value-of select="position()" /> =

        <!-- link to top component -->
        <a href="#{$top-component}"><xsl:value-of select="$top-component"
        <!-- link to subcomponent -->
        <a href="#{.}"><xsl:value-of select="." /></a>, 

        <!-- go through each of the sub-subcomponents, accessed through
             the 'super-sub' key defined above, indexed on the content
             of the 'SubComponent' element -->
        <xsl:for-each select="key('super-sub', .)">

          <!-- link to sub-subcomponent -->
          <a href="#{.}"><xsl:value-of select="." /></a>

          <!-- append a dash unless it is last in the list -->
          <xsl:if test="position() != last()"> - </xsl:if>

>2.2 Description of each component (each name is a hyperlink, too)

Since the descriptions include the parent of the particular component, you
need a key where 'here' is the name of a subcomponent, 'there' is a
Component element and 'here from there' is that the subcomponent name
should be the value of a SubComponent child of the Component element:

<xsl:key name="sub-super" match="Component" use="SubComponent" />

The following template gives the details that you described, with HTML
links, and with 'None' as the default where there aren't any parents or
children of the particular component:

<xsl:template match="Components" mode="details">
  <!-- goes through the components in the order they appear in the
       input - may want to sort this alphabetically -->
  <xsl:for-each select="Component">

    <!-- define the HTML anchor for the component -->
    <a name="{@name}">Component = <xsl:value-of select="@name" /></a>

    <!-- define a variable to hold the list of supercomponents of
         the component using the 'sub-super' key -->
    <xsl:variable name="parent" select="key('sub-super', @name)" />
    Parent = 
      <!-- if there are any parents... -->
      <xsl:when test="$parent">
        <!-- go through the parents one by one (document order) -->
        <xsl:for-each select="$parent">
          <a href="#{@name}"><xsl:value-of select="@name" /></a>
          <xsl:if test="position() != last()">, </xsl:if>
      <!-- if there aren't, output 'None' -->

    <!-- define a variable to hold the list of subcomponents of the
         component (SubComponent child nodes) -->
    <xsl:variable name="children" select="SubComponent" />
    Children =
      <!-- if there are any children... -->
      <xsl:when test="$children">
        <!-- go through the children one by one (document order) -->
        <xsl:for-each select="$children">
          <a href="#{.}"><xsl:value-of select="." /></a>
          <xsl:if test="position() != last()">, </xsl:if>
      <!-- if there aren't, output 'None' -->

This produces the output that you specified in your email, but I am
concerned that it is not general enough for you.  For example, you
mentioned branches with very different depths, which isn't covered here.

I have used xsl:for-each in these examples both for brevity and because the
definition of the output you wanted from the logical order seemed to be in
terms of 'depth', which is easier to manage with xsl:for-each than
xsl:apply-templates.  However, if you can define the stopping condition
(i.e. when do you stop going down a branch?) in terms of things that you
know about a particular component (e.g. has it got a longer name than its
parent?), then you could (should?) probably use xsl:apply-templates instead.

Anyway, I hope this helps if not *solve* your problem, at least let you
frame some other questions that can help us take you closer to a solution
next time :)



Dr Jeni Tennison
Epistemics Ltd * Strelley Hall * Nottingham * NG8 6PE
tel: 0115 906 1301 * fax: 0115 906 1304 * email: jeni.tennison@xxxxxxxxxxxxxxxx

 XSL-List info and archive:

Current Thread