/*****************************************************************************/
/*
                                sdm2htm.c

The SDM2HTM utility converts SDML (VAX DOCUMENT source) into HTML (Hyper-Text
Markup Language).  Not bullet-proof!  No apologies; this is a quick throw-
together to get our SDML documentation available to the hypertext environment.
(NOTE at 23-JAN-1995; even with HyperReader working reasonably well and 
providing access to native Bookreader documents this utility still has a 
valuable function in providing HTML-formatted versions, which look and work 
better under Web browsers) 

This program cannot be understood without a knowledge of VAX DOCUMENT and 
HTML, and without reference to descriptions of DOCUMENT markup tags.

Uses recursive function calls extensively ...

The need for recursion is illustrated by the following SDML fragment:
"<CHAPTER>(The <UNDERLINE>(Need For <EMPHASIS>(Recursion\BOLD)))\symbol)

Uses fixed size buffers for all processing.  This means that there is fixed 
capacity for such constructs as a tag's parameter parentheses ( e.g. 
<tag>(...))  The size of these buffers is generous, but still finite.  Checks 
for buffer overflow are included. 

This utility could be improved by making these buffers dynamically allocated, 
and expand as string sizes demanded.  Sigh!

Many common GLOBAL tags are handled elegantly.  This utility does not pretend 
to provide a complete SDML conversion.  It is easily extensible.  It does not
do any sort of syntax checking.  It relies on the prior use of DOCUMENT to 
ensure that all all syntax problems are detected and corrected.  It assumes 
that the source is syntax-problem-free. 

The file names generated comprise three components.  First the source file 
name, separated from the other two by an underscore.  Second the <CHAPTER> 
number as two digits, 01 to 99.  Third, the sub-chapter (<EXAMPLE>) files as 
two digits, 00 to 99.  Hence file names look like ... NAME_0100.HTML
DOCUMENT <FRONT_MATTER> tags occur in files with digits 0000 to 0099.

A "Table Of Contents" file is created (NAME_0000).
A <TITLE_PAGE> tag becomes a "Title Page" HTML file (usually NAME_0001).
A <COPYRIGHT_PAGE> tag becomes a "Copyright Page" HTML file (usually NAME_0002).
A <PREFACE> tag becomes a "Preface" HTML file (usually NAME_0003).
Each <CHAPTER> becomes a separate HTML file (NAME_nn00). 
<EXAMPLE> tags become separate HTML files (NAME_nnnn).
<TABLE> tags become separate HTML files (NAME_nnnn).


HTML/DOCUMENT SPECIFIC OUTPUT
-----------------------------

HTML control is enabled from within <COMMENT>(...) tags.  As this is embedded 
in comment tags DOCUMENT nevers sees it, allowing HTML directives to be passed 
to this utility.  These directives are:

<COMMENT>(HTML/OFF) turns off HTML generation, allowing printed/Bookreader
specific text to be generated.

<COMMENT>(HTML/ON) turns HTML generation back on.

<COMMENT>(HTML=...) allows HTML specific markup text to be provided for Hyper-
Text documents, but is off course hidden from VAX DOCUMENT and is not 
included in printed/online text. 

<COMMENT>(HTML/POPUP=...) ... <COMMENT>(HTML/ENDPOPUP) creates a separate HTML 
file that is linked to using the text following the /POPUP=.


QUALIFIERS
----------
/[NO]COMMENTS           SDML <COMMENT>s are included (default: NO)
/COMPATIBILITY=integer  provide some level of backward compatible behaviour
/BODY=string            string for <BODY> tag attributes
/[NO]COLOUR             default is document colour, specify fo no colour
/DBUG                   turns on all "if (Debug)" statements
/DIRECTORY=string       directory into which to place the generated HTML files
/[NO]FLAG_UNKNOWN_TAGS  report all unknown tags (default: NO)
/[NO]FRAMED             create an HTML <FRAMESET> framed document (default: NO)
/[NO]HTML               output HTML (default), /NOHTML allows checking
/INDEX=[file-name]      create a "home page" also with the name_0000.HTML
/[NO]ODS5               control extended file specification (basically testing)
/[NO]SENTENCE_SPACING   add a &nbsp; after sentence-terminating periods
/[NO]UNKNOWN_TAGS       include commented-out  unknown flags (default: NO)
/[NO]VERBOSE            display progress messages (default)
/VERSION                display software version


BUILD DETAILS
-------------
See BUILD_SDM2HTM.COM


COPYRIGHT
---------
Copyright (C) 1996-2004 Mark G.Daniel
This program, comes with ABSOLUTELY NO WARRANTY.
This is free software, and you are welcome to redistribute it
under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2.


VERSION HISTORY (update SoftwareID as well!)
---------------
23-DEC-2003  MGD  v1.10.7, minor conditional mods to support IA64
                           add a bit of space around table borders
31-MAR-2003  MGD  v1.10.6, in line with general WASD doco changes
                           LINK and VLINK colours from 0000ff to 0000cc,
                           table row colour to #eeeeee
17-NOV-2002  MGD  v1.10.5, remove '\n' in <LINE> HTML,
                           add <P> before <LI> in lists
29-DEC-2001  MGD  v1.10.4, make /NOSENTENCE_SPACING the default
17-MAY-2001  MGD  v1.10.3, provide for <LIST>(SIMPLE) using <DL>,
                           add /VERSION for easily identification
04-FEB-2001  MGD  v1.10.2, bugfix; no trailing '\n' on some </HTML>
25-JAN-2001  MGD  v1.10.1, bugfix; += associated with </HEAD>s
20-DEC-2000  MGD  v1.10.0, add ChapterReferences(),
                           modify table-of-contents generation,
                           provide /COMPATIBILITY,
                           (after looking at this again after some
                            period of time I'm a little embarressed)
20-AUG-2000  MGD  v1.9.1, add colour to <CODE_EXAMPLE>
09-APR-2000  MGD  v1.9.0, support extended file specifications (ODS-5),
                          add a &nbsp; after sentence-terminating periods,
                          integrated MCJ modifications noted below
11-Feb-2000  MCJ  V1.8.2gw, Change <TD in <REVISION_INFO> to use WIDTH=50%
                            to align second column.
                            Add processing of <DEFINE_SYMBOL> using existing
                            NoteReference functionality.  One small change
                            to NoteReference to distinguish between symbols
                            generated by headings and those by explicit
                            symbol definitions.  The ReferenceData structure
                            now has a type field to accommodate this. Modify
                            MakeReference to add active links for heading
                            symbol references but to do simple text replacement
                            for the others.
                            Change <ABSTRACT> processing to not generate an
                            unmatched </BLOCKQUOTE>.
04-Feb-2000  MCJ  V1.8.1gw, Add handling for <DATE>, <DOCTYPE>, <APPENDIX>
                            and <ENDAPPENDIX>.  Appendix only handled like a
                            chapter at the moment.
                            Add <FOOTREF> using HTML <SUP>.
                            Change <REVISION_INFO> to use HTML <TABLE> to
                            better mirror SDML.
26-SEP-1999  MGD  v1.8.1, ReadSdmlLine() read check
16-JAN-1999  MGD  v1.8.0, add space before <HEAD>,
                          only use <H1>, <H2>, <H3> for headings,
                          default document colours,
                          make generated tables HTML 3.2 compliant,
                          force all HTML file specifications to lower case
23-JAN-1998  MGD  v1.7.0, "home page" copy of document name_0000.HTML,
                          "unmarkup" text for <TITLE></TITLE>
28-AUG-1997  MGD  v1.6.1, minor fiddle to [contents] link in framed documents
03-AUG-1997  MGD  v1.6.0, frames capability,
                          bugfix; "[next]" link truncated all links following
29-JUL-1997  MGD  v1.5.0, /BODY qualifier
27-MAY-1997  MGD  v1.4.0, added HTML DOCTYPE and META information
08-JUL-1996  MGD  v1.3.0, output file record format changed from VAR to STMLF;
                          bugfix in concluding a popup-file;
                          changes to table formatting for better compliance
24-MAY-1995  MGD  v1.2.0, minor changes for AXP compatibility
20-APR-1995  MGD  v1.1.2, added <H1>book-title</H1> to each chapter
23-JAN-1995  MGD  v1.1.1, remove "[" and "]" anchor descriptions
20-OCT-1994  MGD  v1.1.0, ignore tags outside <TITLE_PAGE>, <COPYRIGHT_PAGE>,
                          <PREFACE>, <CHAPTER> context ... shouldn't be there!
08-AUG-1994  MGD  v1.0.0, initial development
*/
/*****************************************************************************/

#define SOFTWAREVN "1.10.7"
#define SOFTWARENM "SDM2HTM"
#ifdef __ALPHA
   char SoftwareID [] = SOFTWARENM " AXP-" SOFTWAREVN;
#endif
#ifdef __ia64
   char SoftwareID [] = SOFTWARENM " IA64-" SOFTWAREVN;
#endif
#ifdef __VAX
   char SoftwareID [] = SOFTWARENM " VAX-" SOFTWAREVN;
#endif

/* standard C header files */
#include <stdio.h>
#include <ctype.h>
#include <errno.h>
#include <time.h>

/* VMS related header files */
#include <descrip.h>
#include <iodef.h>
#include <jpidef.h>
#include <libdtdef.h>
#include <rmsdef.h>
#include <rms.h>
#include <ssdef.h>
#include <stsdef.h>
#include <syidef.h>

/* application header files */
#include "enamel.h"

#ifndef __VAX
#   pragma nomember_alignment
#endif

#ifndef __VAX
#  ifndef NO_ODS_EXTENDED
#     define ODS_EXTENDED 1
      /* this is smaller than the technical maximum, but still quite large! */
#     define ODS_MAX_FILE_NAME_LENGTH 511
#     define ODS_MAX_FILESYS_NAME_LENGTH 264
#  endif
#endif
#define ODS2_MAX_FILE_NAME_LENGTH 255
#ifndef ODS_MAX_FILE_NAME_LENGTH
#  define ODS_MAX_FILE_NAME_LENGTH ODS2_MAX_FILE_NAME_LENGTH
#endif
#if ODS_MAX_FILE_NAME_LENGTH < ODS2_MAX_FILE_NAME_LENGTH
#  define ODS_MAX_FILE_NAME_LENGTH ODS2_MAX_FILE_NAME_LENGTH
#endif

#define boolean int
#define true 1
#define false 0
 
#define VMSok(x) ((x) & STS$M_SUCCESS)
#define VMSnok(x) !(((x) & STS$M_SUCCESS))
 
#define OUTPUT_LINE_SIZE 65536
#define BUFFER_SIZE 65536
#define INPUT_LINE_SIZE 2048
#define SYMBOL_SIZE 32
#define TAG_NAME_SIZE 32
#define MAX_HEAD_TAG 6

#define HTML_DOC_TYPE "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n"

#define DEFAULT_INDEX_PAGE_NAME "INDEX.HTML"

/**************************/
/* data type declarations */
/**************************/

struct SdmlFileData
{
   FILE  *FilePtr;
   char  FileName [ODS_MAX_FILE_NAME_LENGTH];
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH];
   char  InLine [INPUT_LINE_SIZE];
   int  LineNumber;
};

struct HtmlFileData
{
   FILE  *FilePtr;
   char  FileName [ODS_MAX_FILE_NAME_LENGTH];
   char  NameOfFile [ODS_MAX_FILE_NAME_LENGTH];
};

struct ReferenceData
{
   struct ReferenceData  *NextPtr;
   int  ReferenceNumber;
   char  NameOfFile [ODS_MAX_FILE_NAME_LENGTH];
   char  Symbol [SYMBOL_SIZE];
   char  NumberString [32];
   char  Title [256];
   char  Type;
};

struct ErrorData
{
   char  *TagNamePtr;
   int  TagLineNumber;
   char *SdmlFileNamePtr;
   int  SourceLineNumber;
};

/******************/
/* global storage */
/******************/

char  CopyrightInfo [] =
"Copyright (C) 1996-2004 Mark G.Daniel.\n\
This software comes with ABSOLUTELY NO WARRANTY.\n\
This is free software, and you are welcome to redistribute it\n\
under the conditions of the GNU GENERAL PUBLIC LICENSE, version 2.";

char  Utility[] = "SDM2HTM";

boolean  Debug,
         DoFramed,
         DoGenerateHtml,
         NoDocumentColour,
         DoVerbose,
         IncludeComments,
         IncludeSourceLineNumber,
         IncludeUnknownTags,
         FlagUnknownTags,
         HtmlOn,
         NoSentenceSpacing,
         OdsExtended,
         SentenceSpacing;

int  CompatibilityLevel,
     FrontMatterCount,
     InsideComment,
     InsideFormalExample,
     InsideFormalTable,
     InsideLiteral,
     InsidePreformattedText,
     InsideTagCount,
     Pass,
     ReferenceNumber,
     PopUpNumber,
     TableOfContentsLevel,
     TableOfContentsCount,
     TitlePageCount,
     TotalChapters,
     UnknownTagCount;

/* section numbers range from 0 (chapter) to 6 (head6) */
int  SectionNumber[MAX_HEAD_TAG+1];

char  CommandLine [256],
      ChapterTitle [256],
      ContentsFileName [ODS_MAX_FILE_NAME_LENGTH],
      DocumentTitle [256],
      HtmlBodyTag [256],
      HtmlName [ODS_MAX_FILE_NAME_LENGTH],
      IndexPageName [ODS_MAX_FILE_NAME_LENGTH],
      LastChars [2],
      OutputLine [OUTPUT_LINE_SIZE];

