/* Copyright 1997 Acorn Computers Ltd
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */
/***************************************************/
/* File   : Trace.c                                */
/*                                                 */
/* Purpose: Trace build only debugging functions   */
/*          - not the sort of thing that hacks     */
/*          up some text display or whatever; that */
/*          is the domain of TML. These functions  */
/*          are for principally file-based output  */
/*          of things like token list dumps.       */
/*                                                 */
/* Author : A.D.Hodgkinson                         */
/*                                                 */
/* History: 02-Jul-97: Created.                    */
/***************************************************/

#ifdef TRACE

  #include <stdlib.h>
  #include <stdio.h>
  #include <string.h>

  #include "swis.h"

  #include "HTMLLib.h" /* HTML library API, Which will include html2_ext.h, tags.h and struct.h */

  #include "wimp.h"
  #include "wimplib.h"
  #include "event.h"

  #include "svcprint.h"
  #include "Global.h"
  #include "MiscDefs.h"
  #include "Utils.h"

  #include "Trace.h"

  /* Local definitions */

  #define Trace_DumpTokens_Filename_L "<Browse$Dir>.Tokens-L"
  #define Trace_DumpTokens_Filename_S "<Browse$Dir>.Tokens-S"

  #define TrOut(f, d, p) {                                                       \
                           int temp_debug;                                       \
                                                                                 \
                           for (temp_debug = 0; temp_debug < (d); temp_debug ++) \
                           {                                                     \
                             if (fprintf((f), "\t") < 0) return 1;               \
                           }                                                     \
                                                                                 \
                           if (fprintf p < 0) return 1;                          \
                         }                                                       \

  /* Static function prototypes */

  static int          trace_dump_tokens_by_line_r   (browser_data * b, reformat_cell * cell, FILE * file, int depth);
  static int          trace_dump_tokens_by_stream_r (browser_data * b, HStream * streambase, FILE * file, int depth);
  static int          trace_dump_hstream            (FILE * file, HStream * token, int depth);
  static int          trace_dump_tstream            (FILE * file, table_stream * table, int depth);
  static int          trace_dump_row                (FILE * file, table_row * row, int depth);
  static int          trace_dump_head               (FILE * file, table_headdata * head, int depth);
  static const char * trace_tag_name                (tag_no tagno);
  static char       * trace_add_description         (char * list, const char * description);
  static char       * trace_style_bits              (HStream * t);

  /*************************************************/
  /* trace_dump_tokens_by_line()                   */
  /*                                               */
  /* Outputs a diagnostic description of the       */
  /* token list of a browser. The browser is       */
  /* obtained by getting the client handle of the  */
  /* ancestor of whatever object raised the        */
  /* ETraceTokenDumpByLine event (see TBEvents.h). */
  /*                                               */
  /* This function follows line arrays to get the  */
  /* tokens (so not every token output by the      */
  /* library will be included, but those that the  */
  /* browser is actually using will be).           */
  /*                                               */
  /* Parameters are as standard for a Toolbox      */
  /* event handler.                                */
  /*************************************************/

  int trace_dump_tokens_by_line(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
  {
    FILE            * tfile;
    browser_data    * b;
    _kernel_oserror * e;

    /* Try to open out the file */

    tfile = fopen(Trace_DumpTokens_Filename_L, "wb");

    if (!tfile)
    {
      erb.errnum = Utils_Error_Custom_Message;
      StrNCpy0(erb.errmess,
               "trace_dump_tokens: Error trying to open output file");

      show_error_ret(&erb);

      return 0;
    }

    /* Try to get the browser_data structure */

    e = toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b);
    if (e)
    {
      show_error_ret(e);
      return 0;
    }

    if (!is_known_browser(b))
    {
      erb.errnum = Utils_Error_Custom_Message;
      sprintf(erb.errmess,
              "trace_dump_tokens: Can't determine what token list to use ('%08x' is not a known browser)",
              (int) b);

      show_error_ret(&erb);
      return 0;
    }

    /* OK, can proceed */

    if (fprintf(tfile, "Token dump list for browser %08x\n\n\n\n", (int) b) < 0) goto tdtbl_fo_err;

    if (trace_dump_tokens_by_line_r(b, b->cell, tfile, 0)) goto tdtbl_fo_err;

    /* Finished */

    fclose(tfile);

    _swix(OS_CLI,
          _IN(0),

          "SetType "
          Trace_DumpTokens_Filename_L
          " fff");

    return 1;

    /* Now some special case code */

tdtbl_fo_err: /* If there's an error whilst writing to the file */

    fclose(tfile);

    erb.errnum = Utils_Error_Custom_Message;
    StrNCpy0(erb.errmess,
             "trace_dump_tokens: Error whilst writing token dump file");

    show_error_ret(e);

    return 0;
  }

  /*************************************************/
  /* trace_dump_tokens_by_line_r()                 */
  /*                                               */
  /* Recursive back-end to                         */
  /* trace_dump_tokens_by_line.                    */
  /*                                               */
  /* Parameters: Pointer to a browser_data struct  */
  /*             relevant to the overall stream;   */
  /*                                               */
  /*             Pointer to a reformat_cell struct */
  /*             holding the lines to examine;     */
  /*                                               */
  /*             Pointer to a FILE structure for   */
  /*             outputting the information;       */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    1 if there was an error, else 0.  */
  /*************************************************/

  static int trace_dump_tokens_by_line_r(browser_data * b, reformat_cell * cell, FILE * file, int depth)
  {
    int               line, chunk, chunkmax;
    unsigned int      valid;
    HStream         * token = NULL;
    HStream         * last  = NULL;
    _kernel_oserror * e;

    TrOut(file, depth, (file, "Browser %08x, cell %08x:\n\n\n\n", (int) b, (int) cell));

    /* If no lines, is there apparently line or chunk data allocated? */

    if (!cell->nlines)
    {
      TrOut(file, depth, (file, "This cell has no lines.\n\n"));

      if (cell->ldata || cell->cdata)
      {
        TrOut(file, depth, (file,
                    "The cell appears corrupt; nlines = 0, but ldata = %08x and cdata = %08x.\n\n",
                    (int) cell->ldata,
                    (int) cell->cdata));
      }

      return 0;
    }

    /* If there is line data, but nlines is non-zero, the cell is broken */

    if (!cell->ldata)
    {
      TrOut(file, depth, (file,
                  "This cell appears corrupted - nlines = %d, but ldata = NULL.\n\n",
                  cell->nlines));

      return 0;
    }

    /* If we get here, there are lines and line data. If there's no chunk data, */
    /* the cell isn't necessarily broken but we should flag a warning - various */
    /* areas of the browser won't be able to handle this.                       */

    if (!cell->cdata)
    {
      TrOut(file, depth, (file,
                  "Warning: nlines = %d, ldata = %08x, but cdata = NULL.\n\n",
                  cell->nlines,
                  (int) cell->ldata));
    }

    for (line = 0; line < cell->nlines; line ++)
    {
      chunk    = cell->ldata[line].chunks;
      chunkmax = cell->ldata[line].n + chunk;

      for (/* 'chunk' is already initialised */ ; chunk < chunkmax; chunk ++)
      {
        token = cell->cdata[chunk].t;

        if (token != last)
        {
          last = token;

          /* Output info on the token as an HStream */

          TrOut(file, depth, (file,
                      "Line %d chunk %d, new token %08x:\n\n",
                      line,
                      chunk,
                      (int) token));

          /* Deal with tables */

          if (token->tagno == TAG_TABLE)
          {
            TrOut(file, depth, (file, "This token represents a table. As a table_stream struct:\n\n"));

            if (trace_dump_tstream(file, (table_stream *) token, depth)) return 1;

            {
              table_stream   * table     = (table_stream *) token;
              table_row      * row       = table->List;
              table_headdata * head      = NULL;
              reformat_cell  * c         = NULL;
              reformat_cell  * cellarray = table->cells;
              int              cellmax   = table->ColSpan * table->RowSpan;
              int              cellindex;

              if (cellarray)
              {
                {
                  int i;

                  TrOut(file, depth, (file, "Cell array contents:\n\n"));

                  for (i = 0; i < cellmax; i ++)
                  {
                    TrOut(file, depth, (file, "[%d]\t%08x\n", (int) i, (int) &cellarray[i]));
                  }

                  TrOut(file, depth, (file, "\n"));
                }

                while (row)
                {
                  head = row->List;

                  while (
                          head                           &&
                          head->RowOffs < table->RowSpan &&
                          head->ColOffs < table->ColSpan
                        )
                  {
                    switch (head->Tag)
                    {
                      case TagTableData:
                      case TagTableHead:
                      {
                        /* Find the reformat_cell structure for this table cell */

                        cellindex = head->RowOffs * table->ColSpan + head->ColOffs;

                        if (cellindex < cellmax)
                        {
                          c = &cellarray[cellindex];

                          TrOut(file, 0, (file, "\n\n"));

                          if (c)
                          {
                            if (trace_dump_tokens_by_line_r(b, c, file, depth + 1)) return 1;
                          }
                          else
                          {
                            TrOut(file, depth, (file, "Warning: Entry %d in cellarray is NULL\n", cellindex));
                          }
                        }
                      }
                    }

                    if (head->Next)
                    {
                      /* Check the 'next' pointer is valid */

                      e = _swix(OS_ValidateAddress,
                                _INR(0,1) | _OUT(_FLAGS),

                                head->Next,
                                head->Next + 1,

                                &valid);

                      if (e || (valid & _C))
                      {
                        fprintf(file, "Warning: This table_headdata has an invalid 'Next' pointer - aborting list here\n");

                        return 1;
                      }
                    }

                    /* Get next item */

                    head = head->Next;
                  }

                  if (row->Next)
                  {
                    /* Check the 'next' pointer is valid */

                    e = _swix(OS_ValidateAddress,
                              _INR(0,1) | _OUT(_FLAGS),

                              row->Next,
                              row->Next + 1,

                              &valid);

                    if (e || (valid & _C))
                    {
                      fprintf(file, "Warning: This table_row has an invalid 'Next' pointer - aborting list here\n");

                      return 1;
                    }
                  }

                  /* Get next item */

                  row = row->Next;
                }
              }
              else
              {
                TrOut(file, depth, (file, "Warning: No cellarray - can't scan the lines inside the table.\n"));
              }
            }
          }
          else
          {
            /* Normal tokens */

            if (trace_dump_hstream(file, token, depth)) return 1;
          }
        }
        else
        {
          TrOut(file, depth, (file,
                      "Line %d chunk %d, same token (%08x) as last chunk.\n\n",
                      line,
                      chunk,
                      (int) token));
        }
      }
    }

    return 0;
  }

  /*************************************************/
  /* trace_dump_tokens_by_stream()                 */
  /*                                               */
  /* Outputs a diagnostic description of the       */
  /* token list of a browser. The browser is       */
  /* obtained by getting the client handle of the  */
  /* ancestor of whatever object raised the        */
  /* ETraceTokenDumpByStream event (see            */
  /* TBEvents.h).                                  */
  /*                                               */
  /* This function follows the raw stream to get   */
  /* each token, so everything output by the       */
  /* library should be included, even if the       */
  /* browser isn't actually using it.              */
  /*                                               */
  /* Parameters are as standard for a Toolbox      */
  /* event handler.                                */
  /*************************************************/

  int trace_dump_tokens_by_stream(int eventcode, ToolboxEvent * event, IdBlock * idb, void * handle)
  {
    FILE            * tfile;
    browser_data    * b;
    _kernel_oserror * e;

    /* Try to open out the file */

    tfile = fopen(Trace_DumpTokens_Filename_S, "wb");

    if (!tfile)
    {
      erb.errnum = Utils_Error_Custom_Message;
      StrNCpy0(erb.errmess,
               "trace_dump_tokens: Error trying to open output file");

      show_error_ret(&erb);

      return 0;
    }

    /* Try to get the browser_data structure */

    e = toolbox_get_client_handle(0, idb->ancestor_id, (void *) &b);
    if (e)
    {
      show_error_ret(e);
      return 0;
    }

    if (!is_known_browser(b))
    {
      erb.errnum = Utils_Error_Custom_Message;
      sprintf(erb.errmess,
              "trace_dump_tokens: Can't determine what token list to use ('%08x' is not a known browser)",
              (int) b);

      show_error_ret(&erb);
      return 0;
    }

    /* OK, can proceed */

    if (fprintf(tfile, "Token dump list for browser %08x\n\n\n\n", (int) b) < 0) goto tdtbs_fo_err;

    if (trace_dump_tokens_by_stream_r(b, b->stream, tfile, 0)) goto tdtbs_fo_err;

    /* Finished */

    fclose(tfile);

    _swix(OS_CLI,
          _IN(0),

          "SetType "
          Trace_DumpTokens_Filename_S
          " fff");

    return 1;

    /* Now some special case code */

tdtbs_fo_err: /* If there's an error whilst writing to the file */

    fclose(tfile);

    erb.errnum = Utils_Error_Custom_Message;
    StrNCpy0(erb.errmess,
             "trace_dump_tokens: Error whilst writing token dump file");

    show_error_ret(e);

    return 0;
  }

  /*************************************************/
  /* trace_dump_tokens_by_stream_r()               */
  /*                                               */
  /* Recursive back-end to                         */
  /* trace_dump_tokens_by_stream.                  */
  /*                                               */
  /* Parameters: Pointer to a browser_data struct  */
  /*             relevant to the overall stream;   */
  /*                                               */
  /*             Pointer to the first HStream      */
  /*             struct to examine;                */
  /*                                               */
  /*             Pointer to a FILE structure for   */
  /*             outputting the information.       */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    1 if there was an error, else 0.  */
  /*************************************************/

  static int trace_dump_tokens_by_stream_r(browser_data * b, HStream * streambase, FILE * file, int depth)
  {
    HStream         * token = streambase;
    unsigned int      valid;
    _kernel_oserror * e;

    TrOut(file, depth, (file, "Browser %08x, stream base %08x:\n\n\n\n", (int) b, (int) streambase));

    while (token)
    {
      TrOut(file, depth, (file, "Token %08x:\n\n", (int) token));

      /* Deal with tables */

      if (token->tagno == TAG_TABLE)
      {
        TrOut(file, depth, (file, "This token represents a table. As a table_stream struct:\n\n"));

        if (trace_dump_tstream(file, (table_stream *) token, depth)) return 1;

        /* Recursive table scan */

        {
          table_stream   * table = (table_stream *) token;
          table_row      * row   = table->List;
          table_headdata * head  = NULL;

          while (row)
          {
            TrOut(file, depth, (file, "Table %08x: Row %08x\n\n", (int) table, (int) row));

            if (trace_dump_row(file, row, depth + 1)) return 1;

            head = row->List;

            while (head)
            {
              HStream * base = (HStream *) head->List;

              TrOut(file, depth, (file, "Table %08x, row %08x: Head %08x\n\n", (int) table, (int) row, (int) head));

              if (trace_dump_head(file, head, depth + 1)) return 1;

              TrOut(file, 0, (file, "\n\n"));

              if (trace_dump_tokens_by_stream_r(b, base, file, depth + 1)) return 1;

              if (head->Next)
              {
                /* Check the 'next' pointer is valid */

                e = _swix(OS_ValidateAddress,
                          _INR(0,1) | _OUT(_FLAGS),

                          head->Next,
                          head->Next + 1,

                          &valid);

                if (e || (valid & _C))
                {
                  fprintf(file, "Warning: This table_headdata has an invalid 'Next' pointer - aborting list here\n");

                  return 1;
                }
              }

              /* Get next item */

              head = head->Next;
            }

            if (row->Next)
            {
              /* Check the 'next' pointer is valid */

              e = _swix(OS_ValidateAddress,
                        _INR(0,1) | _OUT(_FLAGS),

                        row->Next,
                        row->Next + 1,

                        &valid);

              if (e || (valid & _C))
              {
                fprintf(file, "Warning: This table_row has an invalid 'Next' pointer - aborting list here\n");

                return 1;
              }
            }

            /* Get next item */

            row = row->Next;
          }
        }
      }
      else if (token->tagno == TAG_OBJECT)
      {
        /* Deal with objects */
        TrOut(file, depth, (file, "This token represents an object.\n\n"));

        if (trace_dump_hstream(file, token, depth)) return 1;

        TrOut(file, depth, (file, "Object %08x:\n\n", (int) token));

        if (trace_dump_tokens_by_stream_r(b, HtmlOBJECTstream(token), file, depth + 1)) return 1;
      }
      else
      {
        /* Normal tokens */

        if (trace_dump_hstream(file, token, depth)) return 1;
      }

      if (token->next)
      {
        /* Check the 'next' pointer is valid */

        e = _swix(OS_ValidateAddress,
                  _INR(0,1) | _OUT(_FLAGS),

                  token->next,
                  token->next + 1,

                  &valid);

        if (e || (valid & _C))
        {
          fprintf(file, "Warning: This token has an invalid 'next' pointer - aborting list here\n");

          return 1;
        }
      }

      /* Follow the list */

      token = token->next;
    }

    return 0;
  }

  /*************************************************/
  /* trace_dump_hstream()                          */
  /*                                               */
  /* Output the contents of an HStream in a        */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the HStream struct;    */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_hstream(FILE * file, HStream * token, int depth)
  {
    unsigned int   mask;
    char         * style_info;

    /* Display the known values in the flags word */

    TrOut(file, depth, (file, "flags\t\t0x%x\n",        (int) token->flags));

    if (token->flags & HFlags_DealtWithToken)
    {
      TrOut(file, depth, (file, "\t\tHFlags_DealtWithToken\n"));
    }

    if (token->flags & HFlags_LinkVisited)
    {
      TrOut(file, depth, (file, "\t\tHFlags_LinkVisited\n"));
    }

    if (token->flags & HFlags_IgnoreObject)
    {
      TrOut(file, depth, (file, "\t\tHFlags_IgnoreObject\n"));
    }

    mask = HFlags_DealtWithToken |
           HFlags_LinkVisited    |
           HFlags_IgnoreObject;

    if (token->flags & ~mask)
    {
      TrOut(file, depth, (file, "\t\tWarning: undefined flags bits set\n"));
    }

    TrOut(file, depth, (file, "parent\t\t0x%x\n",       (int) token->parent));

    /* Deal with known 'type' field values */

    TrOut(file, depth, (file, "type\t\t%08x\n",         (int) token->type));

    if (ISHEAD(token))
    {
      TrOut(file, depth, (file, "\t\tISHEAD\n"));
    }

    if (ISBODY(token))
    {
      TrOut(file, depth, (file, "\t\tISBODY\n"));
    }

    if (ISNULL(token))
    {
      TrOut(file, depth, (file, "\t\tISNULL\n"));
    }

    if (ISFRAMESET(token))
    {
      TrOut(file, depth, (file, "\t\tISFRAMESET\n"));
    }

    /* General info */

    style_info = trace_style_bits(token);

    if (style_info)
    {
      TrOut(file, depth, (file, "style\t\t%08x: %s\n", (int) token->style, style_info));

      free(style_info);
    }
    else
    {
      if (!token->style)
      {
        TrOut(file, depth, (file, "style\t\t%08x: (NULL)\n",    (int) token->style));
      }
      else
      {
        TrOut(file, depth, (file, "style\t\t%08x: (Can't generate description)\n", (int) token->style));
      }
    }

    // TrOut(file, depth, (file, "style2\t\t%08x\n",       (int) token->style2));

    TrOut(file, depth, (file, "indent\t\t0x%x\n",       (int) token->indent));

    TrOut(file, depth, (file, "anchor\t\t%08x: '%s'\n", (int) token->anchor, token->anchor   ? token->anchor  : "(NULL)"));
    TrOut(file, depth, (file, "text\t\t%08x: '%s'\n",   (int) token->text,   token->text     ? token->text    : "(NULL)"));

    TrOut(file, depth, (file, "tag\t\t%08x\n",          (int) token->tag));
    TrOut(file, depth, (file, "tagno\t\t%d\t: <%s>\n",        token->tagno,  trace_tag_name(token->tagno)));

    TrOut(file, depth, (file, "src\t\t%08x: '%s'\n",    (int) token->src,    token->src      ? token->src     : "(NULL)"));

    TrOut(file, depth, (file, "next\t\t%08x\n",         (int) token->next));
    TrOut(file, depth, (file, "prev\t\t%08x\n",         (int) token->prev));

    TrOut(file, depth, (file, "enctype\t\t%08x: '%s'\n",(int) token->enctype, token->enctype ? token->enctype : "(NULL)"));
    TrOut(file, depth, (file, "name\t\t%08x: '%s'\n",   (int) token->name,    token->name    ? token->name    : "(NULL)"));
    TrOut(file, depth, (file, "value\t\t%08x: '%s'\n",  (int) token->value,   token->value   ? token->value   : "(NULL)"));
    TrOut(file, depth, (file, "target\t\t%08x: '%s'\n", (int) token->target,  token->target  ? token->target  : "(NULL)"));

    TrOut(file, depth, (file, "coords\t\t0x%08x: ",     (int) token->coords));

    /* Deal with coordinates - i.e. image map areas */

    if (token->coords)
    {
      areashape type;
      int       n;

      type = (areashape) token->coords[0];
      n    = token->coords[1];

      switch (type)
      {
        case areashape_RECT:
        {
          if (fprintf(file, "RECT (%d coords): ", n) < 0) return 1;

          if (n != 4)
          {
            if (fprintf(file, "Illegal\n") < 0) return 1;
          }
          else
          {
            if (fprintf(file, "lx %d, ty %d, rx %d, by %d\n", token->coords[2], token->coords[3], token->coords[4], token->coords[5]) < 0) return 1;
          }
        }
        break;

        case areashape_CIRCLE:
        {
          if (fprintf(file, "CIRCLE (%d coords): ", n) < 0) return 1;

          if (n != 3)
          {
            if (fprintf(file, "Illegal\n") < 0) return 1;
          }
          else
          {
            if (fprintf(file, "x %d, y %d, r %d\n", token->coords[2], token->coords[3], token->coords[4]) < 0) return 1;
          }
        }
        break;

        case areashape_POLY:
        {
          if (fprintf(file, "POLY (%d coords)", n) < 0) return 1;

          if (n < 6 || n % 2)
          {
            if (fprintf(file, ": Illegal\n") < 0) return 1;
          }
          else
          {
            int vc;

            if (fprintf(file, "\n\n") < 0) return 1;

            TrOut(file, depth, (file, "\t\t\t       x       y\n"));

            for (vc = 0; vc < n; vc += 2)
            {
              TrOut(file, depth, (file, "\t\t\t%8d%8d\n", token->coords[vc + 2], token->coords[vc + 3]));
            }
          }
        }
        break;

        case areashape_DEFAULT:
        {
          if (fprintf(file, "DEFAULT\n") < 0) return 1;
        }
        break;
      }
    }
    else if (fprintf(file, "(NULL)\n") < 0) return 1;

    /* Continue on with other fields */

    TrOut(file, depth, (file, "size\t\t0x%x\n",         (int) token->size));
    TrOut(file, depth, (file, "maxlen\t\t0x%x\n",       (int) token->maxlen));
    TrOut(file, depth, (file, "rows\t\t0x%x\n",         (int) token->rows));
    TrOut(file, depth, (file, "cols\t\t0x%x\n",         (int) token->cols));
    TrOut(file, depth, (file, "colour\t\t0x%06xxx\n",   (int) token->colour));
    TrOut(file, depth, (file, "fontsize\t%d\n",         (int) token->fontsize));

    switch (token->tagno)
    {
      case TAG_PARAM:
      {
        const char *valuetypename[3] = { "DATA", "REF", "OBJECT" };

        TrOut(file, depth, (file, "NAME\t\t'%s'\n",  HtmlPARAMname(token)  ? HtmlPARAMname(token)  : "(NULL)"));
        TrOut(file, depth, (file, "VALUE\t\t'%s'\n", HtmlPARAMvalue(token) ? HtmlPARAMvalue(token) : "(NULL)"));
        TrOut(file, depth, (file, "VALUETYPE\t%s\n", valuetypename[HtmlPARAMvaluetype(token)]));
        TrOut(file, depth, (file, "TYPE\t\t'%s'\n",  HtmlPARAMtype(token)  ? HtmlPARAMtype(token)  : "(NULL)"));
      }
      break;

      case TAG_FORM:
      {
        TrOut(file, depth, (file, "METHOD\t\t%d\n",  HtmlFORMmethod(token)));
        TrOut(file, depth, (file, "ACTION\t\t%s\n",  HtmlFORMaction(token) ? HtmlFORMaction(token) : "(NULL)"));
        TrOut(file, depth, (file, "TARGET\t\t%s\n",  HtmlFORMtarget(token) ? HtmlFORMtarget(token) : "(NULL)"));
      }
      break;

      case TAG_INPUT:
      {
        TrOut(file, depth, (file, "NAME\t\t%s\n",    HtmlINPUTname(token)  ? HtmlINPUTname(token)  : "(NULL)"));
        TrOut(file, depth, (file, "VALUE\t\t%s\n",   HtmlINPUTvalue(token) ? HtmlINPUTvalue(token) : "(NULL)"));
        TrOut(file, depth, (file, "SRC\t\t%s\n",     HtmlINPUTsrc(token)   ? HtmlINPUTsrc(token)   : "(NULL)"));
        TrOut(file, depth, (file, "ALT\t\t%s\n",     HtmlINPUTalt(token)   ? HtmlINPUTalt(token)   : "(NULL)"));
      }
    }

    TrOut(file, depth, (file, "\n"));

    return 0;
  }

  /*************************************************/
  /* trace_dump_tstream()                          */
  /*                                               */
  /* Output the contents of a table_stream in a    */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the table_stream       */
  /*             structure;                        */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_tstream(FILE * file, table_stream * table, int depth)
  {
    unsigned int   mask;
    char         * style_info;

    /* Display the flags */

    TrOut(file, depth, (file, "flags\t\t0x%x\n", (int) table->flags));

    if (table->flags & HFlags_DealtWithToken)
    {
      TrOut(file, depth, (file, "\t\tHFlags_DealtWithToken\n"));
    }

    if (table->flags & HFlags_LinkVisited)
    {
      TrOut(file, depth, (file, "\t\tHFlags_LinkVisited\n"));
    }

    mask = HFlags_DealtWithToken |
           HFlags_LinkVisited;

    if (table->flags & ~mask)
    {
      TrOut(file, depth, (file, "\t\tWarning: undefined flags bits set\n"));
    }

    TrOut(file, depth, (file, "parent\t\t0x%x\n",  (int) table->parent));

    /* Deal with known cases of the 'type' field */

    TrOut(file, depth, (file, "type\t\t%08x\n",    (int) table->type));

    if (ISHEAD(table))
    {
      TrOut(file, depth, (file, "\t\tISHEAD\n"));
    }

    if (ISBODY(table))
    {
      TrOut(file, depth, (file, "\t\tISBODY\n"));
    }

    if (ISNULL(table))
    {
      TrOut(file, depth, (file, "\t\tISNULL\n"));
    }

    if (ISFRAMESET(table))
    {
      TrOut(file, depth, (file, "\t\tISFRAMESET\n"));
    }

    style_info = trace_style_bits((HStream *) table);

    if (style_info)
    {
      TrOut(file, depth, (file, "style\t\t%08x: %s\n", (int) table->style, style_info));

      free(style_info);
    }
    else
    {
      if (!table->style)
      {
        TrOut(file, depth, (file, "style\t\t%08x: (NULL)\n",    (int) table->style));
      }
      else
      {
        TrOut(file, depth, (file, "style\t\t%08x: (Can't generate description)\n", (int) table->style));
      }
    }

    // TrOut(file, depth, (file, "style2\t\t%08x\n",  (int) table->style2));

    TrOut(file, depth, (file, "Tag\t\t%08x\n",     (int) table->Tag));

    TrOut(file, depth, (file, "ColSpan\t\t%d\n",   (int) table->ColSpan));
    TrOut(file, depth, (file, "RowSpan\t\t%d\n",   (int) table->RowSpan));

    /* Display the arrays of row and column offsets, if possible */

    TrOut(file, depth, (file, "ColOffs\t\t%08x\n", (int) table->ColOffs));

    if (table->ColOffs)
    {
      int col, limit;

      /* Try to use the table_stream's record of number of rows/cols - only */
      /* filled in if the browser has parsed the table. If this is zero,    */
      /* use 16 (more or less arbitrary); otherwise, output as many as      */
      /* indicated plus 4 to check that the array wasn't overshot (should   */
      /* commonly see silly values in those extra 4). Don't do more than 64 */
      /* though, or we'll be here all day... (Potentially, a corrupted or   */
      /* uninitialised ColSpan/RowSpan could indicate a *very* large number */
      /* of rows or columns!).                                              */

      limit = table->ColSpan;

      if (limit == 0) limit = 16;
      else            limit += 4;

      if (limit > 64) limit = 64;

      for (col = 0; col < limit; col ++)
      {
        TrOut(file, depth, (file, "\t\t[%d]\t%08x (%08x)\n", col, (int) table->ColOffs[col], (int) table->ColOffs[col]/400));

        if (table->ColSpan && col == table->ColSpan - 1)
        {
          TrOut(file, depth, (file, "\t\t(This is probably the last entry)\n"));
        }
      }
    }

    TrOut(file, depth, (file, "RowOffs\t\t%08x\n", (int) table->RowOffs));

    /* Same as above, for rows */

    if (table->RowOffs)
    {
      int row, limit;

      limit = table->RowSpan;

      if (limit == 0) limit = 16;
      else            limit += 4;

      if (limit > 64) limit = 64;

      for (row = 0; row < limit; row ++)
      {
        TrOut(file, depth, (file, "\t\t[%d]\t%08x (%08x)\n", row, (int) table->RowOffs[row], (int) table->RowOffs[row]/400));

        if (table->RowSpan && row == table->RowSpan - 1)
        {
          TrOut(file, depth, (file, "\t\t(This is probably the last entry)\n"));
        }
      }
    }

    /* General info */

    TrOut(file, depth, (file, "Next\t\t%08x\n",           (int) table->Next));
    TrOut(file, depth, (file, "Prev\t\t%08x\n",           (int) table->Prev));
    TrOut(file, depth, (file, "List\t\t%08x\n",           (int) table->List));

    TrOut(file, depth, (file, "bgcol\t\t%08x\n",          (int) table->bgcol));
    TrOut(file, depth, (file, "background\t%08x: '%s'\n", (int) table->background, table->background ? table->background : "(NULL)"));

    TrOut(file, depth, (file, "cols\t\t%d\n",             (int) table->cols));
    TrOut(file, depth, (file, "width\t\t%d\n",            (int) table->width));
    TrOut(file, depth, (file, "height\t\t%d\n",           (int) table->height));
    TrOut(file, depth, (file, "border\t\t%d\n",           (int) table->border));

    TrOut(file, depth, (file, "cellspacing\t%d\n",        (int) table->cellspacing));
    TrOut(file, depth, (file, "cellpadding\t%d\n",        (int) table->cellpadding));

    TrOut(file, depth, (file, "stackedstyle\t%08x\n",     (int) table->stackedstyle));
    TrOut(file, depth, (file, "cells\t\t%08x\n",          (int) table->cells));

    TrOut(file, depth, (file, "Align\t\t0x%x\n",          (int) table->Align));

    /* Deal with specific bitfield flags */

    if (table->awaiting_tr)
    {
      TrOut(file, depth, (file, "awaiting_tr\tyes\n"));
    }
    else
    {
      TrOut(file, depth, (file, "awaiting_tr\tno\n"));
    }

    if (table->finished)
    {
      TrOut(file, depth, (file, "finished\tyes\n\n"));
    }
    else
    {
      TrOut(file, depth, (file, "finished\tno\n\n"));
    }

    return 0;
  }

  /*************************************************/
  /* trace_dump_row()                              */
  /*                                               */
  /* Output the contents of a table_row in a       */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the table_row struct;  */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_row(FILE * file, table_row * row, int depth)
  {
    /* General info */

    TrOut(file, depth, (file, "Next\t\t%08x\n",   (int) row->Next));
    TrOut(file, depth, (file, "Prev\t\t%08x\n",   (int) row->Prev));
    TrOut(file, depth, (file, "parent\t\t%08x\n", (int) row->parent));
    TrOut(file, depth, (file, "List\t\t%08x\n",   (int) row->List));

    /* 8 bit values */

    TrOut(file, depth, (file, "align\t\t%d\n",    (int) row->align));
    TrOut(file, depth, (file, "valign\t\t%d\n",   (int) row->valign));

    /* Background colour (inherited by cells on the row) */

    TrOut(file, depth, (file, "bgcol\t\t%08x\n\n",(int) row->bgcol));

    return 0;
  }

  /*************************************************/
  /* trace_dump_head()                             */
  /*                                               */
  /* Output the contents of a table_headdata in a  */
  /* readable form to the given file.              */
  /*                                               */
  /* Parameters: Pointer to a FILE structure for   */
  /*             the file to write to;             */
  /*                                               */
  /*             Pointer to the table_headdata     */
  /*             structure;                        */
  /*                                               */
  /*             Nesting depth.                    */
  /*                                               */
  /* Returns:    0 for success, 1 for failure (an  */
  /*             error, usually when writing to    */
  /*             the file).                        */
  /*************************************************/

  static int trace_dump_head(FILE * file, table_headdata * head, int depth)
  {
    TrOut(file, depth, (file, "ColSpan\t\t%d\n",   (int) head->ColSpan));
    TrOut(file, depth, (file, "RowSpan\t\t%d\n",   (int) head->RowSpan));
    TrOut(file, depth, (file, "ColOffs\t\t%d\n",   (int) head->ColOffs));
    TrOut(file, depth, (file, "RowOffs\t\t%d\n",   (int) head->RowOffs));

    TrOut(file, depth, (file, "Tag\t\t0x%x\n",     (int) head->Tag));

    switch (head->Tag)
    {
      case TagTableHead:
      {
        TrOut(file, depth, (file, "\t\tTagTableHead\n"));
      }
      break;

      case TagTableData:
      {
        TrOut(file, depth, (file, "\t\tTagTableData\n"));
      }
      break;

      default:
      {
        TrOut(file, depth, (file, "\t\tWarning: Unrecognised Tag type\n"));
      }
      break;
    }

    /* General info */

    TrOut(file, depth, (file, "parent\t\t%08x\n",   (int) head->parent));
    TrOut(file, depth, (file, "Next\t\t%08x\n",     (int) head->Next));
    TrOut(file, depth, (file, "Prev\t\t%08x\n",     (int) head->Prev));
    TrOut(file, depth, (file, "List\t\t%08x\n",     (int) head->List));

    /* 8 bit values */

    TrOut(file, depth, (file, "Align\t\t%d\n",      (int) head->Align));
    TrOut(file, depth, (file, "VAlign\t\t%d\n",     (int) head->VAlign));

    /* Background image */

    TrOut(file, depth, (file, "background\t%08x: '%s'\n", (int) head->background, head->background ? head->background : "(NULL)"));

    /* More general info */

    TrOut(file, depth, (file, "width\t\t%d (%d)\n", (int) head->width,  (int) head->width  / 400));
    TrOut(file, depth, (file, "height\t\t%d (%d)\n",(int) head->height, (int) head->height / 400));

    /* Background colour */

    TrOut(file, depth, (file, "bgcol\t\t%08x\n\n",  (int) head->bgcol));

    return 0;
  }

  /*************************************************/
  /* trace_dump_buffer()                           */
  /*                                               */
  /* Outputs a given buffer's contents over TML,   */
  /* with unprintable chars shown in green (well,  */
  /* this depends on the buffer type - see below)  */
  /* as '[xx]', where xx is the character's ASCII  */
  /* code in hex.                                  */
  /*                                               */
  /* For clarity at the output terminal, it is     */
  /* possible to specify the type of buffer - a    */
  /* buffer for data transmission, reception, or   */
  /* a miscellaneous buffer. Control code colours  */
  /* will change for this; green for transmission, */
  /* red for reception, cyan for others.           */
  /*                                               */
  /* Parameters: Pointer to the buffer;            */
  /*                                               */
  /*             Size of the buffer;               */
  /*                                               */
  /*             1 for a transmit buffer, 2 for a  */
  /*             receive buffer, else 'other'.     */
  /*************************************************/

  void trace_dump_buffer(void * buffer, int buffer_size, int type)
  {
    int  i;
    char c;

    /* Is the buffer empty (well, zero bytes long or less...)? */

    if (buffer_size < 1)
    {
      if      (type == 1) Printf("\nTransmission buffer %p empty\n\n", buffer);
      else if (type == 2) Printf("\nReception buffer %p empty\n\n", buffer);
      else                Printf("\nGeneral buffer %p empty\n\n", buffer);

      return;
    }

    /* If not, dump the contents - first, a header message */

    if      (type == 1) Printf("\nTransmission buffer %p contents:\n\n", buffer);
    else if (type == 2) Printf("\nReception buffer %p contents:\n\n", buffer);
    else                Printf("\nGeneral buffer %p contents:\n\n", buffer);

    /* Now, the body of the buffer */

    for (i = 0; i < buffer_size; i++)
    {
      c = *((char *) buffer + i);

      if (type == 1)
      {
        if (c < 32 || c == 127) Printf("\0212[%02x]\0217", (int) c);
        else Printf("%c", c);
      }
      else if (type == 2)
      {
        if (c < 32 || c == 127) Printf("\0211[%02x]\0217", (int) c);
        else Printf("%c", c);
      }
      else
      {
        if (c < 32 || c == 127) Printf("\0216[%02x]\0217", (int) c);
        else Printf("%c", c);
      }
    }

    /* Ensure the next output item is one blank line after the buffer */
    /* contents (aesthetics and clarity)                              */

    Printf("\n\n");
  }

  /*************************************************/
  /* trace_tag_name()                              */
  /*                                               */
  /* Converts a tag number (enum tag_no) to        */
  /* textual form (why isn't there a built in      */
  /* function to do this?).                        */
  /*                                               */
  /* Parameters: Tag number.                       */
  /*                                               */
  /* Returns:    Pointer to a statically allocated */
  /*             name.                             */
  /*************************************************/

  static const char * trace_tag_name(tag_no tagno)
  {
    switch (tagno)
    {
      case TAG_NONE:       return "none";
      case TAG_A:          return "A";
      case TAG_ADDRESS:    return "ADDRESS";
      case TAG_BOLD:       return "BOLD";
      case TAG_BASE:       return "BASE";
      case TAG_BASEFONT:   return "BASEFONT";
      case TAG_BLOCKQUOTE: return "BLOCKQUOTE";
      case TAG_BR:         return "BR";
      case TAG_CENTER:     return "CENTER";
      case TAG_CITE:       return "CITE";
      case TAG_CODE:       return "CODE";
      case TAG_DD:         return "DD";
      case TAG_DIR:        return "DIR";
      case TAG_DIV:        return "DIV";
      case TAG_DL:         return "DL";
      case TAG_DT:         return "DT";
      case TAG_DUMMY:      return "DUMMY";
      case TAG_EM:         return "EM";
      case TAG_FONT:       return "FONT";
      case TAG_FORM:       return "FORM";
      case TAG_H1:         return "H1";
      case TAG_H2:         return "H2";
      case TAG_H3:         return "H3";
      case TAG_H4:         return "H4";
      case TAG_H5:         return "H5";
      case TAG_H6:         return "H6";
      case TAG_HR:         return "HR";
      case TAG_ITALIC:     return "ITALIC";
      case TAG_IMG:        return "IMG";
      case TAG_INPUT:      return "INPUT";
      case TAG_ISINDEX:    return "ISINDEX";
      case TAG_KBD:        return "KBD";
      case TAG_LI:         return "LI";
      case TAG_LINK:       return "LINK";
      case TAG_MENU:       return "MENU";
      case TAG_META:       return "META";
      case TAG_OL:         return "OL";
      case TAG_OPTION:     return "OPTION";
      case TAG_P:          return "P";
      case TAG_PRE:        return "PRE";
      case TAG_SAMP:       return "SAMP";
      case TAG_SCRIPT:     return "SCRIPT";
      case TAG_SELECT:     return "SELECT";
      case TAG_STRIKE:     return "STRIKE";
      case TAG_STRONG:     return "STRONG";
      case TAG_STYLE:      return "STYLE";
      case TAG_TABLE:      return "TABLE";
      case TAG_TD:         return "TD";
      case TAG_TEXTAREA:   return "TEXTAREA";
      case TAG_TITLE:      return "TITLE";
      case TAG_TH:         return "TH";
      case TAG_TR:         return "TR";
      case TAG_TT:         return "TT";
      case TAG_U:          return "U";
      case TAG_UL:         return "UL";
      case TAG_VAR:        return "VAR";
      case TAG_XMP:        return "XMP";
      case TAG_FRAME:      return "FRAME";
      case TAG_BODY:       return "BODY";
      case TAG_FRAMESET:   return "FRAMESET";
      case TAG_HEAD:       return "HEAD";
      case TAG_NOFRAMES:   return "NOFRAMES";
      case TAG_SUB:        return "SUB";
      case TAG_SUP:        return "SUP";
      case TAG_AREA:       return "AREA";
      case TAG_MAP:        return "MAP";
      case TAG_OBJECT:     return "OBJECT";
      case TAG_PARAM:      return "PARAM";
      case TAG_BIG:        return "BIG";
      case TAG_SMALL:      return "SMALL";
      case TAG_APPLET:     return "APPLET";
      case TAG_FORM_END:   return "/FORM";

      default: return "unknown";
    }
  }

  /*************************************************/
  /* trace_add_description()                       */
  /*                                               */
  /* Used to add a description to a list of other  */
  /* descriptions. If the first item, the text is  */
  /* merely placed in a new block; subsequent      */
  /* items are concatenated onto the existing list */
  /* preceeded by a comma and a space.             */
  /*                                               */
  /* Parameters: Pointer to the malloc block, or   */
  /*             NULL if nothing has been          */
  /*             allocated yet;                    */
  /*                                               */
  /*             Pointer to a statically allocated */
  /*             description string.               */
  /*                                               */
  /* Returns:    Pointer to the malloc block,      */
  /*             which may have moved.             */
  /*************************************************/

  static char * trace_add_description(char * list, const char * description)
  {
    if (list)
    {
      char * new = realloc(list, strlen(list) + 3); /* 3 = comma, space, terminator */

      if (new)
      {
        list = new;
        strcat(list, ", ");

        new = realloc(list, strlen(list) + strlen(description) + 1);

        if (new)
        {
          list = new;
          strcat(list, description);
        }
      }
    }
    else
    {
      list = malloc(strlen(description) + 1);

      if (list) strcpy(list, description);
    }

    return list;
  }

  /*************************************************/
  /* trace_style_bits()                            */
  /*                                               */
  /* Converts a style word to textual form.        */
  /*                                               */
  /* Parameters: Pointer to the token to examine.  */
  /*                                               */
  /* Returns:    Pointer to a comma separated list */
  /*             of names, in a malloced block     */
  /*             which the caller is responsible   */
  /*             for freeing. NULL may be returned */
  /*             if the initial malloc fails.      */
  /*************************************************/

  static char * trace_style_bits(HStream * t)
  {
    char         * list = NULL;
    unsigned int   rems = t->style;

    if (ISHEAD(t))
    {
      if (t->style & LINK)           rems &= (~LINK),       list = trace_add_description(list, "<LIST>");
      if (t->style & ISINDEX)        rems &= (~ISINDEX),    list = trace_add_description(list, "<ISINDEX>");
      if (t->style & META)           rems &= (~META),       list = trace_add_description(list, "<META>");

      if (rems)
      {
        char desc[32];

        sprintf(desc, "Unknown remainder 0x%08x", (int) rems);

        list = trace_add_description(list, desc);
      }
    }
    else if (ISFRAMESET(t))
    {
      if (t->style & FRAME)          rems &= (~FRAME),      list = trace_add_description(list, "<FRAME>");

      if (rems)
      {
        char desc[32];

        sprintf(desc, "Unknown remainder 0x%08x", (int) rems);

        list = trace_add_description(list, desc);
      }
    }
    else if (ISBODY(t))
    {
      /* General flags */

      if (t->style & BOLD)           rems &= (~BOLD),       list = trace_add_description(list, "<BOLD>");
      if (t->style & ITALIC)         rems &= (~ITALIC),     list = trace_add_description(list, "<ITALIC>");
      if (t->style & TT)             rems &= (~TT),         list = trace_add_description(list, "<TT>");
      if (t->style & A)              rems &= (~A),          list = trace_add_description(list, "<A>");
      if (t->style & IMG)            rems &= (~IMG),        list = trace_add_description(list, "<IMG>");
      if (t->style & P)              rems &= (~P),          list = trace_add_description(list, "<P>");
      if (t->style & BR)             rems &= (~BR),         list = trace_add_description(list, "<BR>");
      if (t->style & HR)             rems &= (~HR),         list = trace_add_description(list, "<HR>");
      if (t->style & PRE)            rems &= (~PRE),        list = trace_add_description(list, "<PRE>");
      if (t->style & DL)             rems &= (~DL),         list = trace_add_description(list, "<DL>");
      if (t->style & DT)             rems &= (~DT),         list = trace_add_description(list, "<DT>");
      if (t->style & DD)             rems &= (~DD),         list = trace_add_description(list, "<DD>");
      if (t->style & UL)             rems &= (~UL),         list = trace_add_description(list, "<UL>");
      if (t->style & LI)             rems &= (~LI),         list = trace_add_description(list, "<LI>");
      if (t->style & BLOCKQUOTE)     rems &= (~BLOCKQUOTE), list = trace_add_description(list, "<BLOCKQUOTE>");
      if (t->style & ADDRESS)        rems &= (~ADDRESS),    list = trace_add_description(list, "<ADDRESS>");
      if (t->style & CENTER)         rems &= (~CENTER),     list = trace_add_description(list, "<CENTER>");
      if (t->style & FONT)           rems &= (~FONT),       list = trace_add_description(list, "<FONT>");
      if (t->style & UNDERLINE)      rems &= (~UNDERLINE),  list = trace_add_description(list, "<UNDERLINE>");
      if (t->style & STRIKE)         rems &= (~STRIKE),     list = trace_add_description(list, "<STRIKE>");
      if (t->style & FORM)           rems &= (~FORM),       list = trace_add_description(list, "<FORM>");
      if (t->style & RIGHT)          rems &= (~RIGHT),      list = trace_add_description(list, "<RIGHT>");
      if (t->style & SUB)            rems &= (~SUB),        list = trace_add_description(list, "<SUB>");
      if (t->style & SUP)            rems &= (~SUP),        list = trace_add_description(list, "<SUP>");
      if (t->style & NOBR)           rems &= (~NOBR),       list = trace_add_description(list, "<NOBR>");

      /* This area has got fairly messy due to various table */
      /* implementations and so forth                        */

      if (t->style & PCDATA)         rems &= (~PCDATA),     list = trace_add_description(list, "(PCDATA)");

      /* Heading items */

      if ((t->style & H_MASK) == H1) rems &= (~H_MASK),     list = trace_add_description(list, "<H1>");
      if ((t->style & H_MASK) == H2) rems &= (~H_MASK),     list = trace_add_description(list, "<H2>");
      if ((t->style & H_MASK) == H3) rems &= (~H_MASK),     list = trace_add_description(list, "<H3>");
      if ((t->style & H_MASK) == H4) rems &= (~H_MASK),     list = trace_add_description(list, "<H4>");
      if ((t->style & H_MASK) == H5) rems &= (~H_MASK),     list = trace_add_description(list, "<H5>");
      if ((t->style & H_MASK) == H6) rems &= (~H_MASK),     list = trace_add_description(list, "<H6>");

      if (rems)
      {
        char desc[32];

        sprintf(desc, "Unknown remainder 0x%08x", (int) rems);

        list = trace_add_description(list, desc);
      }

      /* These currently map back to existing items so they don't need including */
      /* separately (as every TT item would say TT, CODE, SAMP... etc. - they    */
      /* are left here to make it easier to add them to the above code should a  */
      /* distinct bit get allocated.                                             */

      // if (t->style & EM)           rems &= (~EM),         list = trace_add_description(list, "<EM -> ITALIC>");
      // if (t->style & STRONG)       rems &= (~STRONG),     list = trace_add_description(list, "<STRONG -> BOLD>");
      // if (t->style & CODE)         rems &= (~CODE),       list = trace_add_description(list, "<CODE -> TT>");
      // if (t->style & SAMP)         rems &= (~SAMP),       list = trace_add_description(list, "<SAMP -> TT>");
      // if (t->style & KBD)          rems &= (~KBD),        list = trace_add_description(list, "<KBD -> TT>");
      // if (t->style & VAR)          rems &= (~VAR),        list = trace_add_description(list, "<VAR -> TT>");
      // if (t->style & CITE)         rems &= (~CITE),       list = trace_add_description(list, "<CITE -> ITALIC>");
    }
    else list = trace_add_description(list, "(Not HEAD or BODY)");

    return list;
  }

#else

  /* This'll work, but it is more efficient not to link to  */
  /* Trace.o at all for non-debug builds, as if TRACE is    */
  /* undefined, absolutely nothing within Trace.o will be   */
  /* referenced (and nothing will #include Trace.h either). */

  void trace_keep_compiler_happy(void)
  {
    int a;

    a = 0;
  }

#endif