LaTeX-niques for HSC

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:

  1. We want a hierarchy of tags that manage and number chapters, sections, subsections and so on, e.g. like this:
    <CHAPTER TITLE="First chapter">
    <SECTION TITLE="This is a section of a chapter">
    <SUBSECTION TITLE="A subsection">
    <SECTION TITLE="The next section">
    which should come out like
    1 First chapter
    1.1 This is a section of a chapter
    1.1.1 A subsection
    1.2 The next section1
  2. A table of contents should be available through a simple macro, <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.
  3. Possibly a central TOC file that does the above for a whole collection of documents.

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>&nbsp;" +  TITLE + "</H2>" )>
    36	
    37	  <$export FILE=((basename hsc.source.name) + ".toc") APPEND
    38	           DATA=('<H2><A HREF="hscsectname' + sectnum + '">' + sectnum +
    39	           "&nbsp;" + 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:

  1. Check that the level is between 1 and 6, so a valid heading tag can be generated.
  2. Determine the name of the counter variable to be used for this level.
  3. Create or increment this variable.
  4. Make sure all the counters for higher levels exist.
  5. Set all counters for lower levels (if any) to zero.
  6. Create a section number from all relevant counters and add it to the TITLE.
  7. Write an appropriate entry to the TOC file.

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>&nbsp;" +  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.


Footnotes
  1. Actually, these should be real HTML headings, but I chose to simulate their usual visual effect by increasing the font size, just as not to screw up this document's structure. So these may not look like real headings on your browser—just pretend they did! [back]
  2. For a properly structured document, only the one level below it would have to be. We'll reset all of them anyway, to account for strange people who want to put subsections into chapters and stuff like that. [back]
  3. That's the usual style. Maybe a non-numeric numbering should be possible as well... [back]
  4. This isn't strictly necessary, it could be done every time the name is needed, but it saves some typing. [back]
  5. Unless you have activated "strict refs", which is usually a Good Thing. [back]

Last change: 21-Feb-2006, 06:43

You are not supposed to see this—arachnoids only: nude metal goth porn tits video i phone thai hentai gangbang girls i touch lolita emo slutsparis ghettoschlampen arschgeficktgay geigh salope enculée pédé