char  *BgColourPtr = " BGCOLOR=\"#ffffff\"",
      *CodeExampleColourPtr = " COLOR=\"#333333\"",
      *LinkColourPtr = " LINK=\"#0000cc\"",
      *TableRowColourPtr = " BGCOLOR=\"#eeeeee\"",
      *TextColourPtr = " TEXT=\"#000000\"",
      *VLinkColourPtr = " VLINK=\"#0000cc\"",
      *FrameTargetPage,
      *FrameTargetSelf,
      *FrameTargetToc,
      *FrameTargetTop,
      *HtmlDirectoryPtr,
      *OutputLinePtr,
      *SdmlFileSpecPtr;

int  ListItemCount [64];
char  EndListTag [64][8];
int  EndListTagIndex = 0;

FILE  *ContentsFilePtr;

struct HtmlFileData  HtmlFile;
struct SdmlFileData  SdmlFile;

struct ReferenceData  *RefPtr = NULL,
                      *LastPtr = NULL;

/***********************/
/* required prototypes */
/***********************/

char* AbsorbTagParameter (struct SdmlFileData*, char*);
char* ChapterReferences (struct ErrorData*, char*, int*, char*);
struct ReferenceData* FindReference (char*);
char* HtmlCopy (struct ErrorData*, char*, int*, char*, int);
char* MakeReference (struct HtmlFileData*, struct ErrorData*,
                     char*, int*, char*);
char *MetaInformation (struct SdmlFileData *SdmlFilePtr);
char* NavigationButtons (boolean);
void NoteReference (struct HtmlFileData*, char*, char*, char*, char);
char* ProcessSdmlLines (struct SdmlFileData*, struct HtmlFileData*, char*);
char* ProcessTag (struct SdmlFileData*, struct HtmlFileData*,
                  struct ErrorData*, char*, char*, int);
char* ProcessTagParameter (struct SdmlFileData*, struct HtmlFileData*,
                           struct ErrorData*, char*, char*, int);
char* RawHtml (struct SdmlFileData*, struct HtmlFileData*,
               struct ErrorData*, char*, char*, int);
char* ReadSdmlLine (struct SdmlFileData*);
char* SkipUnknownTag (struct SdmlFileData*, struct ErrorData*,
                      char*, char*, int);
char* StringCopy (struct ErrorData*, char*, int*, char*, int);
char* SysGetMsg (int);
char* UnmarkupTitle (char*);

/*****************************************************************************/
/*
*/

int main ()

{
   /*********/
   /* begin */
   /*********/

#ifdef ODS_EXTENDED
   OdsExtended = (GetVmsVersion() >= 72);
   ENAMEL_NAML_SANITY_CHECK
#endif /* ODS_EXTENDED */

   DoVerbose = DoGenerateHtml = NoSentenceSpacing = true;
   SentenceSpacing = false;
   HtmlDirectoryPtr = SdmlFileSpecPtr = "";

   GetParameters ();

   /* each two digits is major, then minor, then bugfix number */
   if (CompatibilityLevel <= 0)
      CompatibilityLevel = 999999;
   else
      fprintf (stdout, "%%%s-W-COMPATABILITY, level is %d\n",
               Utility, CompatibilityLevel);

   exit (SourceFile (SdmlFileSpecPtr));
}

/*****************************************************************************/
/*
Get "command-line" parameters, whether from the command-line or from a
configuration symbol or logical containing the equivalent.
*/

GetParameters ()

{
   static char  CommandLine [256];
   static unsigned long  Flags = 0;

   int  status;
   unsigned short  Length;
   char  *aptr, *cptr, *clptr, *sptr;
   char  ch;
   $DESCRIPTOR (CommandLineDsc, CommandLine);

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "GetParameters()\n");

   if ((clptr = getenv ("SDM2HTM$PARAM")) == NULL)
   {
      /* get the entire command line following the verb */
      if (VMSnok (status =
          lib$get_foreign (&CommandLineDsc, 0, &Length, &Flags)))
         exit (status);
      (clptr = CommandLine)[Length] = '\0';
   }

   aptr = NULL;
   ch = *clptr;
   for (;;)
   {
      if (aptr != NULL && *aptr == '/') *aptr = '\0';
      if (!ch) break;

      *clptr = ch;
      if (Debug) fprintf (stdout, "clptr |%s|\n", clptr);
      while (*clptr && isspace(*clptr)) *clptr++ = '\0';
      aptr = clptr;
      if (*clptr == '/') clptr++;
      while (*clptr && !isspace (*clptr) && *clptr != '/')
      {
         if (*clptr != '\"')
         {
            clptr++;
            continue;
         }
         cptr = clptr;
         clptr++;
         while (*clptr)
         {
            if (*clptr == '\"')
               if (*(clptr+1) == '\"')
                  clptr++;
               else
                  break;
            *cptr++ = *clptr++;
         }
         *cptr = '\0';
         if (*clptr) clptr++;
      }
      ch = *clptr;
      if (*clptr) *clptr = '\0';
      if (Debug) fprintf (stdout, "aptr |%s|\n", aptr);
      if (!*aptr) continue;

      /**************/
      /* parameters */
      /**************/

      if (strsame (aptr, "/COMMENTS", 5))
      {
         IncludeComments = true;
         continue;
      }
      if (strsame (aptr, "/NOCOMMENTS", 7))
      {
         IncludeComments = false;
         continue;
      }

      if (strsame (aptr, "/COMPATIBILITY=", 5))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         CompatibilityLevel = atoi(cptr);
         continue;
      }

      if (strsame (aptr, "/BODY=", 4))
      {
         strcpy (sptr = HtmlBodyTag, "<BODY ");
         sptr += 6;
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         while (*cptr) *sptr++ = *cptr++;
         strcpy (sptr, ">\n");
         continue;
      }

      if (strsame (aptr, "/COLOUR", 4))
      {
         NoDocumentColour = false;
         continue;
      }
      if (strsame (aptr, "/NOCOLOUR", 6))
      {
         NoDocumentColour = true;
         continue;
      }

      if (strsame (aptr, "/DBUG", -1))
      {
         Debug = true;
         continue;
      }

      if (strsame (aptr, "/DIRECTORY=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         HtmlDirectoryPtr = aptr;
         continue;
      }

      if (strsame (aptr, "/FLAG_UNKNOWN_TAGS", 4))
      {
         FlagUnknownTags = true;
         continue;
      }
      if (strsame (aptr, "/NOFLAG_UNKNOWN_TAGS", 6))
      {
         FlagUnknownTags = false;
         continue;
      }

      if (strsame (aptr, "/FRAMED", 4))
      {
         DoFramed = true;
         continue;
      }
      if (strsame (aptr, "/NOFRAMED", 6))
      {
         DoFramed = false;
         continue;
      }

      if (strsame (aptr, "/HTML", 4))
      {
         DoGenerateHtml = true;
         continue;
      }
      if (strsame (aptr, "/NOHTML", 6))
      {
         DoGenerateHtml = false;
         continue;
      }

      if (strsame (aptr, "/INDEX=", 4))
      {
         for (cptr = aptr; *cptr && *cptr != '='; cptr++);
         if (*cptr) cptr++;
         *(sptr = IndexPageName) = '\0';
         while (*cptr) *sptr++ = *cptr++;
         *sptr = '\0';
         if (!IndexPageName[0])
            strcpy (IndexPageName, DEFAULT_INDEX_PAGE_NAME);
         continue;
      }

      if (strsame (aptr, "/SOURCE", 4))
      {
         IncludeSourceLineNumber = true;
         continue;
      }

      if (strsame (aptr, "/ODS5", 5))
      {
         OdsExtended = true;
         continue;
      }
      if (strsame (aptr, "/NOODS5", 7))
      {
         OdsExtended = false;
         continue;
      }

      if (strsame (aptr, "/SENTENCE_SPACING", 5))
      {
         NoSentenceSpacing = false;
         continue;
      }
      if (strsame (aptr, "/NOSENTENCE_SPACING", 7))
      {
         NoSentenceSpacing = true;
         continue;
      }

      if (strsame (aptr, "/UNKNOWN_TAGS", 4))
      {
         IncludeUnknownTags = true;
         continue;
      }
      if (strsame (aptr, "/NOUNKNOWN_TAGS", 6))
      {
         IncludeUnknownTags = false;
         continue;
      }

      if (strsame (aptr, "/VERBOSE", 5))
      {
         DoVerbose = true;
         continue;
      }
      if (strsame (aptr, "/NOVERBOSE", 6))
      {
         DoVerbose = false;
         continue;
      }

      if (strsame (aptr, "/VERSION", 5))
      {
         fprintf (stdout, "%%%s-I-SOFTWAREID, %s\n%s\n",
                  Utility, SoftwareID, CopyrightInfo);
         exit (SS$_NORMAL);
      }

      if (*aptr == '/')
      {
         fprintf (stdout, "%%%s-E-IVQUAL, unrecognized qualifier\n \\%s\\\n",
                  Utility, aptr+1);
         exit (STS$K_ERROR | STS$M_INHIB_MSG);
      }

      if (!SdmlFileSpecPtr[0])
      {
         SdmlFileSpecPtr = aptr;
         continue;
      }

      fprintf (stdout, "%%%s-E-MAXPARM, too many parameters\n \\%s\\\n",
               Utility, aptr);
      exit (STS$K_ERROR | STS$M_INHIB_MSG);
   }
}

/*****************************************************************************/
/*
Using 'SdmlFileSpecPtr' find matching file(s), process SDML text into HTML text.
*/ 

SourceFile (char *FileSpec)

{
   int  status,
        FileCount = 0;
   unsigned short  Length;
   char  c;
   char  *cptr,
         *NamDevicePtr,
         *NamNamePtr,
         *NamTypePtr,
         *NamVersionPtr;
   char  ExpFileName [ODS_MAX_FILE_NAME_LENGTH];
   struct FAB  ParseFab;
   struct NAM  ParseNam;
#ifdef ODS_EXTENDED
   struct NAML  ParseNaml;
#endif /* ODS_EXTENDED */

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "SourceFile() |%s|\n", FileSpec);

   ParseFab = cc$rms_fab;

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      ParseFab.fab$l_dna = ParseFab.fab$l_fna = -1;
      ParseFab.fab$b_dns = ParseFab.fab$b_fns = 0;
      ParseFab.fab$l_nam = &ParseNaml;

      ENAMEL_RMS_NAML(ParseNaml)
      ParseNaml.naml$l_long_defname = "*.SDML";
      ParseNaml.naml$l_long_defname_size = 6;
      ParseNaml.naml$l_long_filename = FileSpec;
      ParseNaml.naml$l_long_filename_size = strlen(FileSpec);
      ParseNaml.naml$l_long_expand = ExpFileName;
      ParseNaml.naml$l_long_expand_alloc = sizeof(ExpFileName)-1;
   }
   else
#endif /* ODS_EXTENDED */
   {
      ParseFab.fab$l_dna = "*.SDML";
      ParseFab.fab$b_dns = 6;
      ParseFab.fab$l_fna = FileSpec;
      ParseFab.fab$b_fns = strlen(FileSpec);
      ParseFab.fab$l_nam = &ParseNam;

      ParseNam = cc$rms_nam;
      ParseNam.nam$l_esa = ExpFileName;
      ParseNam.nam$b_ess = ODS2_MAX_FILE_NAME_LENGTH;
   }

   if (VMSnok (status = sys$parse (&ParseFab, 0, 0)))
      return (status);

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      NamDevicePtr = ParseNaml.naml$l_long_dev;
      NamNamePtr = ParseNaml.naml$l_long_name;
      NamTypePtr = ParseNaml.naml$l_long_type;
      NamVersionPtr = ParseNaml.naml$l_long_ver;
   }
   else
#endif /* ODS_EXTENDED */
   {
      NamDevicePtr = ParseNam.nam$l_dev;
      NamNamePtr = ParseNam.nam$l_name;
      NamTypePtr = ParseNam.nam$l_type;
      NamVersionPtr = ParseNam.nam$l_ver;
   }

#ifdef ODS_EXTENDED
   if (OdsExtended)
   {
      if (ParseNaml.naml$l_fnb & NAM$M_WILDCARD)
      {
         fprintf (stdout,
         "%%%s-E-WLD, wildcards not allowed for input specification\n",
                  Utility);
         return (STS$K_ERROR | STS$M_INHIB_MSG);
      }
   }
   else
