Annotating program listings

It is often the case that you need to comment on lines of code to explain it. There are three mechanisms you can use for that purpose.

Line annotations

You can mix lineannotation elements in with your code to explain something directly in the text. For example:

<programlisting># constructor
sub new {
    my ($file, $output) = @_; <lineannotation>Store args</lineannotation>
    my $dir = basename $file; <lineannotation>Get dir name</lineannotation>
}
</programlisting>

Lineannotations in the stock stylesheet print as italic, but they inherit the monospace font family of the programlisting. You may want them to appear in the italic version of the body font to make them stand out more. The following is such a customization for a print customization layer:

FO template customization:
<xsl:template match="lineannotation">
  <fo:inline font-family="{$body.font.family}"
             font-style="italic">
    <xsl:call-template name="inline.charseq"/>
  </fo:inline>
</xsl:template>

For HTML output, you can let the CSS stylesheet handle the formatting:

HTML CSS stylesheet:
span.lineannotation { 
    font-family: serif;
    font-style:  italic;
}

Line annotations cannot be added in files inserted using textobject or <xi:include parse="text"> because the "<" character that starts the element will be escaped as &lt; when it is brought in. You can use lineannotations in files brought in with <xi:include parse="xml">, but then you have to be careful to escape other XML characters in your program file. Line annotations also cannot be used with examples marked as CDATA, because any lineannotation element won't be recognized as an XML element.

Line numbering

You can add line numbers to the listing, and then your paragraphs can refer to the line numbers. Currently line numbering is only available with the Java processors Saxon and Xalan, not xsltproc, because it is done with an extension function.

Line numbers are turned on by a linenumbering attribute on each programlisting element that needs line numbering. By default, the numbering starts at 1, but you can assign your own starting number with the optional startinglinenumber attribute. You can also continue the numbering from the most recent programlisting that had line numbering by adding a continuation="continues" attribute to the current element. The following is an example with startinglinenumber:

<programlisting linenumbering="numbered" startinglinenumber="12">
...

You have to enable the line numbering feature by setting a couple of stylesheet parameters. The parameters are use.extensions=1 and linenumbering.extension=1. Both must be set for it to work.

Once your lines are numbered, you can refer to the line numbers in the paragraphs. The problem with line numbers, though, is you cannot see them until the text is formatted at least once. Also, if you edit the code, the line numbers may change and you will need to adjust your number references. It is useful for stable code examples, though.

The formatting of line numbers can be controlled using a set of line numbering parameters for all program listings (and screen and cmdsynopsis elements). You can also override the formatting for a single program listing using special processing instructions. The line numbering parameters are:

linenumbering.everyNth

By default, every 5th line of the programlisting displays its number, so the visible numbers are 5, 10, 15, etc. If you set this parameter to 3, for example, then every third line shows its number. Setting it to 1 numbers all lines.

linenumbering.width

This is the number of spaces at the beginning of each line reserved for the line numbers. The default value is three, so numbers up to 999 will fit. You can set this parameter to a different integer to save fewer or more spaces for the line numbers. The numbers are right-aligned within this space so the digits line up properly.

linenumbering.separator

The literal content of this parameter is printed after the line number and before the program listing text. The default value is a single space, but it could be changed to any text.

To control the line number formatting for an individual program listing, you can use equivalent processing instructions. The processing instructions begin with <?dbhtml for HTML output, or <?dbfo for print output. The following is an example.

<programlisting 
  linenumbering="numbered" 1><?dbhtml linenumbering.everyNth="2" 
  linenumbering.separator=" &gt;" linenumbering.width="2" 2
?><?dbfo linenumbering.everyNth="2" 
  linenumbering.separator=" &gt;" linenumbering.width="2" 3
?><textobject><textdata  fileref="mycode.c" /></textobject>
</programlisting>
1

Put the processing instructions somewhere inside the programlisting element. Be sure to not introduce spaces and line breaks between them and any elements, since such white space is preserved in a programlisting and appears in the output. You can put line breaks within a processing instruction, however.

2

This PI sets the three values for HTML output.

3

This PI sets the three values for print output.

When processed, this particular program listing will have a number appearing on every other line, with two spaces allocated for the numbers, and with a literal > character (&gt;) separating the number from the line of code.

Callouts

You can use callouts to mark specific locations in a program listing and link explanatory text to the marks. To see how callouts look in output, see the example below. In DocBook, the callout element contains the explanatory text. The mark, which is called a callout bug, is most easily placed using the co element. Those two elements can be linked to each other to allow the reader to move back and forth between them.

The callout bug is usually rendered as a white number in a black circle. To see other options for rendering the callout bugs, see the section “Callout icons”.

The following is an annotated example of how callouts are written, using actual callouts to identify the important points.

