
On to the next of LaTeX's features missing (for good reasons you may say, after all, HTML is not meant for typesetting) in HTML: a table of contents. If you read previous versions of my macro file and decided it's not worth following the genesis of a mess like the "section macromaker", read on anyway. This is different, it doesn't even need submacros to make it at least writable, like the last version did!
What's there to be done about it? Several things:
<CHAPTER TITLE="First chapter"><SECTION TITLE="This is a section of a chapter"><SUBSECTION TITLE="A subsection"><SECTION TITLE="The next section"><TABLEOFCONTENTS> for example :-), with
(here comes something typesetting for dead wood will never offer!) clickable
section titles that link to the beginning of their respective sections.OK, to the technical side of it—prepare yourself for a lot of string
operations, weird quoting and general HTML sucking!
Obviously, there has to
be a counter for every section level, i.e. one for chapters, one for sections,
and so on. Every time one of the higher level sections (i.e. the ones higher
in the document structure's hierarchy, not the ones with the higher
numbers in their Hx headings) is started, all the counters
for lower levels have to be reset2, so opening a new chapter starts counting sections
anew, etc. This counter, with all the higher level counters prepended and
separated by periods3, should appear in front of a
properly marked-up heading, and at the same time, this title has to be written
to a TOC file that <TABLEOFCONTENTS> can read in again
later.
Here's a first naïve attempt:
1 <$macro SECTION TITLE:string>
2 <$define hlvl:string>
3
4 <* define or increment the counter *>
5 <$if COND=(defined _section_counter)>
6 <$let _section_counter = (_section_counter & '1')>
7 <$else>
8 <$define _section_counter:num/GLOBAL='1'>
9 </$if>
10
11 <* make sure chapter counter exists *>
12 <$if COND=(not defined _chapter_counter)>
13 <$define _chapter_counter:num/GLOBAL='1'>
14 </$if>
15
16 <* zero all lower-level counters if there are any defined *>
17 <$if COND=(defined _subsection_counter)>
18 <$let _subsection_counter='0'>
19 </$if>
20 <$if COND=(defined _subsubsection_counter)>
21 <$let _subsubsection_counter='0'>
22 </$if>
23 <$if COND=(defined _paragraph_counter)>
24 <$let _paragraph_counter='0'>
25 </$if>
26 <$if COND=(defined _subparagraph_counter)>
27 <$let _subparagraph_counter='0'>
28 </$if>
29
30 <* make the section number *>
31 <$define sectnum:string = (_chapter_counter + "." + _section_counter)>
32
33 <* insert the counter string and title at current location *>
34 <( '<H2><A NAME="hscsectname' + sectnum + '">' +
35 sectnum + "</A> " + TITLE + "</H2>" )>
36
37 <$export FILE=((basename hsc.source.name) + ".toc") APPEND
38 DATA=('<H2><A HREF="hscsectname' + sectnum + '">' + sectnum +
39 " " + TITLE + '</A></H2>')>
40 </$macro>
This does all of the above, but not in a particularly nice way. The thing is,
this is only one of up to six sectioning macros; the CHAPTER macro
would become a bit shorter, because it doesn't have to check for any
superordinate section counter nor create the sectnum variable, but
all of the lower levels would be longer. And if you wanted to make one layout
change, you'd have to do it in all six almost identical copies, and adding
more options like roman numbers would make things even worse. Thus, it would
be better to have one macro to handle all levels of sections
in a generalized way, and to derive the individual sectioning macros from
that.
Such a macro would need at least two attributes: a title just like the one above, and a level specifying its position in the hierarchy. It has to do the following things:
TITLE.The macro below does this:
1 <$macro _TEX-SECTION LEVEL:num TITLE:string>
2 <$define sectnum:string=''>
3 <$define ctrname:string=('the_section' + LEVEL + '_counter')>
4
5 <$if COND=((LEVEL < '1') or (LEVEL > "6"))>
6 <$message CLASS="error"
7 TEXT="LEVEL must be between 1 and 6 for current HTML versions!">
8 </$if>
9 <$define open_hdr:string=("<H" + LEVEL + ">")>
10 <$define close_hdr:string=("<" + "/H" + LEVEL + ">")>
11
12 <* add to the current counter *>
13 <(
14 "<$if COND=(defined " + ctrname + ")>" +
15 "<$let " + ctrname + "=(" + ctrname + " & '1')>" +
16 "<$else>" +
17 "<$define " + ctrname + ":num/GLOBAL='1'>" +
18 "</$if>"
19 )>
20
21 <* make sure all higher-level counters exist *>
22 <FOR VAR=i START=1 TO=(LEVEL - "1")>
23 <(
24 "<$if COND=(not defined the_section" + i + "_counter)>" +
25 "<$define the_section" + i + "_counter:num/GLOBAL='1'>" +
26 "</$if>"
27 )>
28 </FOR>
29
30 <* zero all lower-level counters if there are any defined *>
31 <FOR VAR=i START=(LEVEL & "1") TO="6">
32 <$if COND=(defined {"the_section" + i + "_counter"})>
33 <$let {"the_section" + i + "_counter"}='0'>
34 </$if>
35 </FOR>
36
37 <* concatenate the actual counters, separated by periods *>
38 <FOR VAR=i START=1 TO=(LEVEL)>
39 <$if COND=(i > "1")>
40 <$let sectnum = (sectnum + '.')>
41 </$if>
42 <$let sectnum=(sectnum + {"the_section" + i + "_counter"})>
43 </FOR>
44
45 <* insert the counter string and title at current location *>
46 <( open_hdr + '<A NAME="hscsectname' + sectnum + '">' +
47 sectnum + "</A> " + TITLE + close_hdr )>
48
49 <$export FILE=((basename (HSC.Source.Name)) + ".toc") APPEND
50 DATA=("<TABLE-OF-CONTENTS-ENTRY LEVEL=" + LEVEL +
51 " NUMBER='" + sectnum + "' TITLE='" + TITLE + "'>" + HSC.LF)>
52 </$macro>
The name for the current section's counter is built in line
34; the following
code up to line 10 checks the level and creates the corresponding heading tags
as open_hdr and close_hdr. Lines 13-19 contain
some dynamic code (see the "Nested Loops" section in the loops article) to check if the counter exists,
and increment it if so. They are indented as usual, although they are in fact
strings that get concatenated first and then expanded in the surrounding
"insert-expression" tag <(...)>. Lines 22-28 employ the
same mechanism, but wrapped in a loop running from one to LEVEL -
1. This is the first place where things can get slow—try writing
the resulting code to a file using <$export APPEND ...>
and see how much code is actually expanded here!
The only thing new about lines 31-35 is the use of a new construct
introduced in HSC V0.926: symbolic references. This is similar to Perl, where
you can reference variables by name:
$foo="bar"; $$foo=42; print "$bar\n"; will print "42", because
if you try to dereference a variable that is not a true reference, Perl will
access the variable whose name is stored in the one
dereferenced5. HSC uses "curly brackets", AKA braces, to do the
same, explicitly.
So the expression (defined {"the_section" + i + "_counter"})
will first concatenate the counter variable i with its prefix and
suffix to form a new variable name, and then check the existence of a variable
by that name. Unlike normal expressions, the ones thus bracketed yield an
"lvalue", i.e. they can also be used on the left hand side of an assignment,
as it is done in line 33.
Finally, the actual section number is built as a string by the loop in
lines 38-43. Thanks to symbolic references this doesn't need any dynamic
code with its consequent quoting orgies and is fairly easy to understand:
the individual numbers are just concatenated, with a period before any one
but the first.
Lines 46 and 47 are the only ones in this macro that actually produce any
text to appear in the final HTML file. A straightforward insert-expression tag
builds a heading according to open_hdr and close_hdr
defined before. The final code written to the TOC uses a technique known from
the HSC/SQL article: formatting macros.
Instead of creating the layout for the table of contents directly in the
section macro (which in some cases may not even be possible, as things like
the maximum heading level in the whole document may be needed, but is not known
here yet) a single TABLE-OF-CONTENTS-ENTRY-macro call is written
out, and this macro can then go on to format the TOC without the section macro
having to worry about this.
Now that we have a general sectioning macro that manages numbering and headings, deriving the CHAPTER, SECTION, etc. macros is trivial. They are all just wrappers that set the required LEVEL:
<$macro SECTION TITLE:string/R> <_tex-section LEVEL=1 TITLE=(TITLE)> </$macro> <$macro SUBSECTION TITLE:string/R> <_tex-section LEVEL=2 TITLE=(TITLE)> </$macro> <* and so on... *>
The following is an example for the abovementioned formatting macro for a
TOC-entry, presented here without further comments as it should be clear from
its inline documentation. TABLE-OF-CONTENTS reads the TOC file and
expands the TABLE-OF-CONTENTS-ENTRY-macros contained therein. Each
of these adds an almost completely formatted table row to a string defined by
TABLE-OF-CONTENTS, and sets _max_toc_level. When all
of these have been expanded and _max_toc_level has its final
value, TABLE-OF-CONTENTS opens a new table and inserts the string
that will yield a nicely formatted TOC.
1 <$macro TABLE-OF-CONTENTS-ENTRY
2 LEVEL:num/R NUMBER:string/R TITLE:string/R>
3 <$define _tocentry:string='<TR>'>
4
5 <* update max_toc_level (must be defined in TABLE-OF-CONTENTS!) *>
6 <$if COND=(max_toc_level < LEVEL)><$let max_toc_level=(LEVEL)></$if>
7 <* emit an empty cell for indentation according to level *>
8 <$if COND=(LEVEL > "1")>
9 <$let _tocentry = (_tocentry +
10 '<TD ALIGN="left" COLSPAN="' + (LEVEL - "1") + '"></TD>')>
11 </$if>
12 <* add section number *>
13 <$let _tocentry = (_tocentry + '<TD>' + NUMBER + '</TD>')>
14 <* make a cell that stretches to the right of the table *>
15 <$let _tocentry = (_tocentry +
16 '<TD COLSPAN=(max_toc_level - "'+(LEVEL - "1")+'")>')>
17 <* emit a link, add title, close anchor, cell and row *>
18 <$let _tocentry = (_tocentry + '<A HREF="#hscsectname' + NUMBER + '">' + TITLE)>
19 <$let _tocentry = (_tocentry + '</A></TD></TR>' + HSC.LF)>
20 <* add _tocentry to the actual TOC string *>
21 <* we have to delay the actual TOC creation until after all
22 * TABLE-OF-CONTENTS-ENTRY macros have been processed, so max_toc_level has
23 * its final value, therefore everything has to go into the_toc first! *>
24 <$let the_toc = (the_toc + _tocentry)>
25 </$macro>
26
27
28 <$macro TABLE-OF-CONTENTS TITLE:string>
29 <$define max_toc_level:num=1>
30 <$define the_toc:string=''>
31 <$define tocfile:string>
32
33 <$if COND=(defined PROJECT_GENERATE_TOC)>
34 <$let tocfile=((basename (HSC.Source.Name)) + '.toc')>
35
36 <$if COND=(Exists(tocfile))>
37 <* include file with some TABLE-OF-CONTENTS-ENTRY macros and expand them *>
38 <$include FILE=(tocfile)>
39 <$else>
40 <$define errormsg:string="Missing TOC file - rerun HSC to get it right!">
41 <$let the_toc=("<TR><TD>" + errormsg + "</TD></TR>")>
42 <$WARNING T=(errormsg)>
43 </$if>
44 <* now expand the actual TOC in a context where max_toc_level has its
45 final value already *>
46 <TABLE CLASS="table_of_contents" BORDER="0" SUMMARY="Table of Contents">
47 <$if COND=(set TITLE)>
48 <TR><TH COLSPAN=(max_toc_level & "1") ALIGN="left"><(TITLE)></TH></TR>
49 </$if>
50 <(the_toc)>
51 </TABLE>
52 <* get rid of the TOC file *>
53 <HSC_SYSCMD_RMFILE FILE=(tocfile)>
54 </$if>
55 </$macro>
56
57
Finally, have a look at the macros in the macro file coming with HSC, they contain a few extra features over the ones presented here, although the way they work is basically the same.
Last change: 21-Feb-2006, 06:43