#endif /* ODS_EXTENDED */
   if (ParseNam.nam$l_fnb & NAM$M_WILDCARD)
   {
      fprintf (stdout,
      "%%%s-E-WLD, wildcards not allowed for input specification\n",
               Utility);
      return (STS$K_ERROR | STS$M_INHIB_MSG);
   }

   NamVersionPtr[0] = '\0';
   if (Debug) fprintf (stdout, "ExpFileName |%s|\n", ExpFileName);

   if (!HtmlDirectoryPtr[0])
   {
      c = NamNamePtr[0];
      NamNamePtr[0] = '\0';
      strcpy (HtmlDirectoryPtr, NamDevicePtr);
      for (cptr = HtmlDirectoryPtr; *cptr; cptr++) *cptr = tolower(*cptr);
      NamNamePtr[0] = c;
   }
   NamTypePtr[0] = '\0';
   strcpy (HtmlName, NamNamePtr);
   for (cptr = HtmlName; *cptr; cptr++) *cptr = tolower(*cptr);
   NamTypePtr[0] = '.';
   if (Debug) fprintf (stdout, "|%s|%s|\n", HtmlDirectoryPtr, HtmlName);

   NamVersionPtr[0] = '\0';
   strcpy (SdmlFile.FileName, ExpFileName);
   if (DoVerbose) fprintf (stdout, "%%%s-I-FILE, %s\n", Utility, ExpFileName);
   NamVersionPtr[0] = ';';

   SdmlFile.InLine[0] = HtmlFile.FileName[0] = HtmlFile.NameOfFile[0] = '\0';

   if (DoFramed)
   {
      FrameTargetPage = " TARGET=\"frame_page\"";
      FrameTargetSelf = " TARGET=\"_self\"";
      FrameTargetToc = " TARGET=\"frame_toc\"";
      FrameTargetTop = "TARGET=\"_top\" ";
   }
   else
      FrameTargetPage = FrameTargetSelf = FrameTargetToc = FrameTargetTop = "";

   if (NoDocumentColour)
      BgColourPtr = CodeExampleColourPtr = TextColourPtr =
         LinkColourPtr = VLinkColourPtr = TableRowColourPtr = "";

   if (!HtmlBodyTag[0])
      sprintf (HtmlBodyTag, "<BODY%s%s%s%s>\n",
               BgColourPtr, TextColourPtr, LinkColourPtr, VLinkColourPtr);

   /**************/
   /* first pass */
   /**************/

   /* count chapters, gets title, etc., */
   Pass = 1;
   if (DoVerbose) fprintf (stdout, "%%%s-I-PASS, 1\n", Utility);

   strcpy (DocumentTitle, "*** UNTITLED ***"); 
   TotalChapters = 0;

   FrontMatterCount =
      InsideComment =
      InsideFormalExample =
      InsideFormalTable =
      InsideLiteral =
      InsidePreformattedText =
      InsideTagCount =
      PopUpNumber =
      ReferenceNumber =
      SectionNumber[0] =
      TableOfContentsLevel =
      UnknownTagCount = 0;

   HtmlOn = true;

   if (VMSnok (status = ProcessSdmlFile (&SdmlFile, &HtmlFile)))
      return (status);

   TotalChapters = SectionNumber[0];

   /***************/
   /* second pass */
   /***************/

   /* outputs document */
   Pass = 2;
   if (DoVerbose) fprintf (stdout, "%%%s-I-PASS, 2\n", Utility);

   FrontMatterCount =
      InsideComment =
      InsideFormalExample =
      InsideFormalTable =
      InsideLiteral =
      InsidePreformattedText =
      InsideTagCount =
      PopUpNumber =
      ReferenceNumber =
      SectionNumber[0] =
      TableOfContentsLevel =
      UnknownTagCount = 0;

   HtmlOn = true;

   BeginContents (&SdmlFile);

   status = ProcessSdmlFile (&SdmlFile, &HtmlFile);

   EndHtmlFile (&SdmlFile, &HtmlFile);
   EndContents ();

   if (DoFramed) WriteFrameFile (&SdmlFile);

   if (IndexPageName[0]) WriteIndexPage ();

   if (DoVerbose && UnknownTagCount)
      fprintf (stdout, "%%%s-W-TAG, unknown tag(s) encountered %d time(s)\n",
               Utility, UnknownTagCount);

   return (status);
}

/*****************************************************************************/
/*
Recursively called function.

Open input SDML text file, process into HTML text file.
*/ 

int ProcessSdmlFile
(
struct SdmlFileData *SdmlFilePtr,
struct HtmlFileData *HtmlFilePtr
)
{
   int  status;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "ProcessSdmlFile() |%s|\n", SdmlFilePtr->FileName);

   SdmlFilePtr->FilePtr = fopen (SdmlFilePtr->FileName, "r");
   if (SdmlFilePtr->FilePtr == NULL)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-SDMLFILE, opening %s\n-%s\n",
               Utility, SdmlFilePtr->FileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   if (!fgetname (SdmlFilePtr->FilePtr, SdmlFilePtr->ExpFileName))
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-SDMLFILE, getting full name %s\n-%s\n",
               Utility, SdmlFilePtr->FileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }
   for (cptr = SdmlFilePtr->ExpFileName; *cptr && *cptr != ';'; cptr++);
   if (*cptr) *cptr = '\0';
   fprintf (stdout, "[Processing %s]\n", SdmlFilePtr->FileName);

   if (ReadSdmlLine (SdmlFilePtr) == NULL)
   {
      fclose (SdmlFilePtr->FilePtr);
      SdmlFilePtr->FilePtr = NULL;
      return (SS$_NORMAL);
   }

   ProcessSdmlLines (SdmlFilePtr, HtmlFilePtr, SdmlFilePtr->InLine);

   fclose (SdmlFilePtr->FilePtr);
   SdmlFilePtr->FilePtr = NULL;

   return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Recursively called function.

Read lines (records) in the SDML text file, processing into HTML text.  This 
line processing can be terminated by end-of-file or by a tag returning a 
pseudo-tag indicating that a recursive call to this function should be 
concluded.  In this case a single SDML source file may not yet be exhausted.

If an opening '<' of a tag is encountered the function ProcessTag() is called 
to process this tag.  If it is tag with no parameters then a conversion into 
SDML will be made and returned in 'Scratch'.  If it has parameter (e.g. 
<tag>(...)), then it will in turn call function ProcessTagParameter(), which 
may in turn encounter tags within the parameter itself (e.g. "<UNDERLINE>(this 
is some <EMPHASIS>(example\BOLD) text)").  Eventually this function will be 
returned to with text built up into 'Scratch', which is then copied to the 
HTML output buffer.
*/ 

char* ProcessSdmlLines
(
struct SdmlFileData *SdmlFilePtr,
struct HtmlFileData *HtmlFilePtr,
char *sdptr
)
{
   static int  RecursionLevel = 0;

   int  status,
        Capacity;
   char  Scratch [BUFFER_SIZE];
   struct ErrorData  ErrorInfo;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ProcessSdmlLines(%d)\n", ++RecursionLevel);

   ErrorInfo.SdmlFileNamePtr = SdmlFilePtr->FileName;

   FlushOutputLine (HtmlFilePtr);
   Capacity = sizeof(OutputLine);

   /*******************/
   /* read lines loop */
   /*******************/

   for (;;)
   {
      /*********************/
      /* process line loop */
      /*********************/

      while (*sdptr)
      {
         if (*sdptr == '<')
         {
            ErrorInfo.TagNamePtr = sdptr;
            ErrorInfo.TagLineNumber = SdmlFilePtr->LineNumber;

            sdptr = ProcessTag (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                sdptr, Scratch, sizeof(Scratch));

            /***********************************************************/
            /* detect pseudo-tag to exit from a recusive function call */
            /***********************************************************/

            if (strsame (Scratch, "<PSEUDO-END>", -1))
            {
               FlushOutputLine (HtmlFilePtr);
               RecursionLevel--;
               return (sdptr);
            }

            Capacity = sizeof(OutputLine) - 1 -
                       ((int)OutputLinePtr - (int)OutputLine);
            OutputLinePtr = StringCopy (NULL, OutputLinePtr, &Capacity,
                                        Scratch, -1);
         }
         else
         {
            if (HtmlOn && (!InsideComment || IncludeComments)) 
               OutputLinePtr = HtmlCopy (NULL, OutputLinePtr, &Capacity,
                                         sdptr, 1);

            /* perhaps sentence terminating punctuation? */
            if (!NoSentenceSpacing && SentenceSpacing &&
                (*sdptr == '.' || *sdptr == '?' || *sdptr == '!') &&
                ((isalpha(LastChars[0]) && isalpha(LastChars[1])) ||
                 (LastChars[0] == ')' && isalpha(LastChars[1]))) &&
                (isspace(*(sdptr+1)) || !*(sdptr+1)))
               OutputLinePtr = StringCopy (NULL, OutputLinePtr, &Capacity,
                                           "&nbsp;", 6);
            LastChars[1] = LastChars[0];
            LastChars[0] = *sdptr;

            /* this increment must be outside the if() statement above */
            sdptr++;
         }
      }

      /*************************/
      /* end process line loop */
      /*************************/

      FlushOutputLine (HtmlFilePtr);
      Capacity = sizeof(OutputLine);

      if (ReadSdmlLine (SdmlFilePtr) == NULL)
      {
         /***************/
         /* end-of-file */
         /***************/

         FlushOutputLine (HtmlFilePtr);
         RecursionLevel--;
         return (sdptr);
      }

      sdptr = SdmlFilePtr->InLine;

      /* add a space to the output buffer */
      if (InsidePreformattedText && !*sdptr)
         OutputLinePtr = HtmlCopy (NULL, OutputLinePtr, &Capacity, " ", -1);
   }
}

/*****************************************************************************/
/*
Recursively called function.

Some SDML tags have parameters.  One, or more, strings between parentheses, 
e.g. "<tag>(...)".  If multiple parameters then each is separated by a 
backslash, e.g. "\".  This function obtains one parameter from between the 
tag's "(...)".  If multiple parameters exists then each may be obtained by 
calling this function multiple times.  The terminating character, a ")" or "\" 
is returned being pointed at by 'sdptr' so that progress may be determined by 
the calling routine.

Calling this function with parameter 'htptr' set to NULL effectively absorbs 
any tag parameter.
*/ 

char* ProcessTagParameter
(
struct SdmlFileData  *SdmlFilePtr,
struct HtmlFileData  *HtmlFilePtr,
struct ErrorData *ErrorInfoPtr,
char *sdptr,
char *htptr,
int Capacity
)
{
   static int  RecursionLevel = 0;

   int  status,
        ParenthesesCount = 0;
   char  Buffer [BUFFER_SIZE];

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ProcessTagParameter(%d)\n", ++RecursionLevel);

   /* allow for the terminating null */
   if (Capacity) Capacity--;

   /*******************/
   /* read lines loop */
   /*******************/

   for (;;)
   {
      /*********************/
      /* process line loop */
      /*********************/

      while (*sdptr)
      {
         if (!InsideComment && *sdptr == '<')
         {
            /********************************************/
            /* start of nested tag, process recursively */
            /********************************************/

            sdptr = ProcessTag (SdmlFilePtr, HtmlFilePtr, ErrorInfoPtr,
                                sdptr, Buffer, sizeof(Buffer));
            if (htptr != NULL)
            {
               ErrorInfoPtr->SourceLineNumber = __LINE__;
               htptr = StringCopy (ErrorInfoPtr, htptr, &Capacity, Buffer, -1);
            }
         }
         else
         if (*sdptr == '\\')
         {
            /**************************/
            /* start of new parameter */
            /**************************/

            RecursionLevel--;
            return (sdptr);
         }
         else
         if (*sdptr == '(')
         {
            /*****************************************/
            /* opening parenthesis (not tag-related) */
            /*****************************************/

            ParenthesesCount++;
            if (htptr != NULL) 
            {
               ErrorInfoPtr->SourceLineNumber = __LINE__;
               htptr = StringCopy (ErrorInfoPtr, htptr, &Capacity, sdptr, 1);
            }
            sdptr++;
         }
         else
         if (*sdptr == ')')
         {
            if (!ParenthesesCount)
            {
               /********************/
               /* end of parameter */
               /********************/

               InsideTagCount--;
               RecursionLevel--;
               /* return pointing at the tag parameter termination character */
               return (sdptr);
            }
            else
            {
               /*****************************************/
               /* closing parenthesis (not tag-related) */
               /*****************************************/

               ParenthesesCount--;
               if (htptr != NULL)
               {
                  ErrorInfoPtr->SourceLineNumber = __LINE__;
                  htptr = StringCopy (ErrorInfoPtr, htptr, &Capacity, sdptr, 1);
               }
               sdptr++;
            }
         }
         else
         {
            /***************************/
            /* copy a single character */
            /***************************/

            if (htptr != NULL)
            {
               ErrorInfoPtr->SourceLineNumber = __LINE__;
               htptr = HtmlCopy (ErrorInfoPtr, htptr, &Capacity, sdptr, 1);
            }

            /* perhaps sentence terminating punctuation? */
            if (!NoSentenceSpacing && SentenceSpacing &&
                (*sdptr == '.' || *sdptr == '?' || *sdptr == '!') &&
                ((isalpha(LastChars[0]) && isalpha(LastChars[1])) ||
                 (LastChars[0] == ')' && isalpha(LastChars[1]))) &&
                (isspace(*(sdptr+1)) || *(sdptr+1) == '\\'  ||
                 *(sdptr+1) == ')' || !*(sdptr+1)))
               htptr = StringCopy (NULL, htptr, &Capacity, "&nbsp;", 6);
            LastChars[1] = LastChars[0];
            LastChars[0] = *sdptr;

            sdptr++;
         }
      }

      if (ReadSdmlLine (SdmlFilePtr) == NULL)
      {
         /***************/
         /* end-of-file */
         /***************/

         RecursionLevel--;
         return (sdptr);
      }

      sdptr = SdmlFilePtr->InLine;

      /* add a new line to the output buffer */
      if (htptr != NULL)
      {
         ErrorInfoPtr->SourceLineNumber = __LINE__;
         htptr = StringCopy (ErrorInfoPtr, htptr, &Capacity, "\n", 1);
      }
   }
}

/*****************************************************************************/
/*
Recursively called function.

Sorry about its length!

The opening '<' of an SDML tag has been encountered.  It is pointed to by 
'sdptr'.  Get the tag name.  By string comparison find it, and process it. If 
not a known tag then absorb it if a tag without parameters, e.g. "<tag>", or 
absorb all between the parentheses if a tag with parameters, e.g. 
"<tag>(...)".

The 'htptr' that the tag is processed into is not directly for output into the 
HTML output file.  When functions ProcessSdmlLines() and 
ProcessTagParameters() encounter an opening '<' of a tag this function, 
ProcessTag(), is called with 'htptr' pointing at a fixed sized buffer.  When 
function ProcessTag() finishes processing the tag and returns the function 
that called it resumes with that buffer containing the
*/ 
 
