Microsoft XML 2.5 SDK


 

Generating Item Numbers Using Script

[This is preliminary documentation and subject to change.]

Item numbers can be calculated and formatted in script and the results inserted into the text stream using the <xsl:eval> element. The helper functions childNumber() and formatIndex make this task easier.

The childNumber() function returns the index of the element relative to its siblings of the same type. In this example, it will provide numbers specific to "section" elements.

The element index can be converted to a string using the formatIndex function. This function provides a handy way to format indexes as alphabetic labels, roman numerals, or integers with or without leading zeroes. For more information on the allowed values of the format string, see the formatIndex reference documentation. This example illustrates the use of the format string for plain integers—"1".

<xsl:template match="section">
  <DIV>
    <H2>Chapter <xsl:eval>formatIndex(childNumber(this), "1")</xsl:eval>.
      <xsl:value-of select="title"/></H2>
    <xsl:apply-templates />
  </DIV>
</xsl:template>

A More Complex Example

Hierarchical section numbers can be generated by recursively concatenating the expression above. The following example is from the XML version of the XSL spec found on the W3C Web site at http://www.w3.org/TR/1998/WD-xsl-19981216.xml. Documents conforming to this grammar have structures similar to this:

<spec>
  <header>header material goes here</header>
  <body>
    <div1>
      <head>title of section 1</head>
      <div2>
        <head>title of section 1.1</head>
        <div3>
          <head>title of section 1.1.1</head>
        </div3>
      </div2>
    </div1>
    <div1>
      <head>title of section 2</head>
    </div1>
  </body>
  <back>
    <div1>
      <head>title of appendix A</head>
      <div2>
        <head>title of section A.1</head>
      </div2
    </div1>
    <inform-div1>
      <head>title of informative appendix B</head>
    </inform-div1>
  </back>
</spec>

The template for the "div1" displays the title of the section along with its hierarchical section number. The various other "div" elements have similar templates differing only in the size and style of the title.

<xsl:template match="div1">
  <div class="div1">
    <h2>
      <xsl:eval>sectionNum(this)</xsl:eval>
      <xsl:value-of select="head"/>
    </h2>
    <xsl:apply-templates/>
  </div>
</xsl:template>
  
<xsl:template match="div2">
  <div class="div2">
    <h3>
      <xsl:eval>sectionNum(this)</xsl:eval>
      <xsl:value-of select="head"/>
    </h3>
    <xsl:apply-templates/>
  </div>
</xsl:template>

The sectionNum function is defined in a script block within the style sheet, and calculates the section number of this section using the same expression as in the earlier example—"formatIndex(childNumber(e), '1')". It concatenates this to the section number of the nearest ancestor "div" element, which is obtained by recursively calling sectionNum. The recursion stops when there are no more ancestors, in other words, when the parameter "e" is null.

<xsl:script><![CDATA[
  function sectionNum(e) {
    if (e)
    {
      // if element is a child of the back-matter use an appendix number
      if (e.parentNode.nodeName == "back")
        return formatIndex(absoluteChildNumber(e), "A") + ".";
      else
        return sectionNum(e.selectSingleNode("ancestor(inform-div1|div1|div2|div3|div4|div5)")) +
             formatIndex(childNumber(e), "1") + ".";
    }
    else
    {
      return "";
    }
  }
]]></xsl:script>

This function also checks to see if the element is a top-level section within the appendix (e.parentNode.nodeName == "back"), in which case the index must be formatted using uppercase alphabetic characters indicated by the format string "A". Because an appendix can contain either "div1" elements or "inform-div1" elements, the absoluteChildNumber is used instead of childNumber. This function provides numbering of elements of any type, so the "div1" and "inform-div1" elements are counted together.