<programlisting>
#ifndef _My_Parser_h_  <co1  id="condition-co"2  linkends="condition"3 /> 
#define _My_Parser_h_
#include "MyFetch.h"   <co id="headerfile-co" linkends="headerfile" />
class My_Parser  <co id="classdef-co" linkends="classdef" />
{
public:
        //
        // Construction/Destruction
        //
        My_Parser();  <coref  linkend="classdef-co"/>  4
        virtual      ~My_Parser() = 0;
        virtual int  parse(MyFetch &amp;fetcher) = 0;
};
#endif
</programlisting>

<calloutlist>5
  <callout6 arearefs="condition-co"7  id="condition"8 >
    <para>Make this conditional.</para>
  </callout>
  <callout arearefs="headerfile-co" id="headerfile">
    <para>Load necessary constants.</para>
  </callout>
  <callout arearefs="classdef-co" id="classdef">
    <para>Define new class</para>
  </callout>
</calloutlist>
1

Use a co element to place a callout bug in your code sample. The element is empty, with all the information in attributes.

2

Give the co an id value so the callout text can be linked directly to the callout bug location.

3

Its linkend attribute value (condition) should match the id value of its callout element (see callout #8). That forms a link from the callout bug to the text.

4

Use a coref instead of a co when you want to create a duplicate bug number. That is, when you have more than one location in your code that needs to refer to the same callout paragraph, use a coref for any but the first location. The linkend of the coref must point to the id of the master co element. The duplicate callout icons will all hotlink to the same callout paragraph. But the icon next to the callout paragraph will link back only to the master co element.

5

A calloutlist contains a set of callout elements, and formats them as a list.

6

Each callout element is paired up with a co element. The numbering order is based on the co order, so you should keep the callout elements in the same order.

7

The arearefs attribute value (condition-co) matches the id value of its co callout bug (see callout #2). That forms a link from the callout text to the callout bug.

8

Give the callout an id value so the callout bug can link directly to its callout text.

It helps to establish a naming scheme for the ids to track the two-ended links. This example uses the same name on both ends except the one on the co element adds a -co suffix. If you have to edit the code sample and move lines around, be sure to move any co elements with them. Also, remember when cutting and pasting to not duplicate any id values, which will produce a validation error.

Callouts on imported text

Can you put callouts on code imported from an external file? Yes, but it isn't easy, since you have to place the callout bugs by coordinates rather than literally in the code. This feature lets you use unmodified code files, though. Just don't try it with code samples that change with any frequency, because you will have to remap the coordinates.

You have to wrap your programlisting and calloutlist in a programlistingco element, because it supports the use of an areaspec to provide the coordinates. You also must use either Saxon or Xalan to process the files, because placing the callouts at the coordinates takes an XSLT extension function that is not available in xsltproc. To enable this function in Saxon or Xalan, you must set the stylesheet parameters use.extensions=1 and callouts.extension=1. Both must be set for it to work. Don't forget to include the appropriate DocBook XSL extensions jar file in your CLASSPATH as well.

To place a callout bug, you use an area element inside of a separate areaspec element, instead of a co element inside the code sample. Placing the bugs is the tedious part, because you must count lines and columns for each one.

You specify where the bug goes in the area element's coords attribute. Although the DTD allows you to specify a variety of units to use as coordinates, the DocBook XSL extension functions only support linecolumn and linerange unit types. The units attribute can be specified in the areaspec for all included areas, or in each individual area. If no units are specified, then linecolumn is assumed. With linecolumn, the coords attribute specifies a line number and column number, separated by a space. If the second number is absent, then the value of the stylesheet parameter callout.defaultcolumn is used, which is 60 by default. With linerange, the two numbers specify beginning and ending line numbers.

Here is an example that produces results like the previous one, but using an external file to hold the code sample. Note that the textobject does not have a line break before it, which would throw off the line counting coordinates.

Example 26.2. programlistingco with areaspec

<programlistingco>
  <areaspec units="linecolumn">
    <area id="condition-co" linkends="condition" coords="1 23"/>
    <area id="headerfile-co" linkends="headerfile" coords="3 23"/>
    <area id="classdef-co" linkends="classdef" coords="4 18"/>
  </areaspec> 

  <programlisting><textobject>
      <textdata  fileref="mycode.c" />
    </textobject>
  </programlisting>

  <calloutlist>
    <callout arearefs="condition-co"  id="condition" >
      <para>Make this conditional.</para>
    </callout>
    <callout arearefs="headerfile-co" id="headerfile">
      <para>Load necessary constants.</para>
    </callout>
    <callout arearefs="classdef-co" id="classdef">
      <para>Define new class</para>
    </callout>
  </calloutlist>

</programlistingco>

Here is an example command to process this file with Saxon:

CLASSPATH=../saxon653/saxon.jar:../docbook-xsl/extensions/saxon653.jar \
 java  com.icl.saxon.StyleSheet \
        -o outputfile.html \
        myfile.xml \
        ../docbook-xsl/html/docbook.xsl \
        use.extensions=1 \
        callouts.extension=1 \
        textinsert.extension=1

Callouts on graphics

The DocBook DTD has a mediaobjectco element that is designed to associate callouts with a graphical image. Unfortunately, this feature is currently not implemented in the DocBook XSL stylesheets.