char* ProcessTag
(
struct SdmlFileData *SdmlFilePtr,
struct HtmlFileData *HtmlFilePtr,
struct ErrorData *ErrorInfoPtr,
char *sdptr,
char *htptr,
int Capacity
)
{
   static int  RecursionLevel = 0,
               TableRowCount = 0;

   int  status,
        Count;
   char  *cptr, *sptr;
   char  HeadDigit;
   char  Buffer [BUFFER_SIZE],
         NumberString [32],
         Scratch [BUFFER_SIZE],
         Symbol [SYMBOL_SIZE],
         TagName [TAG_NAME_SIZE];
   struct HtmlFileData  PopUpFile;
   struct SdmlFileData  IncludeFile;
   struct ErrorData  ErrorInfo;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "ProcessTag(%d)\n", ++RecursionLevel);

   /* allow for the terminating null */
   if (Capacity) Capacity--;

   *htptr = Buffer[0] = Scratch[0] = Symbol[0] = '\0';

   /****************************/
   /* handle literal tag first */
   /****************************/

   if (InsideLiteral)
   {
      if (strsame (sdptr, "<ENDLITERAL>", 12))
      {
         InsideLiteral--;
         sdptr += 12;
      }
      else
      {
         ErrorInfoPtr->SourceLineNumber = __LINE__;
         htptr = HtmlCopy (ErrorInfoPtr, htptr, &Capacity, sdptr++, 1);
      }

      RecursionLevel--;
      return (sdptr);
   }

   /**********************************************/
   /* handle HTML control sequences specifically */
   /**********************************************/

   if (strsame (sdptr, "<COMMENT>(", 10))
   {
      ErrorInfo.TagNamePtr = "<COMMENT>";
      ErrorInfo.TagLineNumber = SdmlFilePtr->LineNumber;
      ErrorInfo.SdmlFileNamePtr = SdmlFilePtr->FileName;

      if (strsame (sdptr+10, "HTML=", 5))
      {
         SentenceSpacing = false;
         sdptr = RawHtml (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                          sdptr+15, Buffer, sizeof(Buffer));
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         RecursionLevel--;
         SentenceSpacing = true;
         return (sdptr);
      }

      if (strsame (sdptr+10, "HTML/ON)", 8))
      {
         SentenceSpacing = true;
         HtmlOn = true;
         sdptr += 18;
         RecursionLevel--;
         return (sdptr);
      }

      if (strsame (sdptr+10, "HTML/OFF)", 9))
      {
         SentenceSpacing = false;
         HtmlOn = false;
         sdptr += 19;
         RecursionLevel--;
         return (sdptr);
      }

      if (strsame (sdptr+10, "HTML/POPUP=", 11))
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+21, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Symbol, sizeof(Symbol));
         if (*sdptr == ')') sdptr++;

         GenerateHtmlFileName (&PopUpFile, SectionNumber[0], ++PopUpNumber);
      
         /* create an HTML anchor point in the document for the example file */
         if (!Buffer[0]) strcpy (Buffer, "(no title)");
         sprintf (Scratch,
"\n\
<P>\n\
<A HREF=\"%s\"%s>%s</A>\n\
<P>\n",
         PopUpFile.NameOfFile, FrameTargetSelf, Buffer);
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

         /* flush any remaining output into the current HTML file */
         FlushOutputLine (HtmlFilePtr);

         CreateHtmlFile (&PopUpFile, SdmlFilePtr);

         sprintf (OutputLinePtr,
"%s\
<HTML>\n\
<HEAD>\n\
%s\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
%s\
<A NAME=\"%d\">\n\
<H2>%s</H2>\n\
</A>",
         HTML_DOC_TYPE, MetaInformation(SdmlFilePtr),
         UnmarkupTitle(ChapterTitle), HtmlBodyTag, ++ReferenceNumber, Buffer);
         while (*OutputLinePtr) OutputLinePtr++;

         NoteReference (HtmlFilePtr, Symbol, "", Buffer, 0);

         /* recursive call to process the SDML text into the example file */
         sdptr = ProcessSdmlLines (SdmlFilePtr, &PopUpFile, sdptr);

         strcpy (OutputLinePtr, "</BODY>\n</HTML>\n");
         while (*OutputLinePtr) OutputLinePtr++;

         /* flush output into the current HTML file */
         FlushOutputLine (&PopUpFile);

         CloseHtmlFile (&PopUpFile);

         RecursionLevel--;
         return (sdptr);
      }

      if (strsame (sdptr+10, "HTML/ENDPOPUP)", 14))
      {
         /*
            This psuedo-tag is detected and actioned in function
            ProcessSdmlLines() to terminate the recursive call made
            to it by "HTML/POPUP=".
         */
         sdptr += 24;
         strcpy (htptr, "<PSEUDO-END>");
         htptr += 12;
         RecursionLevel--;
         return (sdptr);
      }
   }
   if (!HtmlOn)
   {
      /* absorb the '<' character */
      sdptr++;
      RecursionLevel--;
      return (sdptr);
   }

   /********************/
   /* get the tag name */
   /********************/

   /* put the tag information into the error data structure */
   ErrorInfo.TagNamePtr = sdptr;
   ErrorInfo.TagLineNumber = SdmlFilePtr->LineNumber;
   ErrorInfo.SdmlFileNamePtr = SdmlFilePtr->FileName;
   ErrorInfo.SourceLineNumber = __LINE__;

   Count = sizeof(TagName)-1;
   cptr = TagName;
   while (*sdptr && *sdptr != '>')
   {
      *cptr++ = *sdptr++;
      if (!Count--) BufferOverflow (&ErrorInfo);
   }
   *cptr++ = *sdptr++;
   *cptr = '\0';
   if (Debug) fprintf (stdout, "TagName |%s|\n", TagName);

   /* put the tag name into the error data structure */
   ErrorInfo.TagNamePtr = TagName;

   /***********************************/
   /* handle comment tag specifically */
   /***********************************/

   if (strsame (TagName, "<ENDCOMMENT>", -1))
   {
      if (InsideComment) InsideComment--;
      if (IncludeComments && !InsideComment)
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, " -->", -1);
      }
      RecursionLevel--;
      return (sdptr);
   }

   if (InsideComment)
   {
      if (IncludeComments)
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, TagName, -1);
      }
      RecursionLevel--;
      return (sdptr);
   }

   if (strsame (TagName, "<COMMENT>", -1))
   {
      if (*sdptr == '(')
      {
         if (IncludeComments && !InsideComment++)
         {
            ErrorInfo.SourceLineNumber = __LINE__;
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<!-- ", -1);
         }
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Scratch, sizeof(Scratch));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         if (InsideComment) InsideComment--;
         if (IncludeComments)
         {
            ErrorInfo.SourceLineNumber = __LINE__;
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);
            if (!InsideComment)
            {
               ErrorInfo.SourceLineNumber = __LINE__;
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity, " -->", -1);
            }
         }
      }
      else
      {
         if (IncludeComments && !InsideComment)
         {
            ErrorInfo.SourceLineNumber = __LINE__;
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<!-- ", -1);
         }
         InsideComment++;
      }
      RecursionLevel--;
      return (sdptr);
   }

   /*********************************/
   /* identify and process SDML tag */
   /*********************************/

   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ABSTRACT>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\n<BLOCKQUOTE>", -1);
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<BR><BR>\n", -1);
/*         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             "</BLOCKQUOTE>\n", -1);		*/
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDABSTRACT>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</BLOCKQUOTE>\n", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<AMPERSAND>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "&amp;", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<BACKSLASH>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\\", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<BOX>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<TT>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</TT>", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<CENTER_LINE>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Scratch, sizeof(Scratch));
         if (*sdptr == ')') sdptr++;
      }
      ErrorInfo.SourceLineNumber = __LINE__;
      if (strsame (Scratch, "BIGSKIP", -1) ||
          strsame (Scratch, "SMALLSKIP", -1))
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<P><CENTER>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</CENTER>", -1);
      }
      else
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<CENTER>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</CENTER>", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<CHAPTER>", -1))
   {
      /* finalize any previous HTML file (from previous chapters, etc.) */
      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);

      /* increment the chapter number */
      SectionNumber[0]++;
      /* set all head counts back to zero */
      for (Count = 1; Count <= MAX_HEAD_TAG; SectionNumber[Count++] = 0);
      /* no popup sections yet (formal examples, tables, etc.) */
      PopUpNumber = 0;

      GenerateHtmlFileName (HtmlFilePtr, SectionNumber[0], PopUpNumber);
      CreateHtmlFile (HtmlFilePtr, SdmlFilePtr);

      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HTML_DOC_TYPE, -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "<HTML>\n<HEAD>\n", -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          MetaInformation(SdmlFilePtr), -1);

      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                              sdptr+1, ChapterTitle, sizeof(ChapterTitle));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Symbol, sizeof(Symbol));
         if (*sdptr == ')') sdptr++;
      }

      sprintf (NumberString, "%d", SectionNumber[0]);

      sprintf (Scratch, "<TITLE>%s</TITLE>\n",
               UnmarkupTitle(ChapterTitle));
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</HEAD>\n", -1);

      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HtmlBodyTag, -1);

      sprintf (Scratch, "<H1>%s</H1>\n", DocumentTitle);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

      if (CompatibilityLevel >= 11000)
      {
         sprintf (Scratch,
"\n\
<A NAME=\"%d\">\n\
<H2>%s - %s</H2>\n\
</A>",
         ++ReferenceNumber, NumberString, ChapterTitle);
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = ChapterReferences (&ErrorInfo, htptr, &Capacity, NumberString);
      }

      sptr = NavigationButtons (false);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, sptr, -1);

      if (CompatibilityLevel < 11000)
      {
         sprintf (Scratch,
"\n\
<A NAME=\"%d\">\n\
<H2>%s - %s</H2>\n\
</A>",
         ++ReferenceNumber, NumberString, ChapterTitle);
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);
      }

      NoteReference (HtmlFilePtr, Symbol, NumberString, ChapterTitle, 0);

      WriteContentsItem (HtmlFilePtr, 0, Symbol);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<CODE_EXAMPLE>", -1) ||
       strsame (TagName, "<DISPLAY>", -1))
   {
      /* do not interfere with any periods in this test */
      SentenceSpacing = false;
      if (!InsidePreformattedText++)
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\n", 1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<FONT", 5);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             CodeExampleColourPtr, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "><PRE>", 6);
      }
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         if (strsame (Buffer, "KEEP", -1) ||
             strsame (Buffer, "WIDE", -1) ||
             strsame (Buffer, "WIDE/MAXIMUM", -1) ||
             strsame (Buffer, "MAXIMUM", -1))
         {
            /* prevent a newline being generated immediately after "<PRE>" */
            while (*sdptr && isspace(*sdptr)) sdptr++;
            if (!*sdptr)
            {
               ReadSdmlLine (SdmlFilePtr);
               sdptr = SdmlFilePtr->InLine;
            }
         }
         else
         {
            if (InsidePreformattedText) InsidePreformattedText--;
            ErrorInfo.SourceLineNumber = __LINE__;
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
            if (!InsidePreformattedText)
            {
               ErrorInfo.SourceLineNumber = __LINE__;
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                   "</PRE></FONT>", -1);
            } 
         } 
      }
      else
      {
         /* prevent a newline being generated immediately after the "<PRE>" */
         while (*sdptr && isspace(*sdptr)) sdptr++;
         if (!*sdptr)
         {
            ReadSdmlLine (SdmlFilePtr);
            sdptr = SdmlFilePtr->InLine;
         }
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDCODE_EXAMPLE>", -1) ||
       strsame (TagName, "<ENDDISPLAY>", -1))
   {
      SentenceSpacing = true;
      if (InsidePreformattedText) InsidePreformattedText--;
      if (!InsidePreformattedText)
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             "</PRE></FONT>", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<CONTENTS_FILE>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<CP>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<COPYRIGHT_PAGE>", -1))
   {
      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);

      strcpy (ChapterTitle, "Copyright Page");
      GenerateHtmlFileName (HtmlFilePtr, 0, ++FrontMatterCount);
      CreateHtmlFile (HtmlFilePtr, SdmlFilePtr);

      sprintf (Scratch, "<TITLE>%s</TITLE>\n<H1>%s</H1>\n",
               UnmarkupTitle(ChapterTitle), DocumentTitle);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

      /* in this case 'Symbol' will be generated by NoteReference() */
      ReferenceNumber++;
      NoteReference (HtmlFilePtr, Symbol, "", ChapterTitle, 0);
      WriteContentsItem (HtmlFilePtr, 0, Symbol);

      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HTML_DOC_TYPE, -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "<HTML>\n<HEAD>\n", -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          MetaInformation(SdmlFilePtr), -1);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</HEAD>\n", -1);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HtmlBodyTag, -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDCOPYRIGHT_PAGE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "</BODY>\n</HTML>\n", -1);
      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<CPAREN>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, ")", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<DOUBLEQUOTE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\"", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ELLIPSIS>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      if (InsidePreformattedText)
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             "\n.\n.\n.\n", -1);
      else
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             "<BR>.\n<BR>.\n<BR>.\n<BR>", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<EMPHASIS>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Scratch, sizeof(Scratch));
         if (*sdptr == ')') sdptr++;
      }
      ErrorInfo.SourceLineNumber = __LINE__;
      if (strsame (Scratch, "BOLD", -1))
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<B>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</B>", -1);
      }
      else
      if (strsame (Scratch, "SMALLCAPS", -1))
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<TT>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</TT>", -1);
      }
      else
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<I>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</I>", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<EXAMPLE>", -1))
   {
      if (*sdptr == '(')
      {
         /* its got a caption, its a formal example, into a file of its own */
         InsideFormalExample++;

         /*
            This is a reasonably complex tag to implement because it
            generates a separate HTML output file. It makes use of a
            recursive call to function ProcessSdmlLines().
         */

         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Symbol, sizeof(Symbol));
         if (*sdptr == ')') sdptr++;

         GenerateHtmlFileName (&PopUpFile, SectionNumber[0], ++PopUpNumber);
      
         /* create an HTML anchor point in the document for the example file */
         if (!Buffer[0]) strcpy (Buffer, "(no title)");
         sprintf (Scratch, "\n<P>\n<A HREF=\"%s\"%s>[Example: %s]</A>\n<P>\n",
                  PopUpFile.NameOfFile, FrameTargetSelf, Buffer);
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

         /* flush any remaining output into the current HTML file */
         FlushOutputLine (HtmlFilePtr);

         CreateHtmlFile (&PopUpFile, SdmlFilePtr);

         sprintf (OutputLinePtr,
"%s\
<HTML>\n\
<HEAD>\n\
%s\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
%s\
<H1>%s</H1>\n\
\n\
<A NAME=\"%d\">\n\
<H2>%s</H2>\n\
</A>",
         HTML_DOC_TYPE, MetaInformation(SdmlFilePtr),
         UnmarkupTitle(Buffer), HtmlBodyTag, ChapterTitle,
         ++ReferenceNumber, Buffer);
         while (*OutputLinePtr) OutputLinePtr++;

         sprintf (Scratch, "Example: %s", Buffer);
         NoteReference (HtmlFilePtr, Symbol, "", Scratch, 0);

         /* recursive call to process the SDML text into the example file */
         sdptr = ProcessSdmlLines (SdmlFilePtr, &PopUpFile, sdptr);

         strcpy (OutputLinePtr, "</BODY>\n</HTML>\n");
         while (*OutputLinePtr) OutputLinePtr++;

         /* flush output into the current HTML file */
         FlushOutputLine (&PopUpFile);

         CloseHtmlFile (&PopUpFile);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDEXAMPLE>", -1))
   {
      if (InsideFormalExample)
      {
         InsideFormalExample--;
         /*
            This psuedo-tag is detected and actioned in function
            ProcessSdmlLines() to terminate the recursive call made
            to it by <EXAMPLE> processing.
         */
         strcpy (htptr, "<PSEUDO-END>");
         htptr += 12;
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<EXAMPLE_ATTRIBUTES>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<FRONT_MATTER>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDFRONT_MATTER>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<HEAD", 5) ||
       strsame (TagName, "<CHEAD>", -1))
   {
      if (isdigit (TagName[5]))
      {
         HeadDigit = atol (TagName+5);
         if (HeadDigit > MAX_HEAD_TAG) HeadDigit = 0;
      }
      else
         HeadDigit = 0;

      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Symbol, sizeof(Symbol));
         if (*sdptr == ')') sdptr++;
      }

      if (HeadDigit)
      {
         /* numbered heading */
         sptr = NumberString;
         /* increment the current heading level ('HeadDigit' always >= 2) */
         SectionNumber[HeadDigit]++;
         /* output the heading numbering into the heading string */
         for (Count = 0; Count <= HeadDigit; Count++)
         {
            if (Count) *sptr++ = '.';
            sprintf (sptr, "%d", SectionNumber[Count]);
            while (*sptr) sptr++;
         }

         sprintf (Scratch,
"\n\
<A NAME=\"%d\">\n\
<H3><BR><U>%s - %s</U></H3>\n\
</A>",
         ++ReferenceNumber, NumberString, Buffer);
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

         /* create a reference for the numbered heading */
         NoteReference (HtmlFilePtr, Symbol, NumberString, Buffer, 0);

         /* create a table of contents entry for the numbered heading */
         WriteContentsItem (HtmlFilePtr, HeadDigit, Symbol);

         /* reset the remainder of the heading counters to zero */
         for (Count = HeadDigit+1;
              Count <= MAX_HEAD_TAG;
              SectionNumber[Count++] = 0);
      }
      else
      {
         /* non-numbered heading */
         if (strsame (TagName, "<CHEAD>", -1))
         {
            sprintf (Scratch,
"\n\
<A NAME=\"%d\">\n\
<H3><BR><CENTER>%s</CENTER></H3>\n\
</A>",
            ++ReferenceNumber, Buffer);
         }
         else
         {
            sprintf (Scratch,
"\n\
<A NAME=\"%d\">\n\
<H3><BR>%s</H3>\n\
</A>",
            ++ReferenceNumber, Buffer);
         }
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

         /* create a reference for the non-numbered heading */
         NoteReference (HtmlFilePtr, Symbol, "", Buffer, 0);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<INCLUDE>", -1) ||
       strsame (TagName, "<ELEMENT>", -1) ||
       strsame (TagName, "<EXAMPLE_FILE>", -1) ||
       strsame (TagName, "<TABLE_FILE>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;

         memset (&IncludeFile, 0, sizeof(struct SdmlFileData));
         strcpy (IncludeFile.FileName, Buffer);

         if (DoVerbose)
            fprintf (stdout, "[Include %s]\n", IncludeFile.FileName);

         ProcessSdmlFile (&IncludeFile, HtmlFilePtr);

         if (DoVerbose)
            fprintf (stdout, "[Resuming %s]\n", SdmlFilePtr->FileName);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<LE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      if (EndListTagIndex &&
          strsame (EndListTag[EndListTagIndex-1], "</DL>", 5))
        htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<DD>", -1);
      else
      if (ListItemCount[EndListTagIndex]++)
        htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<P><LI>", -1);
      else
        htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<LI>", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<HELLIPSIS>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "...", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<INDEX_FILE>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<KEEP>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<LINE>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
      }
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<BR>", -1);
      if (strsame (Buffer, "SMALLSKIP", -1))
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<BR>", -1);
      if (strsame (Buffer, "BIGSKIP", -1))
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<BR>", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<LITERAL>", -1))
   {
      InsideLiteral++;
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));

         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         ErrorInfo.SourceLineNumber = __LINE__;
         while (*sdptr == '\\')
         {
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Buffer, sizeof(Buffer));
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         }
         if (*sdptr == ')') sdptr++;
         InsideLiteral--;
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<LIST>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
      }
      ErrorInfo.SourceLineNumber = __LINE__;
      if (strsame (Buffer, "NUMBERED", -1))
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<P><OL>\n", -1);
         strcpy (EndListTag[EndListTagIndex++], "</OL>\n");
      }
      else
      if (strsame (Buffer, "SIMPLE", -1))
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<P><DL>\n", -1);
         strcpy (EndListTag[EndListTagIndex++], "</DL>\n");
      }
      else
      if (strsame (Buffer, "ALPHABETIC", -1))
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<P><OL>\n", -1);
         strcpy (EndListTag[EndListTagIndex++], "</OL>\n");
      }
      else
      {
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<P><UL>\n", -1);
         strcpy (EndListTag[EndListTagIndex++], "</UL>\n");
      }
      ListItemCount[EndListTagIndex] = 0;
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDLIST>", -1))
   {
      if (EndListTagIndex)
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             EndListTag[--EndListTagIndex], -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<PAGE>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<NOTE>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
"<BLOCKQUOTE><CENTER><B>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
"</B></CENTER><HR SIZE=1 WIDTH=70% ALIGN=center NOSHADE>\n", -1);
      }
      else
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
"<BLOCKQUOTE><CENTER><B>NOTE</B></CENTER>\
<HR SIZE=1 WIDTH=70% ALIGN=center NOSHADE>\n", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDNOTE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, 
"<HR SIZE=1 WIDTH=70% ALIGN=center NOSHADE></BLOCKQUOTE>\n", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ONLINE_CHUNK>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ONLINE_POPUP>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDONLINE_POPUP>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ONLINE_TITLE>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<OPAREN>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "(", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<P>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\n<P>", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<PREFACE>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, NULL, 0);
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Symbol, sizeof(Symbol));
         if (*sdptr == ')') sdptr++;
      }

      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);

      strcpy (ChapterTitle, "Preface");
      GenerateHtmlFileName (HtmlFilePtr, 0, ++FrontMatterCount);
      CreateHtmlFile (HtmlFilePtr, SdmlFilePtr);

      sprintf (Scratch, "<TITLE>%s</TITLE>\n<H1>%s</H1>\n",
               UnmarkupTitle(ChapterTitle), DocumentTitle);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

      ReferenceNumber++;
      NoteReference (HtmlFilePtr, Symbol, "", ChapterTitle, 0);
      WriteContentsItem (HtmlFilePtr, 0, Symbol);

      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HTML_DOC_TYPE, -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "<HTML>\n<HEAD>\n", -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          MetaInformation(SdmlFilePtr), -1);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</HEAD>\n", -1);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HtmlBodyTag, -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDPREFACE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "</BODY>\n</HTML>\n", -1);
      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<PROFILE>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDPROFILE>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<QUOTE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "&quot;", -1);
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "&quot;", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDQUOTE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "&quot;", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<REFERENCE>", -1))
   {
      Buffer[0] = '\0';
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Symbol, sizeof(Symbol));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
      }
      if (Symbol[0])
         htptr = MakeReference (HtmlFilePtr, &ErrorInfo,
                                htptr, &Capacity, Symbol);
   }
   else 
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<REVISION_INFO>", -1))
   {
      Scratch[0] = Buffer[0] = '\0';
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Scratch, sizeof(Scratch));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Buffer, sizeof(Buffer));
         if (*sdptr == ')') sdptr++;

         if (!Scratch[0]) strcpy (Scratch, "Revision Information");
         ErrorInfo.SourceLineNumber = __LINE__;
/*
   Original code
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\n<H2>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</H2>\n<P>\n", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
*/
/*
   New Mcj code
*/
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\n<TABLE WIDTH=\"80%\">\n<TR><TD>", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<TD WIDTH=\"50%\">", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\n</TABLE>\n", -1);
      }
   }
   else 
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<RULE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "\n<HR SIZE=2 NOSHADE>\n", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<SAMPLE_TEXT>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "\n<BLOCKQUOTE>", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDSAMPLE_TEXT>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</BLOCKQUOTE>\n", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE>", -1))
   {
      TableRowCount = 0;

      if (*sdptr == '(')
      {
         /* its got a caption, its a formal table, into a file of its own */
         InsideFormalTable++;

         /*
            This is a reasonably complex tag to implement because it
            generates a separate HTML output file. It makes use of a
            recursive call to function ProcessSdmlLines().  It also
            flushes any HTML text in the output buffer (hence the global
            variable 'OutputLinePtr') before and after processing the table.
         */

         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Symbol, sizeof(Symbol));
         if (*sdptr == ')') sdptr++;

         GenerateHtmlFileName (&PopUpFile, SectionNumber[0], ++PopUpNumber);
      
         /* create an HTML anchor point in the document for the table file */
         if (!Buffer[0]) strcpy (Buffer, "(no title)");
         sprintf (Scratch, "\n<P>\n<A HREF=\"%s\"%s>[Table: %s]</A>\n<P>\n",
                  PopUpFile.NameOfFile, FrameTargetSelf, Buffer);
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);

         /* flush any remaining output into the current HTML file */
         FlushOutputLine (HtmlFilePtr);

         CreateHtmlFile (&PopUpFile, SdmlFilePtr);

         sprintf (OutputLinePtr,
"%s\
<HTML>\n\
<HEAD>\n\
%s\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
%s\
<H1>%s</H1>\n\
\n\
<CENTER>\n\
<TABLE CELLSPACING=0 CELLPADDING=20 BORDER=0>\n\
<TR><TD>\n\
<TABLE CELLSPACING=0 CELLPADDING=3 BORDER=1>\n\
<TR><TD>\n\
<TABLE CELLSPACING=0 CELLPADDING=3 BORDER=0>\n\
<A NAME=\"%d\">\n\
<CAPTION>Table: %s</CAPTION>\n\
</A>",
         HTML_DOC_TYPE, MetaInformation(SdmlFilePtr),
         UnmarkupTitle(Buffer), HtmlBodyTag, ChapterTitle,
         ++ReferenceNumber, Buffer);
         while (*OutputLinePtr) OutputLinePtr++;

         /* recursive call to process the SDML text into the table file */
         sdptr = ProcessSdmlLines (SdmlFilePtr, &PopUpFile, sdptr);

         strcpy (OutputLinePtr,
"</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
</CENTER>\n\
</BODY>\n</HTML>\n");
         while (*OutputLinePtr) OutputLinePtr++;

         /* flush output into the current HTML file */
         FlushOutputLine (&PopUpFile);

         CloseHtmlFile (&PopUpFile);
      }
      else
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
"\n<CENTER>\n\
<TABLE CELLSPACING=0 CELLPADDING=20 BORDER=0>\n\
<TR><TD>\n\
<TABLE CELLSPACING=0 CELLPADDING=3 BORDER=1>\n\
<TR><TD>\n\
<TABLE CELLSPACING=0 CELLPADDING=3 BORDER=0>", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDTABLE>", -1))
   {
      if (InsideFormalTable)
      {
         InsideFormalTable--;
         /*
            This psuedo-tag is detected and actioned in function
            ProcessSdmlLines() to terminate the recursive call made
            to it by <TABLE> processing.
         */
         strcpy (htptr, "<PSEUDO-END>");
         htptr += 12;
      }
      else
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
"</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
</TD></TR>\n\
</TABLE>\n\
</CENTER>\n", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_HEADS>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<TH", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             TableRowColourPtr, -1);
         if (Buffer[0])
         {
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                " ALIGN=left VALIGN=top><U>", -1);
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                "</U></TH>", -1);
         }
         else
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                " ALIGN=left VALIGN=top>&nbsp;</TH>", -1);
         ErrorInfo.SourceLineNumber = __LINE__;
         while (*sdptr == '\\')
         {
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Buffer, sizeof(Buffer));
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<TH", -1);
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                TableRowColourPtr, -1);
            if (Buffer[0])
            {
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                   " ALIGN=left VALIGN=top><U>", -1);
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                   "</U></TH>", -1);
            }
            else
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                   " ALIGN=left VALIGN=top>&nbsp;</TH>", -1);
         }
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</TR>", -1);
      }
   }
   else 
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_KEY>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDTABLE_KEY>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_KEYREF>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_ATTRIBUTES>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_ROW>", -1))
   {
      TableRowCount++;

      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<TD", -1);
         if (!(TableRowCount & 1))
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                TableRowColourPtr, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                             " ALIGN=left VALIGN=top>", -1);
         if (Buffer[0])
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         else
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "&nbsp;", -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</TD>", -1);
         ErrorInfo.SourceLineNumber = __LINE__;
         while (*sdptr == '\\')
         {
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Buffer, sizeof(Buffer));
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<TD", -1);
            if (!(TableRowCount & 1))
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                   TableRowColourPtr, -1);
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                " ALIGN=left VALIGN=top>", -1);
            if (Buffer[0])
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
            else
               htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "&nbsp;", -1);
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                                "</TD>", -1);
         }
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</TR>", -1);
      }
   }
   else 
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_ROW_BREAK>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_SPACE>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_SETUP>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_UNIT>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDTABLE_UNIT>", -1))
   {
      /* just absorb this parameter-less tag */
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TABLE_UNIT_HEADS>", -1))
   {
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TITLE>", -1))
   {
      /* note: <title>() tags can have up to 3 '\'-separated lines in them */
      if (*sdptr == '(')
      {
         cptr = DocumentTitle;
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         for (sptr = Buffer; *sptr; *cptr++ = *sptr++);
         while (*sdptr == '\\')
         {
            *cptr++ = ' ';
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Buffer, sizeof(Buffer));
            for (sptr = Buffer; *sptr; *cptr++ = *sptr++);
         }
         *cptr = '\0';
         if (*sdptr == ')') sdptr++;
      }
      sprintf (Scratch, "<TITLE>%s</TITLE>\n<H1>%s</H1>\n",
               UnmarkupTitle(DocumentTitle), DocumentTitle);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<TITLE_PAGE>", -1))
   {
      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);

      TitlePageCount = ++FrontMatterCount;
      GenerateHtmlFileName (HtmlFilePtr, 0, TitlePageCount);
      CreateHtmlFile (HtmlFilePtr, SdmlFilePtr);

      /* in this case 'Symbol' will be generated by NoteReference() */
      NoteReference (HtmlFilePtr, Symbol, "", "Title Page", 0);
      WriteContentsItem (HtmlFilePtr, 0, Symbol);

      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HTML_DOC_TYPE, -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "<HTML>\n<HEAD>\n", -1);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          MetaInformation(SdmlFilePtr), -1);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</HEAD>\n", -1);
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, HtmlBodyTag, -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDTITLE_PAGE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "</BODY>\n</HTML>\n", -1);
      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<UNDERLINE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<U>", -1);
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</U>", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDUNDERLINE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</U>", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<VBAR>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "|", -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<X>", -1) ||
       strsame (TagName, "<XS>", -1) ||
       strsame (TagName, "<XSUBENTRY>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, NULL, 0);
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
      }
   }
   else
   /*------------------------------------------------------------------------*/
   /* Begin M.C.Jordan extensions */
   if (strsame (TagName, "<DATE>", -1))
   {
      time_t time_now;		/* Mcj */
      struct tm *p;		/* Mcj */

      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, NULL, 0);
         if (*sdptr == ')') sdptr++;
      }
      ErrorInfo.SourceLineNumber = __LINE__;
      time_now = time(NULL);
      p = localtime (&time_now);
      if (strsame (Buffer, "FULL", -1))
        strftime (Buffer, sizeof(Buffer), "%d-%b-%Y %T", p);
      if (!Buffer[0]) strftime (Buffer, sizeof(Buffer), "%b %d, %Y", p);
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<DOCTYPE>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      if (*sdptr == '(') sdptr = AbsorbTagParameter (SdmlFilePtr, sdptr+1);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<ENDAPPENDIX>", -1))
   {
      ErrorInfo.SourceLineNumber = __LINE__;
      htptr = StringCopy (&ErrorInfo, htptr, &Capacity,
                          "</BODY>\n</HTML>\n", -1);
      EndHtmlFile (SdmlFilePtr, HtmlFilePtr);
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<FOOTREF>", -1))
   {
      if (*sdptr == '(')
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<SUP>", -1);
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                      sdptr+1, Buffer, sizeof(Buffer));
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         while (*sdptr == '\\') {
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Buffer, sizeof(Buffer));
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Buffer, -1);
         }
         if (*sdptr == ')') sdptr++;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "</SUP>", -1);
      }
   }
   else
   /*------------------------------------------------------------------------*/
   if (strsame (TagName, "<DEFINE_SYMBOL>", -1))
   {
      if (*sdptr == '(')
      {
         sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                              sdptr+1, Symbol, sizeof(Symbol));
         while (*sdptr == '\\')
            sdptr = ProcessTagParameter (SdmlFilePtr, HtmlFilePtr, &ErrorInfo,
                                         sdptr+1, Scratch, sizeof(Scratch));
         if (*sdptr == ')') sdptr++;
      }
      NoteReference (HtmlFilePtr, Symbol, "", Scratch, 1);
   }
   else
   /* End M.C.Jordan extensions */
   /*------------------------------------------------------------------------*/
   {
      /*************************/
      /* unrecognised SDML tag */
      /*************************/

      if (Pass == 2)
      {
         UnknownTagCount++;
         if (FlagUnknownTags)
         {
            fprintf (stdout, "%%%s-W-TAG, unknown %s in line %d\n",
                     Utility, TagName, SdmlFilePtr->LineNumber);
         }
      }
      if (!InsideComment++)
      {
         if (IncludeUnknownTags)
         {
            ErrorInfo.SourceLineNumber = __LINE__;
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, "<!-- ", -1);
         }
      }
      if (IncludeUnknownTags)
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = HtmlCopy (&ErrorInfo, htptr, &Capacity, TagName, -1);
      }
      if (*sdptr == '(')
      {
         sdptr = SkipUnknownTag (SdmlFilePtr, &ErrorInfo,
                                 sdptr+1, Scratch, sizeof(Scratch));
         if (IncludeUnknownTags)
         {
            ErrorInfo.SourceLineNumber = __LINE__;
            htptr = StringCopy (&ErrorInfo, htptr, &Capacity, Scratch, -1);
         }
         else
            sdptr++;
      }
      if (InsideComment) InsideComment--;
      if (!InsideComment && IncludeUnknownTags)
      {
         ErrorInfo.SourceLineNumber = __LINE__;
         htptr = StringCopy (&ErrorInfo, htptr, &Capacity, " -->", -1);
      }
   }

   RecursionLevel--;
   return (sdptr);
}

/*****************************************************************************/
/*
*/ 
 
EndHtmlFile
(
struct SdmlFileData *SdmlFilePtr,
struct HtmlFileData *HtmlFilePtr
)
{
   char  *sptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "EndHtmlFile() |%s|\n", HtmlFilePtr->FileName);

   if (HtmlFilePtr->FileName[0])
   {
      FlushOutputLine (HtmlFilePtr);
      if (SectionNumber[0])
      {
         /* there has been a previous chapter being output, finalize */
         sptr = NavigationButtons (true);
         fputs (sptr, HtmlFilePtr->FilePtr);
         fputs ("</BODY>\n</HTML>\n", HtmlFilePtr->FilePtr);
      }
      CloseHtmlFile (HtmlFilePtr);
      HtmlFilePtr->FileName[0] = '\0';
   }
}

/*****************************************************************************/
/*
Raw HTML can be introduced into output text from the SDML source text by using 
the construct "<COMMENT>(HTML=...)".
*/ 

char* RawHtml
(
struct SdmlFileData *SdmlFilePtr,
struct HtmlFileData *HtmlFilePtr,
struct ErrorData *ErrorInfoPtr,
char *sdptr,
char *htptr,
int Capacity
)
{
   int  status,
        ParenthesisCount = 0;
   char  *cptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "RawHtml()\n");

   /* allow for the terminating null */
   if (Capacity) Capacity--;

   for (;;)
   {
      while (!*sdptr)
      {
         *htptr++ = '\n';
         if (!Capacity--) BufferOverflow (ErrorInfoPtr);
         if (ReadSdmlLine (SdmlFilePtr) == NULL)
         {
            *htptr = '\0';
            return (sdptr);
         }
         sdptr = SdmlFilePtr->InLine;
      }

      if (*sdptr == ')')
      {
         if (!ParenthesisCount)
         {
            *htptr = '\0';
            return (sdptr);
         }
         ParenthesisCount--;
         if (Debug) fprintf (stdout, "Par..Count %d\n", ParenthesisCount);
         *htptr++ = *sdptr++;
         if (!Capacity--) BufferOverflow (ErrorInfoPtr);
         continue;
      }

      if (*sdptr == '(')
      {
         ParenthesisCount++;
         if (Debug) fprintf (stdout, "Par..Count %d\n", ParenthesisCount);
         *htptr++ = *sdptr++;
         if (!Capacity--) BufferOverflow (ErrorInfoPtr);
         continue;
      }

      if (*sdptr == '<')
      {
         *htptr++ = *sdptr++;
         if (!Capacity--) BufferOverflow (ErrorInfoPtr);
         if (toupper(*sdptr) == 'A')
         {
            *htptr++ = *sdptr++;
            if (!Capacity--) BufferOverflow (ErrorInfoPtr);
            while (*sdptr && isspace(*sdptr))
            {
               *htptr++ = *sdptr++;
               if (!Capacity--) BufferOverflow (ErrorInfoPtr);
            }
            if (strsame (sdptr, "HREF=\"", 6))
            {
               cptr = FrameTargetTop;
               while (*cptr)
               {
                  *htptr++ = *cptr++;
                  if (!Capacity--) BufferOverflow (ErrorInfoPtr);
               }
            }
         }
         continue;
      }

      *htptr++ = *sdptr++;
      if (!Capacity--) BufferOverflow (ErrorInfoPtr);
   }
}

/*****************************************************************************/
/*
Recursively called function.

Skip all text between the parentheses of a "<tag>(...)" construct.
*/ 

char* AbsorbTagParameter
(
struct SdmlFileData *SdmlFilePtr,
char  *sdptr
)
{
   int  status,
        ParenthesisCount = 0;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "AbsorbTagParameter()\n");

   for (;;)
   {
      if (!*sdptr)
      {
         if (ReadSdmlLine (SdmlFilePtr) == NULL)
            return (sdptr);
         sdptr = SdmlFilePtr->InLine;
      }
      if (*sdptr == ')')
      {
         if (!ParenthesisCount) return (sdptr+1);
         ParenthesisCount--;
         if (Debug) fprintf (stdout, "Par..Count %d\n", ParenthesisCount);
      }
      if (*sdptr == '(')
      {
         ParenthesisCount++;
         if (Debug) fprintf (stdout, "Par..Count %d\n", ParenthesisCount);
      }
      sdptr++;
   }
}

/*****************************************************************************/
/*
Recursively called function.

Get all text between the parentheses of a "<tag>(...)" construct.
*/ 

char* SkipUnknownTag
(
struct SdmlFileData *SdmlFilePtr,
struct ErrorData *ErrorInfoPtr,
char  *sdptr,
char  *htptr,
int Capacity
)
{
   int  status,
        ParenthesisCount = 0;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "SkipUnknownTag()\n");

   for (;;)
   {
      if (!*sdptr)
      {
         if (ReadSdmlLine (SdmlFilePtr) == NULL)
            return (sdptr);
         sdptr = SdmlFilePtr->InLine;
      }
      if (*sdptr == ')')
      {
         if (!ParenthesisCount) return (sdptr+1);
         ParenthesisCount--;
         if (Debug) fprintf (stdout, "Par..Count %d\n", ParenthesisCount);
      }
      if (*sdptr == '(')
      {
         ParenthesisCount++;
         if (Debug) fprintf (stdout, "Par..Count %d\n", ParenthesisCount);
      }
      ErrorInfoPtr->SourceLineNumber = __LINE__;
      htptr = HtmlCopy (ErrorInfoPtr, htptr, &Capacity, sdptr++, 1);
   }
}

/*****************************************************************************/
/*
Put HTML anchors "[contents]", "[next]", "[previous]", etc., at the start or 
end of a chapter.  Writes directly into the 'OutputLine'.
*/ 

char* NavigationButtons (boolean AtEnd)

{
   static char  Scratch [1024];

   char  *sptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "NavigationButtons()\n");

   sptr = Scratch;

   if (AtEnd)
   {
      strcpy (sptr, "\n<P>\n<HR SIZE=2 NOSHADE>\n");
      while (*sptr) sptr++;
   }

   if (SectionNumber[0] < TotalChapters)
   {
      sprintf (sptr, "[<A HREF=\"%s_%02.02d%02.02d.html\"%s>next</A>]",
               HtmlName, SectionNumber[0]+1, 0, FrameTargetSelf);     
      while (*sptr) sptr++;
   }
   else
   {
      strcpy (sptr, "[next]");
      sptr += 6;
   }

   if (SectionNumber[0] > 1)
   {
      sprintf (sptr, " [<A HREF=\"%s_%02.02d%02.02d.html\"%s>previous</A>]",
               HtmlName, SectionNumber[0]-1, 0, FrameTargetSelf);
      while (*sptr) sptr++;
   }
   else
   {
      strcpy (sptr, " [previous]");
      sptr += 11;
   }

   sprintf (sptr, " [<A HREF=\"%s_%02.02d%02.02d.html\"%s>contents</A>]",
            HtmlName, 0, TableOfContentsCount, FrameTargetTop);
   while (*sptr) sptr++;

   if (DoFramed)
   {
      sprintf (sptr, " [<A HREF=\"%s_%02.02d%02.02d.html\"%s>full-page</A>]",
               HtmlName, SectionNumber[0], 0, FrameTargetTop);
      while (*sptr) sptr++;
   }

   if (AtEnd)
   {
      *sptr++ = '\n';
      *sptr = '\0';
   }
   else
   {
      strcpy (sptr, "\n<HR SIZE=2 NOSHADE>\n");
      while (*sptr) sptr++;
   }

   return (Scratch);
}

/*****************************************************************************/
/*
Create an entry in the linked-list of cross-reference data.

'Symbol' (usually from SDML tag parameter information but will create a unique 
symbol if required) is used as an index into the list (i.e. can be searched 
on). 

'Number' is the chapter/heading number (e.g. "1.2.3 - Section Numbering").  If 
supplied the above style of reference text heading is generated in the anchor, 
if not then the anchor would be unnumbered (e.g. "Section Numbering").

'Title' is the text appearing in the HTML document anchor (e.g. "Section 
Numbering").
*/ 

void NoteReference
(
struct HtmlFileData *HtmlFilePtr,
char *Symbol,
char *NumberString,
char *Title,
char Type
)
{
   struct ReferenceData  *rptr;
   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "NoteReference() |%s|%s|\n", Symbol, Title);

   if ((rptr = malloc (sizeof (struct ReferenceData))) == NULL)
   {
      status = vaxc$errno;
      fprintf (stdout, "%%%s-E-MALLOC, allocating reference memory\n-%s\n",
               Utility, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }
   if (RefPtr == NULL)
      RefPtr = LastPtr = rptr;
   else
   {
      LastPtr->NextPtr = rptr;
      LastPtr = rptr;
   }

   rptr->ReferenceNumber = ReferenceNumber;
   /* ensure the title does not overwrite the size of the buffer */
   if (Symbol[0])
      Symbol[SYMBOL_SIZE-1] = '\0';
   else
      sprintf (Symbol, "symbol_%d", ReferenceNumber);
   strcpy (rptr->Symbol, Symbol);
   /* ensure the number does not overwrite the size of the buffer */
   strncpy (rptr->NumberString, NumberString, sizeof(rptr->NumberString)-1);
   rptr->NumberString[sizeof(rptr->NumberString)-1] = '\0';
   /* ensure the title does not overwrite the size of the buffer */
   strncpy (rptr->Title, Title, sizeof(rptr->Title)-1);
   rptr->Title[sizeof(rptr->Title)-1] = '\0';
   strcpy (rptr->NameOfFile, HtmlFilePtr->NameOfFile);
   rptr->Type = Type;
   rptr->NextPtr = NULL;
}

/*****************************************************************************/
/*
Look for an entry ('Symbol') in the linked-list of cross-reference data.  If 
found return a pointer to it, if not return NULL.
*/ 

struct ReferenceData*  FindReference  (char *Symbol)

{
   struct ReferenceData  *rptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "FindReference() |%s|\n", Symbol);

   for (rptr = RefPtr; rptr != NULL; rptr = rptr->NextPtr)
      if (toupper(Symbol[0]) == toupper(rptr->Symbol[0]))
         if (strsame (Symbol, rptr->Symbol, -1))
            break;
   return (rptr);
}

/*****************************************************************************/
/*
Look for all references beginning with this chapter number.  Create a list of
them on the page.
*/ 

char* ChapterReferences
(
struct ErrorData *ErrorInfoPtr,
char *htptr,
int *CapacityPtr,
char* ChapterNumberString
)
{
   int  Capacity,
        Count = 0,
        PeriodCount;
   char  *cptr, *sptr;
   char  Scratch [512];
   struct ReferenceData  *rptr;

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "ChapterReferences() |%s|n", ChapterNumberString);

   Capacity = *CapacityPtr;
   for (rptr = RefPtr; rptr != NULL; rptr = rptr->NextPtr)
   {
      /* only interested in the major, numbered headings */
      if (!rptr->NumberString[0]) continue;
      cptr = ChapterNumberString; 
      sptr = rptr->NumberString; 
      while (*cptr && *sptr && *cptr != '.' && *sptr != '.' && *cptr == *sptr)
      {
         cptr++;
         sptr++;
      }
      if (*cptr || *sptr != '.') continue;

      PeriodCount = 0;
      for (sptr = rptr->NumberString; *sptr; sptr++)
         if (*sptr == '.') PeriodCount++;
      sptr = Scratch;
      if (Count++)
      {
         strcpy (sptr, "<BR>");
         sptr += 4;
      }
      else
      {
         strcpy (sptr, "<FONT SIZE=-1>\n<BLOCKQUOTE>\n<NOBR>\n");
         sptr += 35;
      }
      while (PeriodCount-- > 1)
      {
         strcpy (sptr, "&nbsp;&nbsp;&nbsp;&nbsp;");
         sptr += 24;
      }
      sprintf (sptr, "%s - <A HREF=\"%s#%d\"%s>%s</A>\n",
               rptr->NumberString, rptr->NameOfFile,
               rptr->ReferenceNumber, FrameTargetSelf, rptr->Title);
      cptr = Scratch;
      while (*cptr)
      {
         if (!Capacity--) BufferOverflow (ErrorInfoPtr);
         *htptr++ = *cptr++;
      }
   }
   if (Count)
   {
      cptr = "</NOBR>\n</BLOCKQUOTE>\n</FONT>\n";
      while (*cptr)
      {
         if (!Capacity--) BufferOverflow (ErrorInfoPtr);
         *htptr++ = *cptr++;
      }
   }
   *htptr = '\0';
   *CapacityPtr = Capacity;
   return (htptr);
}

/*****************************************************************************/
/*
Create an anchor in the HTML text.  Locate 'Symbol' in the linked-list of 
reference data and use the file name, reference number and title for the 
reference.
*/ 

char* MakeReference
(
struct HtmlFileData *HtmlFilePtr,
struct ErrorData *ErrorInfoPtr,
char *htptr,
int *CapacityPtr,
char *Symbol
)
{
   char  Scratch [BUFFER_SIZE];
   struct ReferenceData  *rptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "MakeReference() |%s|\n", Symbol);

   if ((rptr = FindReference (Symbol)) == NULL) 
      sprintf (Scratch, "#BADREF(%s)", Symbol);
   else
   if (rptr->Type == 0)
   {
      if (rptr->NumberString[0])
         sprintf (Scratch, "<A HREF=\"%s#%d\"%s>%s - %s</A>",
                  rptr->NameOfFile, rptr->ReferenceNumber, FrameTargetSelf,
                  rptr->NumberString, rptr->Title);
      else
         sprintf (Scratch, "<A HREF=\"%s#%d\"%s>%s</A>",
                  rptr->NameOfFile, rptr->ReferenceNumber, FrameTargetSelf,
                  rptr->Title);
   }
   else
   if (rptr->Type == 1)
      sprintf (Scratch, "%s", rptr->Title);

   ErrorInfoPtr->SourceLineNumber = __LINE__;
   htptr = StringCopy (ErrorInfoPtr, htptr, CapacityPtr, Scratch, -1);

   return (htptr);
}

/*****************************************************************************/
/*
Begin the "table of contents" HTML file.
*/ 

int BeginContents (struct SdmlFileData *SdmlFilePtr)

{
   int  status,
        retval;

   /*********/
   /* begin */
   /*********/

   strcpy (ChapterTitle, "Table of Contents");
   OpenContentsFile (SdmlFilePtr);

   if (!DoGenerateHtml || Pass == 1) return (SS$_NORMAL);

   if (DoFramed)
   {
      retval = fprintf (ContentsFilePtr,
"%s\
<HTML>\n\
<HEAD>\n\
%s\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
%s\
<H2>%s</H2>\n\
<FONT SIZE=-1>\n\
<NOBR>\n",
      HTML_DOC_TYPE, MetaInformation(SdmlFilePtr),
      UnmarkupTitle(ChapterTitle), HtmlBodyTag, ChapterTitle);
   }
   else
   {
      retval = fprintf (ContentsFilePtr,
"%s\
<HTML>\n\
<HEAD>\n\
%s\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
%s\
<H1>%s</H1>\n\
<H2>%s</H2>\n\
<NOBR>\n",
      HTML_DOC_TYPE, MetaInformation(SdmlFilePtr),
      UnmarkupTitle(ChapterTitle), HtmlBodyTag, DocumentTitle, ChapterTitle);
   }

   if (retval < 0)
      return (vaxc$errno);
   else
      return (SS$_NORMAL);
}

/*****************************************************************************/
/*
Add an item to the "table of contents" HTML file.  The 'Level' parameter 
indicates whether this is a level 1, level 2, etc., heading.  The item becomes 
an anchor pointing to a chapter HTML file, or into a heading within a chapter 
section.  All "<Hn>" HTML tags increase this count by one.
*/ 

WriteContentsItem
(
struct HtmlFileData *HtmlFilePtr,
int Level,
char *Symbol
)
{
   static int  ItemCount = 0;

   int  Count;
   char  *sptr;
   char  Fragment [32],
         String [BUFFER_SIZE];
   struct ReferenceData  *rptr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "WriteContentsItem() |%d|%s|\n", Level, Symbol);

   if (Pass == 1) return;

   Level++;
   sptr = String;

   if (ItemCount++) fputs ("<BR>", ContentsFilePtr);

   if ((rptr = FindReference (Symbol)) == NULL)
      fputs ("ERROR: REFERENCE NOT FOUND", ContentsFilePtr);
   else
   {
      if (Level > 1)
      {
         for (sptr = rptr->NumberString; *sptr; sptr++)
            if (*sptr == '.')
               fputs ("&nbsp;&nbsp;&nbsp;&nbsp;", ContentsFilePtr);
         for (sptr = rptr->NumberString; isdigit(*sptr); sptr++)
             fputs ("&nbsp;&nbsp;", ContentsFilePtr);
      }

      if (Level > 1)
         sprintf (Fragment, "#%d", rptr->ReferenceNumber);
      else
         Fragment[0] = '\0';

      if (rptr->NumberString[0])
         fprintf (ContentsFilePtr, "%s - <A HREF=\"%s%s\"%s>%s</A>\n",
                  rptr->NumberString, HtmlFilePtr->NameOfFile, Fragment,
                  FrameTargetPage, rptr->Title);
      else
         fprintf (ContentsFilePtr, "<A HREF=\"%s%s\"%s>%s</A>\n",
                  HtmlFilePtr->NameOfFile, Fragment,
                  FrameTargetPage, rptr->Title);
   }
}

/*****************************************************************************/
/*
End the "table of contents" HTML file.
*/ 

EndContents ()

{
   int  Count;
   char  *sptr;

   /*********/
   /* begin */
   /*********/

   fputs ("</NOBR>\n", ContentsFilePtr);

   if (DoFramed) fputs ("</FONT>\n", ContentsFilePtr);

   fprintf (ContentsFilePtr,
"<P>\n\
<FONT SIZE=-1>This HTML document was produced from SDML (DEC DOCUMENT source) \
by the %s utility (%s)</FONT>\n\
</BODY>\n\
</HTML>\n",
   Utility, SoftwareID);

   fclose (ContentsFilePtr);
}

/*****************************************************************************/
/*
Open the "table of contents" HTML file.
*/ 

int OpenContentsFile (struct SdmlFileData *SdmlFilePtr)

{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "OpenContentsFile()");

   status = SS$_NORMAL;

   if (!DoGenerateHtml || Pass == 1) return (status);

   if (DoFramed)
      TableOfContentsCount = ++FrontMatterCount;
   else
      TableOfContentsCount = FrontMatterCount;

   sprintf (ContentsFileName, "%s%s_00%02.02d.html",
            HtmlDirectoryPtr, HtmlName, TableOfContentsCount);

   if (DoVerbose) fprintf (stdout, "[Created %s]\n", ContentsFileName);

   ContentsFilePtr = fopen (ContentsFileName, "w");
   if (ContentsFilePtr == NULL)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-TOCFILE, opening %s\n-%s\n",
               Utility, SdmlFilePtr->FileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   return (status);
}

/*****************************************************************************/
/*
*/ 

int WriteFrameFile (struct SdmlFileData *SdmlFilePtr)

{
   int  status,
        retval;
   char  FrameFileName [ODS_MAX_FILE_NAME_LENGTH];
   FILE  *FrameFilePtr;


   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "WriteFrameFile()");

   status = SS$_NORMAL;

   if (!DoGenerateHtml || Pass == 1) return (status);

   sprintf (FrameFileName, "%s%s_0000.html", HtmlDirectoryPtr, HtmlName);

   if (DoVerbose) fprintf (stdout, "[Created %s]\n", FrameFileName);

   FrameFilePtr = fopen (FrameFileName, "w");
   if (FrameFilePtr == NULL)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-FRAMEDFILE, creating %s\n-%s\n",
               Utility, SdmlFilePtr->FileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   retval = fprintf (FrameFilePtr,
"<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 3.2//EN\">\n\
<HTML>\n\
<HEAD>\n\
%s\
<TITLE>%s</TITLE>\n\
</HEAD>\n\
<FRAMESET BORDER=5 ROWS=\"100%%\" COLS=\"35%%,65%%\">\n\
   <FRAME RESIZE NAME=\"frame_toc\" SRC=\"%s_00%02.02d.html\">\n\
   <FRAME RESIZE NAME=\"frame_page\" SRC=\"%s_00%02.02d.html\">\n\
</FRAMESET>\n\
</HTML>\n",
      MetaInformation(SdmlFilePtr),
      UnmarkupTitle(DocumentTitle),
      HtmlName, TableOfContentsCount,
      HtmlName, TitlePageCount);

   if (retval < 0)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fprintf() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-FRAMEDFILE, writing %s\n-%s\n",
               Utility, SdmlFilePtr->FileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   fclose (FrameFilePtr);

   return (status);
}

/*****************************************************************************/
/*
*/ 

int WriteIndexPage (struct SdmlFileData *SdmlFilePtr)

{
   int  status,
        retvale;
   char  Buffer [2048],
         IndexFileName [ODS_MAX_FILE_NAME_LENGTH],
         ZeroInLine [ODS_MAX_FILE_NAME_LENGTH],
         ZeroFileName [ODS_MAX_FILE_NAME_LENGTH];
   FILE  *IndexFilePtr,
         *ZeroFilePtr;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "WriteIndexPage()");

   status = SS$_NORMAL;

   if (!DoGenerateHtml || Pass == 1) return (status);

   sprintf (ZeroFileName, "%s%s_0000.html", HtmlDirectoryPtr, HtmlName);

   sprintf (IndexFileName, "%s%s", HtmlDirectoryPtr, IndexPageName);

   IndexFilePtr = fopen (IndexFileName, "w");
   if (IndexFilePtr == NULL)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-FRAMEDFILE, creating %s\n-%s\n",
               Utility, IndexFileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   if (DoVerbose) fprintf (stdout, "[Created %s]\n", IndexFileName);

   ZeroFilePtr = fopen (ZeroFileName, "r");
   if (ZeroFilePtr == NULL)
   {
      status = vaxc$errno;
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-FRAMEDFILE, creating %s\n-%s\n",
               Utility, ZeroFileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   while (fgets (Buffer, sizeof(Buffer), ZeroFilePtr) != NULL)
   {
      if (fputs (Buffer, IndexFilePtr) == EOF)
      {
         status = vaxc$errno;
         if (Debug) fprintf (stdout, "fputs() %%X%08.08X\n", status);
         fprintf (stdout, "%%%s-E-INDEXPAGE, writing %s\n-%s\n",
                  Utility, ZeroFileName, SysGetMsg(status)+1);
         exit (status | STS$M_INHIB_MSG);
      }
   }

   fclose (ZeroFilePtr);
   fclose (IndexFilePtr);

   return (status);
}

/*****************************************************************************/
/*
The file name comprises the RMS file 'name' component plus a two digit section
number (the 'chapter' or "<H1>" number),  plus a two digit subsection number.
*/ 

GenerateHtmlFileName
(
struct HtmlFileData *HtmlFilePtr,
int SectionCount,
int SubSectionCount
)
{
   char  *cptr;

   sprintf (HtmlFilePtr->NameOfFile, "%s_%02.02d%02.02d.html",
            HtmlName, SectionCount, SubSectionCount);
   sprintf (HtmlFilePtr->FileName, "%s%s",
            HtmlDirectoryPtr, HtmlFilePtr->NameOfFile);
   for (cptr = HtmlFilePtr->NameOfFile; *cptr; *cptr++) *cptr = tolower(*cptr);
   if (Debug)
      fprintf (stdout, "GenerateHtmlFileName |%s|%s|\n",
               HtmlFilePtr->NameOfFile, HtmlFilePtr->FileName);
}

/*****************************************************************************/
/*
Meta tags providing utility name, source files and date.
*/

char *MetaInformation (struct SdmlFileData *SdmlFilePtr)

{
   static $DESCRIPTOR (MetaFaoDsc,
"<META NAME=\"generator\" CONTENT=\"!AZ\">\n\
<META NAME=\"source\" CONTENT=\"!AZ\">\n\
<META NAME=\"date\" CONTENT=\"!AZ\">\n\
<!!--\n\
!AZ\n\
-->\n");

   static char  String [BUFFER_SIZE];

   int  status;
   short  Length;
   unsigned long  UnixTime;
   char  *cptr;
   char  UnixDateTime [32];
   struct tm  *UnixTmPtr;
   $DESCRIPTOR (StringDsc, String);

   /*********/
   /* begin */
   /*********/

   if (Debug)
      fprintf (stdout, "MetaInformation() |%s|\n",
               SdmlFilePtr->ExpFileName);

   time (&UnixTime);
   UnixTmPtr = localtime (&UnixTime);
   if (!strftime (UnixDateTime, sizeof(UnixDateTime),
                  "%a, %d %b %Y %T", UnixTmPtr))
      strcpy (UnixDateTime, "[error]");
   if (Debug) fprintf (stdout, "UnixDateTime |%s|\n", UnixDateTime);

   if (VMSnok (status =
       sys$fao (&MetaFaoDsc, &Length, &StringDsc,
                SoftwareID,
                SdmlFilePtr->ExpFileName,
                UnixDateTime,
                CopyrightInfo)))
   {
      fprintf (stdout, "%%%s-E-METAINFO, generating META information\n-%s\n",
               Utility, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   String[Length] = '\0';

   return (String);
}

/*****************************************************************************/
/*
Open an HTML output file.
*/ 

int CreateHtmlFile
(
struct HtmlFileData *HtmlFilePtr,
struct SdmlFileData *SdmlFilePtr
)
{
   int  status;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "CreateHtmlFile()\n");

   status = SS$_NORMAL;

   if (!DoGenerateHtml || Pass == 1) return (status);

   HtmlFilePtr->FilePtr = fopen (HtmlFilePtr->FileName, "w");
   if (HtmlFilePtr->FilePtr == NULL)
   {
      if (Debug) fprintf (stdout, "fopen() %%X%08.08X\n", status);
      fprintf (stdout, "%%%s-E-HTMLFILE, creating %s\n-%s\n",
               Utility, HtmlFilePtr->FileName, SysGetMsg(status)+1);
      exit (status | STS$M_INHIB_MSG);
   }

   if (DoVerbose) fprintf (stdout, "[Created %s]\n", HtmlFilePtr->FileName);
   return (status);
}

/*****************************************************************************/
/*
*/ 
 
CloseHtmlFile (struct HtmlFileData *HtmlFilePtr)

{
   if (Debug) fprintf (stdout, "CloseHtmlFile()\n");

   if (HtmlFilePtr->FilePtr == NULL) return;

   FlushOutputLine (HtmlFilePtr);
   fclose (HtmlFilePtr->FilePtr);
   HtmlFilePtr->FilePtr = NULL;
   HtmlFilePtr->FileName[0] = '\0';
}

/*****************************************************************************/
/*
*/
 
FlushOutputLine (struct HtmlFileData *HtmlFilePtr)

{
   if (Debug) fprintf (stdout, "FlushOutputLine()\n");

   if (OutputLine[0])
   {
      fputs (OutputLine, HtmlFilePtr->FilePtr);
      fputc ('\n', HtmlFilePtr->FilePtr);
   }
   *(OutputLinePtr = OutputLine) = '\0';
}

/*****************************************************************************/
/*
Read a record (line) from the source SDML file.  Returns pointer to buffer if 
not end-of-file, NULL if end-of-file (a'la fgets()).
*/ 

char* ReadSdmlLine (struct SdmlFileData *SdmlFilePtr)

{
   int  status;
   char  *cptr;

   SdmlFilePtr->InLine[0] = '\0';
   if (fgets (SdmlFilePtr->InLine,
              sizeof(SdmlFilePtr->InLine),
              SdmlFilePtr->FilePtr) == NULL) 
      return (NULL);

   SdmlFilePtr->LineNumber++;
   for (cptr = SdmlFilePtr->InLine; *cptr && *cptr != '\n'; cptr++);
   *cptr = '\0';
   if (Debug) fprintf (stdout, "ReadSdmlLine()\n|%s|\n", SdmlFilePtr->InLine);

   return (SdmlFilePtr->InLine);
}

/*****************************************************************************/
/*
Uses static storage ... careful!  Assume all document '<' and '>' are entity-
escaped and any here only represent markup!
*/ 

char* UnmarkupTitle (char *String)

{
   static char  OutString [256];

   char  *cptr, *sptr;

   sptr = OutString;
   cptr = String;
   while (*cptr)
   {
      if (*cptr == '<')
      {
         while (*cptr && *cptr != '>') cptr++;
         if (*cptr) cptr++;
      }
      else
         *sptr++ = *cptr++;
   }
   *sptr = '\0';
   return (OutString);
}

/*****************************************************************************/
/*
Copy one string to another, with capacity checking.  If parameter 'ccnt' is 
less than 0 then all characters are copied, if 1 then one character copied, 
etc.
*/ 

char* StringCopy
( 
struct ErrorData *ErrorInfoPtr,
char *sptr,
int *CapacityPtr,
char *tptr,
int ccnt
)
{
    int  Capacity;

    Capacity = *CapacityPtr;

    while (ccnt-- && *tptr)
    {
       if (isprint(*tptr) || *tptr == '\n')
       {
          *sptr++ = *tptr++;
          if (!Capacity--) BufferOverflow (ErrorInfoPtr);
       }
       else
          tptr++;
    }
    *sptr = '\0';
    *CapacityPtr = Capacity;

    return (sptr);
}

/*****************************************************************************/
/*
Copy text from one string to another, with capacity checking, converting 
characters forbidden to appear as plain-text in HTML.  For example the '<', 
'&', etc.  Convert these to the corresponding HTML character entities.  If 
parameter 'ccnt' is less than 0 then all characters are copied, if 1 then one 
character copied, etc.
*/ 

char* HtmlCopy
( 
struct ErrorData *ErrorInfoPtr,
char *sptr,
int *CapacityPtr,
char *tptr,
int ccnt
)
{
    int  Capacity;

    Capacity = *CapacityPtr;

    while (ccnt-- && *tptr)
    {
       switch (*tptr)
       {
          case '<' :
             if ((Capacity -= 4) < 0) BufferOverflow (ErrorInfoPtr);
             strcpy (sptr, "&lt;");
             sptr += 4;
             tptr++;
             break;
          case '>' :
             if ((Capacity -= 4) < 0) BufferOverflow (ErrorInfoPtr);
             strcpy (sptr, "&gt;");
             sptr += 4;
             tptr++;
             break;
          case '&' :
             if ((Capacity -= 5) < 0) BufferOverflow (ErrorInfoPtr);
             strcpy (sptr, "&amp;");
             sptr += 5;
             tptr++;
             break;
          default :
             if (isprint(*tptr) || *tptr == '\t' || *tptr == '\n')
             {
                *sptr++ = *tptr++;
                if (!Capacity--) BufferOverflow (ErrorInfoPtr);
             }
             else
                tptr++;
       }
    }
    *sptr = '\0';
    *CapacityPtr = Capacity;
    return (sptr);
}

/*****************************************************************************/
/*
Report buffer overflow error.
*/
 
BufferOverflow (struct ErrorData *ErrorInfoPtr) 

{
   if (ErrorInfoPtr == NULL)
      fprintf (stdout, "%%%s-E-BUFOVF, buffer overflow\n", Utility);
   else
   {
      if (IncludeSourceLineNumber)
      {
         fprintf (stdout,
         "%%%s-E-BUFOVF, buffer overflow (%s line %d)\n",
         Utility, SoftwareID, ErrorInfoPtr->SourceLineNumber);
      }
      else
         fprintf (stdout, "%%%s-E-BUFOVF, buffer overflow\n", Utility);

      fprintf (stdout,
      "-%s-I-TAG, \"%s\"\n-%s-I-LOCN, line %d of %s\n",
      Utility, ErrorInfoPtr->TagNamePtr,
      Utility, ErrorInfoPtr->TagLineNumber, ErrorInfoPtr->SdmlFileNamePtr);
   }
   exit (STS$K_ERROR | STS$M_INHIB_MSG);
}

/****************************************************************************/
/*
Return an integer reflecting the major and minor version of VMS (e.g. 60, 61,
62, 70, 71, 72, etc.)
*/ 

#ifdef ODS_EXTENDED

int GetVmsVersion ()

{
   static char  SyiVersion [16];

   static struct {
      short int  buf_len;
      short int  item;
      unsigned char   *buf_addr;
      unsigned short  *ret_len;
   }
   SyiItems [] =
   {
      { 8, SYI$_VERSION, &SyiVersion, 0 },
      { 0,0,0,0 }
   };

   int  status,
        version;

   /*********/
   /* begin */
   /*********/

   if (Debug) fprintf (stdout, "GetVmsVersion()\n");

   if (VMSnok (status = sys$getsyiw (0, 0, 0, &SyiItems, 0, 0, 0)))
      exit (status);
   SyiVersion[8] = '\0';
   version = ((SyiVersion[1]-48) * 10) + (SyiVersion[3]-48);
   if (Debug) fprintf (stdout, "|%s| %d\n", SyiVersion, version);
   return (version);
}

#endif /* ODS_EXTENDED */

/*****************************************************************************/
/*
*/
 
char* SysGetMsg (int StatusValue)
 
{
   static char  Message [256];
   short int  Length;
   $DESCRIPTOR (MessageDsc, Message);
 
   sys$getmsg (StatusValue, &Length, &MessageDsc, 0, 0);
   Message[Length] = '\0';
   if (Debug) fprintf (stdout, "SysGetMsg() |%s|\n", Message);
   return (Message);
}
 
/****************************************************************************/
/*
Does a case-insensitive, character-by-character string compare and returns 
true if two strings are the same, or false if not.  If a maximum number of 
characters are specified only those will be compared, if the entire strings 
should be compared then specify the number of characters as 0.
*/ 
 
boolean strsame
(
char *sptr1,
char *sptr2,
int  count
)
{
   while (*sptr1 && *sptr2)
   {
      if (toupper (*sptr1++) != toupper (*sptr2++)) return (false);
      if (count)
         if (!--count) return (true);
   }
   if (*sptr1 || *sptr2)
      return (false);
   else
      return (true);
}
 
/****************************************************************************/
