/*
 * xhtml backend for Halibut
 * (initial implementation by James Aylett)
 *
 * Still to do:
 *
 *  +++ doesn't handle non-breaking hyphens. Not sure how to yet.
 *  +++ entity names (from a file -- ideally supply normal SGML files)
 *  +++ configuration directive to file split where the current layout
 *      code wouldn't. Needs changes to _ponder_layout() and _do_paras(),
 *      perhaps others.
 *
 * Limitations:
 *
 *  +++ biblio/index references target the nearest section marker, rather
 *   than having a dedicated target themselves. In large bibliographies
 *   this will cause problems. (The solution is to fake up a response
 *   from xhtml_find_section(), probably linking it into the sections
 *   chain just in case we need it again, and to make freeing it up
 *   easier.) docsrc.pl used to work as we do, however, and SGT agrees that
 *   this is acceptable for now.
 *  +++ can't cope with leaf-level == 0. It's all to do with the
 *   top-level file not being normal, probably not even having a valid
 *   section level, and stuff like that. I question whether this is an
 *   issue, frankly; small manuals that fit on one page should probably
 *   not be written in halibut at all.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include "halibut.h"

enum outputtype { OT_XHTML = 0x01, OT_CHM = 0x02, OT_HTML4 = 0x04, OT_HTML5 = 0x08 };

int g_outputtype = OT_XHTML;
#define is_chm() ( (g_outputtype & OT_CHM) == OT_CHM )
#define is_xhtml() ( (g_outputtype & OT_XHTML) == OT_XHTML )
#define is_html5() ( (g_outputtype & OT_HTML5) == OT_HTML5 )

#define gettagtxt_br() ( is_xhtml() ? "<br />" : "<br>" )
#define gettagtxt_hr() ( is_xhtml() ? "<hr />" : "<hr>" )

struct xhtmlsection_Struct {
  struct xhtmlsection_Struct *next;     /* next sibling (NULL if split across files) */
  struct xhtmlsection_Struct *child;    /* NULL if split across files */
  struct xhtmlsection_Struct *parent;   /* NULL if split across files */
  struct xhtmlsection_Struct *chain;    /* single structure independent of weird trees */
  paragraph *para;
  struct xhtmlfile_Struct *file;        /* which file is this a part of? */
  char *fragment;               /* fragment id within the file */
  int level;
};

struct xhtmlfile_Struct {
  struct xhtmlfile_Struct *next;
  struct xhtmlfile_Struct *child;
  struct xhtmlfile_Struct *parent;
  char *filename;
  struct xhtmlsection_Struct *sections; /* sections within this file (only one for non-leaf) */
  int is_leaf;                  /* is this file a leaf file, ie does it not have any children? */
};

typedef struct xhtmlsection_Struct xhtmlsection;
typedef struct xhtmlfile_Struct xhtmlfile;
typedef struct xhtmlindex_Struct xhtmlindex;

struct xhtmlindex_Struct {
  int nsection;
  int size;
  xhtmlsection **sections;
};

typedef struct {
  int just_numbers;
  wchar_t *number_suffix;
} xhtmlheadfmt;

typedef struct {
  int contents_depth[6];
  int leaf_contains_contents;
  int leaf_level;
  int leaf_smallest_contents;
  int include_version_id;
  wchar_t *author, *description;
  wchar_t *html_lang, *meta_charset;
  wchar_t *head_start, *head_middle, *head_end, *body, *body_start, *body_end;
  ustr_slist *meta_append;
  wchar_t *address_start, *address_end, *nav_attrs;
  wchar_t *rlink_prefix, *rlink_suffix;
  wchar_t *chm_toc_file, *chm_ind_file;
  int suppress_address;
  xhtmlheadfmt fchapter, *fsect;
  int nfsect;
  int keywordfragments;
} xhtmlconfig;

/*static void xhtml_level(paragraph *, int);
static void xhtml_level_0(paragraph *);
static void xhtml_docontents(FILE *, paragraph *, int);
static void xhtml_dosections(FILE *, paragraph *, int);
static void xhtml_dobody(FILE *, paragraph *, int);*/

static void xhtml_doheader(FILE *, word *);
static void xhtml_dofooter(FILE *);
static void xhtml_versionid(FILE *, word *, int);

static void xhtml_utostr(wchar_t *, char **);
static int xhtml_para_level(paragraph *);
static int xhtml_reservedchar(int);

static int xhtml_convert(wchar_t *, char **, int);
static void xhtml_rdaddwc(rdstringc *, word *, word *);
static void xhtml_para(FILE *, word *);
static void xhtml_codepara(FILE *, word *);
static void xhtml_heading(FILE *, paragraph *);

static void chm_doheader(FILE *, word *);
static void chm_dofooter(FILE *);
/* File-global variables are much easier than passing these things
 * all over the place. Evil, but easier. We can replace this with a single
 * structure at some point.
 */
static xhtmlconfig conf;
static keywordlist *keywords;
static indexdata *idx;
static xhtmlfile *topfile;
static xhtmlsection *topsection;
static paragraph *sourceparas;
static xhtmlfile *lastfile;
static xhtmlfile *xhtml_last_file = NULL;
static int last_level = -1;
static xhtmlsection *currentsection;
static FILE* chm_toc = NULL;
static FILE* chm_ind = NULL;

static const wchar_t* normalizehtmlkeywordprefix(const wchar_t*s) // [x]html... --> html...
{
  return s && utolower(s[0]) == 'x' && utolower(s[1]) == 'h' && utolower(s[2]) == 't' ? s + 1 : s;
}

static const wchar_t* ishtmlkeyword(const wchar_t*a, const wchar_t*b)
{
  return !ustricmp(normalizehtmlkeywordprefix(a), normalizehtmlkeywordprefix(b)) ? a : 0;
}

static wchar_t* configurekeyword(wchar_t**dst, const wchar_t*keyword, const paragraph*source)
{
  if (ishtmlkeyword(source->keyword, keyword))
    return *dst = uadv(source->keyword);
  else
    return 0;
}

static xhtmlconfig xhtml_configure(paragraph * source)
{
  xhtmlconfig ret;

  /*
   * Defaults.
   */
  ret.contents_depth[0] = 2;
  ret.contents_depth[1] = 3;
  ret.contents_depth[2] = 4;
  ret.contents_depth[3] = 5;
  ret.contents_depth[4] = 6;
  ret.contents_depth[5] = 7;
  ret.leaf_level = 2;
  ret.leaf_smallest_contents = 4;
  ret.leaf_contains_contents = FALSE;
  ret.include_version_id = TRUE;
  ret.author = NULL;
  ret.description = NULL;
  ret.html_lang = NULL;
  ret.meta_charset = NULL;
  ret.head_start = ret.head_middle = ret.head_end = NULL;
  ret.body = NULL;
  ret.body_start = NULL;
  ret.body_end = NULL;
  ret.meta_append = NULL;
  ret.address_start = NULL;
  ret.address_end = NULL;
  ret.nav_attrs = NULL;
  ret.suppress_address = FALSE;
  ret.chm_toc_file = NULL;
  ret.chm_ind_file = NULL;
  chm_toc = NULL;
  chm_ind = NULL;
  ret.fchapter.just_numbers = FALSE;
  ret.fchapter.number_suffix = ustrdup(L": ");
  ret.nfsect = 2;
  ret.fsect = mknewa(xhtmlheadfmt, ret.nfsect);
  ret.fsect[0].just_numbers = FALSE;
  ret.fsect[0].number_suffix = ustrdup(L": ");
  ret.fsect[1].just_numbers = TRUE;
  ret.fsect[1].number_suffix = ustrdup(L" ");
  ret.rlink_prefix = NULL;
  ret.rlink_suffix = NULL;
  ret.keywordfragments = TRUE;

  for (; source; source = source->next)
  {
    if (source->type == para_Config)
    {
      if (ishtmlkeyword(source->keyword, L"html-version"))
      {
        const wchar_t* v = uadv(source->keyword);
        if (!ustricmp(v, L"html4")) g_outputtype = OT_HTML4; // Note: Upstream treats this as "W3C HTML 4.01 Strict"
        else if (!ustricmp(v, L"html5")) g_outputtype = OT_HTML5;
        else if (!ustricmp(v, L"xhtml1.0transitional")) g_outputtype = OT_XHTML;
        else error(err_whatever, "%ls unknown %ls", source->keyword, v);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-contents-depth-0"))
      {
        ret.contents_depth[0] = utoi(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-contents-depth-1"))
      {
        ret.contents_depth[1] = utoi(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-contents-depth-2"))
      {
        ret.contents_depth[2] = utoi(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-contents-depth-3"))
      {
        ret.contents_depth[3] = utoi(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-contents-depth-4"))
      {
        ret.contents_depth[4] = utoi(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-contents-depth-5"))
      {
        ret.contents_depth[5] = utoi(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-leaf-level"))
      {
        ret.leaf_level = utoi(uadv(source->keyword));
      } else
          if (ishtmlkeyword(source->keyword, L"xhtml-leaf-smallest-contents"))
      {
        ret.leaf_smallest_contents = utoi(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-versionid"))
      {
        ret.include_version_id = utob(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-leaf-contains-contents"))
      {
        ret.leaf_contains_contents = utob(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-suppress-address"))
      {
        ret.suppress_address = utob(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-author"))
      {
        ret.author = uadv(source->keyword);
      } else if (!ustricmp(source->keyword, L"chm-toc-file"))
      {
        ret.chm_toc_file = uadv(source->keyword);
      } else if (!ustricmp(source->keyword, L"chm-ind-file"))
      {
        ret.chm_ind_file = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-description"))
      {
        ret.description = uadv(source->keyword);
      } else if (configurekeyword(&ret.html_lang, L"xhtml-lang", source)) {
      } else if (configurekeyword(&ret.meta_charset, L"xhtml-meta-charset", source)) {
      } else if (configurekeyword(&ret.head_start, L"xhtml-head-start", source)) {
      } else if (configurekeyword(&ret.head_middle, L"xhtml-head-middle", source)) {
      } else if (configurekeyword(&ret.head_end, L"xhtml-head-end", source)) {
      } else if (ishtmlkeyword(source->keyword, L"xhtml-body-start"))
      {
        ret.body_start = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-body-tag"))
      {
        ret.body = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-body-end"))
      {
        ret.body_end = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"html-append-meta"))
      {
        ustr_slist_append(&ret.meta_append, uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-address-start"))
      {
        ret.address_start = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-address-end"))
      {
        ret.address_end = uadv(source->keyword);
      } else
          if (ishtmlkeyword(source->keyword, L"xhtml-navigation-attributes"))
      {
        ret.nav_attrs = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-chapter-numeric"))
      {
        ret.fchapter.just_numbers = utob(uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-chapter-suffix"))
      {
        ustrreplacedup(&ret.fchapter.number_suffix, uadv(source->keyword));
      } else if (ishtmlkeyword(source->keyword, L"xhtml-rlink-prefix"))
      {
        ret.rlink_prefix = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-rlink-suffix"))
      {
        ret.rlink_suffix = uadv(source->keyword);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-section-numeric"))
      {
        wchar_t *p = uadv(source->keyword);
        int n = 0;
        if (uisdigit(*p))
        {
          n = utoi(p);
          p = uadv(p);
        }
        if (n >= ret.nfsect)
        {
          int i;
          ret.fsect = resize(ret.fsect, n + 1);
          for (i = ret.nfsect; i <= n; i++)
            ret.fsect[i] = ret.fsect[ret.nfsect - 1];
          ret.nfsect = n + 1;
        }
        ret.fsect[n].just_numbers = utob(p);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-section-suffix"))
      {
        wchar_t *p = uadv(source->keyword);
        int n = 0;
        if (uisdigit(*p))
        {
          n = utoi(p);
          p = uadv(p);
        }
        if (n >= ret.nfsect)
        {
          int i;
          ret.fsect = resize(ret.fsect, n + 1);
          for (i = ret.nfsect; i <= n; i++)
            ret.fsect[i] = ret.fsect[ret.nfsect - 1];
          ret.nfsect = n + 1;
        }
        ustrreplacedup(&ret.fsect[n].number_suffix, p);
      } else if (ishtmlkeyword(source->keyword, L"xhtml-keywordfragments"))
      {
        ret.keywordfragments = utob(uadv(source->keyword));
      }
    }
  }

  /*  printf(" !!! leaf_level = %i\n", ret.leaf_level);
     printf(" !!! contentdepth-0 = %i\n", ret.contents_depth[0]);
     printf(" !!! contentdepth-1 = %i\n", ret.contents_depth[1]);
     printf(" !!! contentdepth-2 = %i\n", ret.contents_depth[2]);
     printf(" !!! contentdepth-3 = %i\n", ret.contents_depth[3]);
     printf(" !!! contentdepth-4 = %i\n", ret.contents_depth[4]);
     printf(" !!! contentdepth-5 = %i\n", ret.contents_depth[5]);
     printf(" !!! leaf_contains_contents = %i\n", ret.leaf_contains_contents); */
  return ret;
}

#define element_close(rs__, tagname__) rdaddsc((rs__), "</" tagname__ ">")
#define element_open(rs__, tagname__) rdaddsc((rs__), "<" tagname__ ">")

static xhtmlsection *xhtml_new_section(xhtmlsection * last)
{
  xhtmlsection *ret = mknew(xhtmlsection);
  ret->next = NULL;
  ret->child = NULL;
  ret->parent = NULL;
  ret->chain = last;
  ret->para = NULL;
  ret->file = NULL;
  ret->fragment = NULL;
  ret->level = -1;              /* marker: end of chain */
  return ret;
}

/* Returns NULL or the section that marks that paragraph */
static xhtmlsection *xhtml_find_section(paragraph * p)
{
  xhtmlsection *ret = topsection;
  if (xhtml_para_level(p) == -1)
  {                             /* first, we back-track to a section paragraph */
    paragraph *p2 = sourceparas;
    paragraph *p3 = NULL;
    while (p2 && p2 != p)
    {
      if (xhtml_para_level(p2) != -1)
      {
        p3 = p2;
      }
      p2 = p2->next;
    }
    if (p3 == NULL)
    {                           /* for some reason, we couldn't find a section before this paragraph ... ? */
      /* Note that this can happen, if you have a cross-reference to before the first chapter starts.
       * So don't do that, then.
       */
      return NULL;
    }
    p = p3;
  }
  while (ret && ret->para != p)
  {
/*    printf(" xhtml_find_section(): checking %s for para @ %p\n", ret->fragment, p);*/
    ret = ret->chain;
  }
  return ret;
}

static xhtmlfile *xhtml_new_file(xhtmlsection * sect)
{
  xhtmlfile *ret = mknew(xhtmlfile);

  ret->next = NULL;
  ret->child = NULL;
  ret->parent = NULL;
  ret->filename = NULL;
  ret->sections = sect;
  ret->is_leaf = (sect != NULL && sect->level == conf.leaf_level);
  if (sect == NULL)
  {
    if (conf.leaf_level == 0)
    {                           /* currently unused */
#define FILENAME_MANUAL "Manual.html"
#define FILENAME_CONTENTS "Contents.html"
      ret->filename = smalloc(strlen(FILENAME_MANUAL) + 1);
      sprintf(ret->filename, FILENAME_MANUAL);
    } else
    {
      ret->filename = smalloc(strlen(FILENAME_CONTENTS) + 1);
      sprintf(ret->filename, FILENAME_CONTENTS);
    }
  } else
  {
    paragraph *p = sect->para;
    rdstringc fname_c = { 0, 0, NULL };
    char *c;
    word *w;
    for (w = (p->kwtext) ? (p->kwtext) : (p->words); w; w = w->next)
    {
      switch (removeattr(w->type))
      {
      case word_Normal:
        /*case word_Emph:
           case word_Code:
           case word_WeakCode: */
        xhtml_utostr(w->text, &c);
        rdaddsc(&fname_c, c);
        sfree(c);
        break;
      }
    }
    rdaddsc(&fname_c, ".html");
    ret->filename = rdtrimc(&fname_c);
  }
  /*  printf(" ! new file '%s', is_leaf == %s\n", ret->filename, (ret->is_leaf)?("true"):("false")); */
  return ret;
}

/*
 * Walk the tree fixing up files which are actually leaf (ie
 * have no children) but aren't at leaf level, so they have the
 * leaf flag set.
 */
void xhtml_fixup_layout(xhtmlfile * file)
{
  if (file->child == NULL)
  {
    file->is_leaf = TRUE;
  } else
  {
    xhtml_fixup_layout(file->child);
  }
  if (file->next)
    xhtml_fixup_layout(file->next);
}

/*
 * Create the tree structure so we know where everything goes.
 * Method:
 *
 * Ignoring file splitting, we have three choices with each new section:
 * 
 * +-----------------+-----------------+
 * |                 |                 |
 * X            +----X----+           (1)
 *              |         |
 *              Y        (2)
 *              |
 *             (3)
 *
 * Y is the last section we added (currentsect).
 * If sect is the section we want to add, then:
 *
 * (1) if sect->level < currentsect->level
 * (2) if sect->level == currentsect->level
 * (3) if sect->level > currentsect->level
 *
 * This requires the constraint that you never skip section numbers
 * (so you can't have a.b.c.d without all of a, a.b and a.b.c existing).
 *
 * Note that you _can_ have 1.1.1.1 followed by 1.2 - you can change
 * more than one level at a time. Lots of asserts, and probably part of
 * the algorithm here, rely on this being true. (It currently isn't
 * enforced by halibut, however.)
 *
 * File splitting makes this harder. For instance, say we added at (3)
 * above and now need to add another section. We are splitting at level
 * 2, ie the level of Y. Z is the last section we added:
 *
 * +-----------------+-----------------+
 * |                 |                 |
 * X            +----X----+           (1)
 *              |         |
 *         +----Y----+   (1)
 *         |         |
 *         Z        (2)
 *         |
 *        (3)
 *
 * The (1) case is now split; we need to search upwards to find where
 * to actually link in. The other two cases remain the same (and will
 * always be like this).
 *
 * File splitting makes this harder, however. The decision of whether
 * to split to a new file is always on the same condition, however (is
 * the level of this section higher than the leaf_level configuration
 * value or not).
 *
 * Treating the cases backwards:
 *
 * (3) same file if sect->level > conf.leaf_level, otherwise new file
 *
 *     if in the same file, currentsect->child points to sect
 *     otherwise the linking is done through the file tree (which works
 *     in more or less the same way, ie currentfile->child points to
 *     the new file)
 *
 * (2) same file if sect->level > conf.leaf_level, otherwise new file
 *
 *     if in the same file, currentsect->next points to sect
 *     otherwise file linking and currentfile->next points to the new
 *     file (we know that Z must have caused a new file to be created)
 *
 * (1) same file if sect->level > conf.leaf_level, otherwise new file
 *
 *     this is actually effectively the same case as (2) here,
 *     except that we first have to travel up the sections to figure
 *     out which section this new one will be a sibling of. In doing
 *     so, we may disappear off the top of a file and have to go up
 *     to its parent in the file tree.
 *
 */
static void xhtml_ponder_layout(paragraph * p)
{
  xhtmlsection *lastsection;
  xhtmlsection *currentsect;
  xhtmlfile *currentfile;

  lastfile = NULL;
  topsection = xhtml_new_section(NULL);
  topfile = xhtml_new_file(NULL);
  lastsection = topsection;
  currentfile = topfile;
  currentsect = topsection;

  if (conf.leaf_level == 0)
  {
    topfile->is_leaf = 1;
    topfile->sections = topsection;
    topsection->file = topfile;
  }

  for (; p; p = p->next)
  {
    int level = xhtml_para_level(p);
    if (level > 0)
    {                           /* actually a section */
      xhtmlsection *sect;
      word *w;
      char *c;
      rdstringc fname_c = { 0, 0, NULL };

      sect = xhtml_new_section(lastsection);
      lastsection = sect;
      sect->para = p;
      for (w = (p->kwtext2) ? (p->kwtext2) : (p->words); w; w = w->next)
      {                         /* kwtext2 because we want numbers only! */
        switch (removeattr(w->type))
        {
        case word_Normal:
          /*case word_Emph:
             case word_Code:
             case word_WeakCode: */
          xhtml_utostr(w->text, &c);
          rdaddsc(&fname_c, c);
          sfree(c);
          break;
        }
      }
/*      rdaddsc(&fname_c, ".html");*/
      sect->fragment = rdtrimc(&fname_c);
      sect->level = level;
      /*      printf(" ! adding para @ %p as sect %s, level %i\n", sect->para, sect->fragment, level); */

      if (level > currentsect->level)
      {                         /* case (3) */
        if (level > conf.leaf_level)
        {                       /* same file */
          assert(currentfile->is_leaf);
          currentsect->child = sect;
          sect->parent = currentsect;
          sect->file = currentfile;
          /*          printf("connected '%s' to existing file '%s' [I]\n", sect->fragment, currentfile->filename); */
          currentsect = sect;
        } else
        {                       /* new file */
          xhtmlfile *file = xhtml_new_file(sect);
          assert(!currentfile->is_leaf);
          currentfile->child = file;
          sect->file = file;
          file->parent = currentfile;
          /*          printf("connected '%s' to new file '%s' [I]\n", sect->fragment, file->filename); */
          currentfile = file;
          currentsect = sect;
        }
      } else if (level >= currentsect->file->sections->level)
      {
        /* Case (1) or (2) *AND* still under the section that starts
         * the current file.
         *
         * I'm not convinced that this couldn't be rolled in with the
         * final else {} leg further down. It seems a lot of effort
         * this way.
         */
        if (level > conf.leaf_level)
        {                       /* stick within the same file */
          assert(currentfile->is_leaf);
          sect->file = currentfile;
          while (currentsect && currentsect->level > level &&
                 currentsect->file == currentsect->parent->file)
          {
            currentsect = currentsect->parent;
          }
          assert(currentsect);
          currentsect->next = sect;
          assert(currentsect->level == sect->level);
          sect->parent = currentsect->parent;
          currentsect = sect;
          /*          printf("connected '%s' to existing file '%s' [II]\n", sect->fragment, currentfile->filename); */
        } else
        {                       /* new file */
          xhtmlfile *file = xhtml_new_file(sect);
          sect->file = file;
          currentfile->next = file;
          file->parent = currentfile->parent;
          file->is_leaf = (level == conf.leaf_level);
          file->sections = sect;
          /*          printf("connected '%s' to new file '%s' [II]\n", sect->fragment, file->filename); */
          currentfile = file;
          currentsect = sect;
        }
      } else
      {                         /* Case (1) or (2) and we must move up the file tree first */
        /* this loop is now probably irrelevant - we know we can't connect
         * to anything in the current file */
        while (currentsect && level < currentsect->level)
        {
          currentsect = currentsect->parent;
          if (currentsect)
          {
            /*            printf(" * up one level to '%s'\n", currentsect->fragment); */
          } else
          {
            /*            printf(" * up one level (off top of current file)\n"); */
          }
        }
        if (currentsect)
        {
          /* I'm pretty sure this can now never fire */
          assert(currentfile->is_leaf);
          /*          printf("connected '%s' to existing file '%s' [III]\n", sect->fragment, currentfile->filename); */
          sect->file = currentfile;
          currentsect->next = sect;
          currentsect = sect;
        } else
        {                       /* find a file we can attach to */
          while (currentfile && currentfile->sections
                 && level < currentfile->sections->level)
          {
            currentfile = currentfile->parent;
            if (currentfile)
            {
              /*              printf(" * up one file level to '%s'\n", currentfile->filename); */
            } else
            {
              /*              printf(" * up one file level (off top of tree)\n"); */
            }
          }
          if (currentfile)
          {                     /* new file (we had to skip up a file to
                                   get here, so we must be dealing with a
                                   level no lower than the configured
                                   leaf_level */
            xhtmlfile *file = xhtml_new_file(sect);
            currentfile->next = file;
            sect->file = file;
            file->parent = currentfile->parent;
            file->is_leaf = (level == conf.leaf_level);
            file->sections = sect;
            /*            printf("connected '%s' to new file '%s' [III]\n", sect->fragment, file->filename); */
            currentfile = file;
            currentsect = sect;
          } else
          {
            fatal(err_whatever,
                  "Ran off the top trying to connect sibling: strange document.");
          }
        }
      }
    }
  }
  topsection = lastsection;     /* get correct end of the chain */
  xhtml_fixup_layout(topfile);  /* leaf files not at leaf level marked as such */
}

static void xhtml_hack_xhtmlify(word*words) // "\" "<br" "\" ">" --> "\" "<br /" "\" ">"
{
  int prevwasslash = 0;
  if (!is_xhtml()) return ;
  for (;words; words = words->next)
  {
    if (!words->text) continue;
    if (prevwasslash)
    {
      if (words->text[0] == '<' && !ustricmp(words->text, L"<br") && words->next && words->next->text && words->next->text[0] == '\\')
        ustrreplacedup(&words->text, L"<br /");
    }
    else
      prevwasslash = words->text[0] == '\\' && !words->text[1];
  }
}

#define NAMEDFRAGMENT_MAXLEN 200 /* More than enough for our usage */
/*
 * Get formatted fragment name for html anchor.
 * Uses para->keyword if possible, falls back to the ?#.#.# default.
 */
static char*
xhtml_get_fragmentname(const xhtmlsection * section, char * fragmentbuf)
{
  if (conf.keywordfragments)
  {
    paragraph *para = section->para;
    if (para && para->keyword && *para->keyword) return ustrtoa(para->keyword, fragmentbuf, NAMEDFRAGMENT_MAXLEN);
  }
  return section->fragment;
}

static void xhtml_do_index();
static void xhtml_do_file(xhtmlfile * file);
static void xhtml_do_top_file(xhtmlfile * file, paragraph * sourceform);
static void xhtml_do_paras(FILE * fp, paragraph * p);
static int xhtml_do_contents_limit(FILE * fp, xhtmlfile * file, int limit);
static int xhtml_do_contents_section_limit(FILE * fp, xhtmlsection * section, int limit);
static int xhtml_add_contents_entry(FILE * fp, xhtmlsection * section, int limit);
static int xhtml_do_contents(FILE * fp, xhtmlfile * file);
static int xhtml_do_naked_contents(FILE * fp, xhtmlfile * file);
static void xhtml_do_sections(FILE * fp, xhtmlsection * sections);

/*
 * Do all the files in this structure.
 */
static void xhtml_do_files(xhtmlfile * file)
{
  xhtml_do_file(file);
  if (file->child)
    xhtml_do_files(file->child);
  if (file->next)
    xhtml_do_files(file->next);
}

/*
 * Free up all memory used by the file tree from 'xfile' downwards
 */
static void xhtml_free_file(xhtmlfile * xfile)
{
  if (xfile == NULL)
  {
    return;
  }

  if (xfile->filename)
  {
    sfree(xfile->filename);
  }
  xhtml_free_file(xfile->child);
  xhtml_free_file(xfile->next);
  sfree(xfile);
}

/*
 * Main function.
 */
void
xhtml_backend(paragraph * sourceform, keywordlist * in_keywords,
              indexdata * in_idx)
{
/*  int i;*/
  indexentry *ientry;
  int ti;
  xhtmlsection *xsect;

  sourceparas = sourceform;
  conf = xhtml_configure(sourceform);
  keywords = in_keywords;
  idx = in_idx;

  /* Clear up the index entries backend data pointers */
  for (ti = 0;
       (ientry = (indexentry *) index234(idx->entries, ti)) != NULL; ti++)
  {
    ientry->backend_data = NULL;
  }

  xhtml_ponder_layout(sourceform);

  /* old system ... (writes to *.alt, but gets some stuff wrong and is ugly) */
/*  xhtml_level_0(sourceform);
  for (i=1; i<=conf.leaf_level; i++)
  {
    xhtml_level(sourceform, i);
  }*/

  /* new system ... (writes to *.html, but isn't fully trusted) */
  xhtml_do_top_file(topfile, sourceform);
  assert(!topfile->next);       /* shouldn't have a sibling at all */
  if (topfile->child)
  {
    xhtml_do_files(topfile->child);
    xhtml_do_index();
  }

  /* release file, section, index data structures */
  xsect = topsection;
  while (xsect)
  {
    xhtmlsection *tmp = xsect->chain;
    if (xsect->fragment)
    {
      sfree(xsect->fragment);
    }
    sfree(xsect);
    xsect = tmp;
  }
  xhtml_free_file(topfile);
  for (ti = 0;
       (ientry = (indexentry *) index234(idx->entries, ti)) != NULL; ti++)
  {
    if (ientry->backend_data != NULL)
    {
      xhtmlindex *xi = (xhtmlindex *) ientry->backend_data;
      if (xi->sections != NULL)
      {
        sfree(xi->sections);
      }
      sfree(xi);
    }
    ientry->backend_data = NULL;
  }
  {
    int i;
    sfree(conf.fchapter.number_suffix);
    for (i = 0; i < conf.nfsect; i++)
      sfree(conf.fsect[i].number_suffix);
    sfree(conf.fsect);
  }
  free_ustr_slist(conf.meta_append);
}

static int xhtml_para_level(paragraph * p)
{
  switch (p->type)
  {
  case para_Title:
    return 0;
    break;
  case para_UnnumberedChapter:
  case para_Chapter:
  case para_Appendix:
    return 1;
    break;
/*  case para_BiblioCited:
    return 2;
    break;*/
  case para_Heading:
  case para_Subsect:
    return p->aux + 2;
    break;
  default:
    return -1;
    break;
  }
}

static char *xhtml_index_filename = "IndexPage.html";

/* Output the nav links for the current file.
 * file == NULL means we're doing the index
 */
static void xhtml_donavlinks(FILE * fp, xhtmlfile * file)
{
  xhtmlfile *xhtml_next_file = NULL;
  fputs("<p", fp);
  if (conf.nav_attrs != NULL)
  {
    fprintf(fp, " %ls>", conf.nav_attrs);
  } else
  {
    fputs(">", fp);
  }
  if (xhtml_last_file == NULL)
  {
    fputs("Previous | ", fp);
  } else
  {
    fprintf(fp, "<a href='%s'>Previous</a> | ", xhtml_last_file->filename);
  }
  fputs("<a href='Contents.html'>Contents</a> | ", fp);
  if (file != NULL)
  {                             /* otherwise we're doing nav links for the index */
    if (xhtml_next_file == NULL)
      xhtml_next_file = file->child;
    if (xhtml_next_file == NULL)
      xhtml_next_file = file->next;
    if (xhtml_next_file == NULL)
      xhtml_next_file = file->parent->next;
  }
  if (xhtml_next_file == NULL)
  {
    if (file == NULL)
    {                           /* index, so no next file */
      fputs("Next	", fp);
    } else
    {
      fprintf(fp, "<a href='%s'>Next</a>", xhtml_index_filename);
    }
  } else
  {
    fprintf(fp, "<a href='%s'>Next</a>", xhtml_next_file->filename);
  }
  fputs("</p>\n", fp);
}

/* Write out the index file */
static void xhtml_do_index_body(FILE * fp)
{
  indexentry *y;
  int ti;

  if (count234(idx->entries) == 0)
    return;                     /* don't write anything at all */

  fputs("<dl>\n", fp);
  /* iterate over idx->entries using the tree functions and display everything */
  for (ti = 0; (y = (indexentry *) index234(idx->entries, ti)) != NULL;
       ti++)
  {
    if (y->backend_data)
    {
      int i;
      xhtmlindex *xi;

      fputs("<dt>", fp);
      xhtml_para(fp, y->text);
      fputs("</dt>\n<dd>", fp);

      xi = (xhtmlindex *) y->backend_data;
      for (i = 0; i < xi->nsection; i++)
      {
        xhtmlsection *sect = xi->sections[i];
        if (sect)
        {
          char fragmentbuf[NAMEDFRAGMENT_MAXLEN];
          fprintf(fp, "<a href='%s#%s'>", conf.leaf_level ? sect->file->filename : "",
                  xhtml_get_fragmentname(sect, fragmentbuf));
          if (sect->para->kwtext)
          {
            xhtml_para(fp, sect->para->kwtext);
          } else if (sect->para->words)
          {
            xhtml_para(fp, sect->para->words);
          }
          fputs("</a>", fp);
          if (i + 1 < xi->nsection)
          {
            fputs(", ", fp);
          }
        }
      }
      fputs("</dd>\n", fp);
    }
  }
  fputs("</dl>\n", fp);
}
static void xhtml_do_index()
{
  word temp_word =
      { NULL, NULL, word_Normal, 0, 0, L"Index", {NULL, 0, 0} };
  FILE *fp = fopen(xhtml_index_filename, "w");

  if (fp == NULL)
    fatal(err_cantopenw, xhtml_index_filename);
  xhtml_doheader(fp, &temp_word);
  xhtml_donavlinks(fp, NULL);

  xhtml_do_index_body(fp);

  xhtml_donavlinks(fp, NULL);
  xhtml_dofooter(fp);
  fclose(fp);
}

/* Output the given file. This includes whatever contents at beginning and end, etc. etc. */
static void xhtml_do_file(xhtmlfile * file)
{
  FILE *fp = fopen(file->filename, "w");
  if (fp == NULL)
    fatal(err_cantopenw, file->filename);

  if (file->sections->para->words)
  {
    xhtml_doheader(fp, file->sections->para->words);
  } else if (file->sections->para->kwtext)
  {
    xhtml_doheader(fp, file->sections->para->kwtext);
  } else
  {
    xhtml_doheader(fp, NULL);
  }

  xhtml_donavlinks(fp, file);

  if (file->is_leaf && conf.leaf_contains_contents &&
      xhtml_do_contents(NULL, file) >= conf.leaf_smallest_contents)
    xhtml_do_contents(fp, file);
  xhtml_do_sections(fp, file->sections);
  if (!file->is_leaf)
    xhtml_do_naked_contents(fp, file);

  xhtml_donavlinks(fp, file);

  xhtml_dofooter(fp);
  fclose(fp);

  xhtml_last_file = file;
}

/* Output the top-level file. */
static void xhtml_do_top_file(xhtmlfile * file, paragraph * sourceform)
{
  paragraph *p;
  char fname[4096];
  int done = FALSE;

  FILE *fp = fopen(file->filename, "w");
  if (fp == NULL)
    fatal(err_cantopenw, file->filename);

  if (conf.chm_toc_file || conf.chm_ind_file) g_outputtype = OT_CHM | (g_outputtype & (OT_XHTML|OT_HTML5));

  ustrtoa(conf.chm_toc_file, fname, 4096);
  if(*fname)
  {
    chm_toc = fopen(fname, "w");
    if (chm_toc == NULL)
      fatal(err_cantopenw, fname);
  }
  else
    chm_toc = NULL;

  ustrtoa(conf.chm_ind_file, fname, 4096);
  if(*fname){
    chm_ind = fopen(fname, "w");
    if (chm_ind == NULL)
      fatal(err_cantopenw, fname);
  }
  else
    chm_ind = NULL;

  /* Do the title -- only one allowed */
  for (p = sourceform; p && !done; p = p->next)
  {
    if (p->type == para_Title)
    {
      xhtml_doheader(fp, p->words);
      if(chm_toc)chm_doheader(chm_toc, p->words);
      if(chm_ind)chm_doheader(chm_ind, p->words);
      done = TRUE;
    }
  }
  if (!done)
    xhtml_doheader(fp, NULL /* Eek! */ );

  /*
   * Display the title.
   */
  for (p = sourceform; p; p = p->next)
  {
    if (p->type == para_Title)
    {
      xhtml_heading(fp, p);
      break;
    }
  }

  /* Do the preamble and copyright */
  for (p = sourceform; p; p = p->next)
  {
    if (p->type == para_Preamble)
    {
      fputs("<p>", fp);
      xhtml_hack_xhtmlify(p->words);
      xhtml_para(fp, p->words);
      fputs("</p>\n", fp);
    }
  }
  for (p = sourceform; p; p = p->next)
  {
    if (p->type == para_Copyright)
    {
      fputs("<p>", fp);
      xhtml_hack_xhtmlify(p->words);
      xhtml_para(fp, p->words);
      fputs("</p>\n", fp);
    }
  }

  xhtml_do_contents(fp, file);
  xhtml_do_sections(fp, file->sections);

  /*
   * Put the index in the top file if we're in single-file mode
   * (leaf-level 0).
   */
  if (conf.leaf_level == 0 && count234(idx->entries) > 0)
  {
    fputs("<a name=\"index\"></a><h1>Index</h1>\n", fp);
    xhtml_do_index_body(fp);
  }

  xhtml_dofooter(fp);
  if(chm_toc)chm_dofooter(chm_toc);
  if(chm_ind)chm_dofooter(chm_ind);
  fclose(fp);
  if(chm_toc)
  {
    fclose(chm_toc);
    chm_toc = NULL;
  }
  if(chm_ind)
  {
    fclose(chm_ind);
    chm_ind = NULL;
  }
}

/* Convert a Unicode string to an ASCII one. '?' is
 * used for unmappable characters.
 */
static void xhtml_utostr(wchar_t * in, char **out)
{
  int l = ustrlen(in);
  int i;
  *out = smalloc(l + 1);
  for (i = 0; i < l; i++)
  {
    if (in[i] >= 32 && in[i] <= 126)
      (*out)[i] = (char) in[i];
    else
      (*out)[i] = '?';
  }
  (*out)[i] = 0;
}

/*
 * Write contents for the given file, and subfiles, down to
 * the appropriate contents depth. Returns the number of
 * entries written.
 */
static int xhtml_do_contents(FILE * fp, xhtmlfile * file)
{
  int level, limit, start_level, count = 0;
  if (!file)
    return 0;

  level = (file->sections) ? (file->sections->level) : (0);
  limit = conf.contents_depth[(level > 5) ? (5) : (level)];
  start_level = (file->is_leaf) ? (level - 1) : (level);
  last_level = start_level;

  count += xhtml_do_contents_section_limit(fp, file->sections, limit);
  count += xhtml_do_contents_limit(fp, file->child, limit);
  if (fp != NULL)
  {
    while (last_level > start_level)
    {
      last_level--;
      fputs("</ul>\n", fp);
      if(chm_toc)fprintf(chm_toc, "</ul>\n");
    }
  }
  return count;
}

/* As above, but doesn't do anything in the current file */
static int xhtml_do_naked_contents(FILE * fp, xhtmlfile * file)
{
  int level, limit, start_level, count = 0;
  if (!file)
    return 0;

  level = (file->sections) ? (file->sections->level) : (0);
  limit = conf.contents_depth[(level > 5) ? (5) : (level)];
  start_level = (file->is_leaf) ? (level - 1) : (level);
  last_level = start_level;

  count = xhtml_do_contents_limit(fp, file->child, limit);
  if (fp != NULL)
  {
    while (last_level > start_level)
    {
      last_level--;
      fputs("</ul>\n", fp);
      if(chm_toc)fprintf(chm_toc, "</ul>\n");
    }
  }
  return count;
}

/*
 * Write contents for the given file, children, and siblings, down to
 * given limit contents depth.
 */
static int xhtml_do_contents_limit(FILE * fp, xhtmlfile * file, int limit)
{
  int count = 0;
  while (file)
  {
    count += xhtml_do_contents_section_limit(fp, file->sections, limit);
    count += xhtml_do_contents_limit(fp, file->child, limit);
    file = file->next;
  }
  return count;
}

/*
 * Write contents entries for the given section tree, down to the
 * limit contents depth.
 */
static int
xhtml_do_contents_section_deep_limit(FILE * fp, xhtmlsection * section,
                                     int limit)
{
  int count = 0;
  while (section)
  {
    if (!xhtml_add_contents_entry(fp, section, limit))
      return 0;
    else
      count++;
    count +=
        xhtml_do_contents_section_deep_limit(fp, section->child, limit);
    section = section->next;
  }
  return count;
}

/*
 * Write contents entries for the given section tree, down to the
 * limit contents depth.
 */
static int
xhtml_do_contents_section_limit(FILE * fp, xhtmlsection * section, int limit)
{
  int count = 0;
  if (!section)
    return 0;
  xhtml_add_contents_entry(fp, section, limit);
  count = 1;
  count += xhtml_do_contents_section_deep_limit(fp, section->child, limit);
  /*  section=section->child;
     while (section && xhtml_add_contents_entry(fp, section, limit)) {
     section = section->next;
     } */
  return count;
}

/*
 * Add a section entry, unless we're exceeding the limit, in which
 * case return FALSE (otherwise return TRUE).
 */
static int
xhtml_add_contents_entry(FILE * fp, xhtmlsection * section, int limit)
{
  char fragmentbuf[NAMEDFRAGMENT_MAXLEN], *fragment, *filename;
  if (!section || section->level > limit)
    return FALSE;
  if (fp == NULL || section->level < 0)
    return TRUE;
  while (last_level > section->level)
  {
    last_level--;
    fputs("</ul>\n", fp);
    if(chm_toc)fprintf(chm_toc, "</ul>\n");
  }
  while (last_level < section->level)
  {
    last_level++;
    fputs("<ul>\n", fp);
    if(chm_toc)fprintf(chm_toc, "<ul>\n");
  }
  filename = conf.leaf_level ? section->file->filename : "";
  fragment = "";
  if (section->para->type != para_Chapter || !conf.leaf_level)
    fragment = xhtml_get_fragmentname(section, fragmentbuf);

  fputs("<li>", fp);
  fprintf(fp, "<a %shref=\"%s#%s\">",
          (section->para->type == para_Chapter|| section->para->type == para_Appendix) ? "class=\"btitle\" " : "",
          filename, fragment);
  if(chm_toc)fprintf(chm_toc, "<li><OBJECT type=\"text/sitemap\"><param name=\"Local\" value=\"%s#%s\"><param name=\"Name\" value=\"",
          filename, fragment);
  if(chm_ind)fprintf(chm_ind, "<li><OBJECT type=\"text/sitemap\"><param name=\"Local\" value=\"%s#%s\"><param name=\"Name\" value=\"",
          filename, fragment);
          //%s
  if (section->para->type == para_Chapter
      || section->para->type == para_Appendix)
    fputs("<b>", fp);
  if ((section->para->type != para_Heading
       && section->para->type != para_Subsect) || (section->para->kwtext
                                                   && !section->para->
                                                   words))
  {
    xhtml_para(fp, section->para->kwtext);
    if(chm_toc)xhtml_para(chm_toc, section->para->kwtext);
    if (section->para->words){
      fputs(": ", fp);
      if(chm_toc)fprintf(chm_toc, ": ");
    }
  }
  if (section->para->type == para_Chapter
      || section->para->type == para_Appendix)
    fputs("</b>", fp);
  if (section->para->words)
  {
    xhtml_para(fp, section->para->words);
    if(chm_toc)xhtml_para(chm_toc, section->para->words);
    if(chm_ind)xhtml_para(chm_ind, section->para->words);
  }
  fputs("</a></li>\n", fp);
  if(chm_toc)fprintf(chm_toc,"\"></OBJECT></li>\n");
  if(chm_ind)fprintf(chm_ind,"\"></OBJECT></li>\n");
  return TRUE;
}

/*
 * Write all the sections in this file. Do all paragraphs in this section, then all
 * children (recursively), then go on to the next one (tail recursively).
 */
static void xhtml_do_sections(FILE * fp, xhtmlsection * sections)
{
  while (sections)
  {
    currentsection = sections;
    xhtml_do_paras(fp, sections->para);
    xhtml_do_sections(fp, sections->child);
    sections = sections->next;
  }
}

/* Write this list of paragraphs. Close off all lists at the end. */
static void xhtml_do_paras(FILE * fp, paragraph * p)
{
  int last_type = -1, first = TRUE;
  if (!p)
    return;

/*  for (; p && (xhtml_para_level(p)>limit || xhtml_para_level(p)==-1 || first); p=p->next) {*/
  for (; p && (xhtml_para_level(p) == -1 || first); p = p->next)
  {
    first = FALSE;
    switch (p->type)
    {
      /*
       * Things we ignore because we've already processed them or
       * aren't going to touch them in this pass.
       */
    case para_IM:
    case para_BR:
    case para_Biblio:          /* only touch BiblioCited */
    case para_VersionID:
    case para_Copyright:
    case para_Preamble:
    case para_NoCite:
    case para_Title:
      break;

      /*
       * Chapter titles.
       */
    case para_Chapter:
    case para_Appendix:
    case para_UnnumberedChapter:
      xhtml_heading(fp, p);
      break;

    case para_Heading:
    case para_Subsect:
      xhtml_heading(fp, p);
      break;

    case para_Rule:
      fprintf(fp, "\n%s\n", gettagtxt_hr());
      break;

    case para_Normal:
      fputs("\n<p>", fp);
      xhtml_para(fp, p->words);
      fputs("</p>\n", fp);
      break;

    case para_Bullet:
    case para_NumberedList:
    case para_BiblioCited:
      if (last_type != p->type)
      {
        /* start up list if necessary */
        if (p->type == para_Bullet)
        {
          fputs("<ul>\n", fp);
        } else if (p->type == para_NumberedList)
        {
          fputs("<ol>\n", fp);
        } else if (p->type == para_BiblioCited)
        {
          fputs("<dl>\n", fp);
        }
      }
      if (p->type == para_Bullet || p->type == para_NumberedList)
        fputs("<li>", fp);
      else if (p->type == para_BiblioCited)
      {
        fputs("<dt>", fp);
        xhtml_para(fp, p->kwtext);
        fputs("</dt>\n<dd>", fp);
      }
      xhtml_para(fp, p->words);
      if (p->type == para_BiblioCited)
      {
        fputs("</dd>\n", fp);
      } else if (p->type == para_Bullet || p->type == para_NumberedList)
      {
        fputs("</li>", fp);
      }
      if (p->type == para_Bullet || p->type == para_NumberedList
          || p->type == para_BiblioCited)
        /* close off list if necessary */
      {
        paragraph *p2 = p->next;
        int close_off = FALSE;
/*          if (p2 && (xhtml_para_level(p2)>limit || xhtml_para_level(p2)==-1)) {*/
        if (p2 && xhtml_para_level(p2) == -1)
        {
          if (p2->type != p->type)
            close_off = TRUE;
        } else
        {
          close_off = TRUE;
        }
        if (close_off)
        {
          if (p->type == para_Bullet)
          {
            fputs("</ul>\n", fp);
          } else if (p->type == para_NumberedList)
          {
            fputs("</ol>\n", fp);
          } else if (p->type == para_BiblioCited)
          {
            fputs("</dl>\n", fp);
          }
        }
      }
      break;

    case para_Code:
      xhtml_codepara(fp, p->words);
      break;
    }
    last_type = p->type;
  }
}

static void printoptstr(FILE * fp, const char*prefix, const wchar_t*str, const char*suffix)
{
  if (str)
  {
    fprintf(fp, "%s%ls%s", prefix ? prefix : "", str, suffix ? suffix : "");
  }
}

/*
 * Output a header for this XHTML file.
 */
static void xhtml_doheader(FILE * fp, word * title)
{
  const int xhtml = is_xhtml(), html5 = is_html5();
  const char *xhtmldoctype = "<!DOCTYPE html PUBLIC \"-//W3C//DTD XHTML 1.0 Transitional//EN\" \"http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd\">\n";
  const char *html4doctype = "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n";
  const char *xhtmlxmlns = xhtml ? " xmlns=\"http://www.w3.org/1999/xhtml\"" : 0;
  const char *voidend = xhtml ? " /" : "";
  const wchar_t *tmpwstr;
  const ustr_slist *pussl;

  if (xhtml && html5) fatal(err_whatever, "indeterminate format");
  fputs(html5 ? "<!DOCTYPE html>\n" : xhtml ? xhtmldoctype : html4doctype, fp);
  fprintf(fp, "<html%s", xhtml ? xhtmlxmlns : "");
  //www.w3.org/International/questions/qa-html-language-declarations
  if (*(tmpwstr = ustrdef(conf.html_lang, L"")))
    fprintf(fp, "%s%ls%s lang=\"%ls\"", xhtml ? " xml:lang=\"" : "", xhtml ? tmpwstr : L"", xhtml ? "\"" : "", tmpwstr);
  fputs("><head>\n", fp);
  if (ustricmp(L"none", (tmpwstr = ustrdef(conf.meta_charset, L"UTF-8"))))
    fprintf(fp, (xhtml || !html5) ? "<meta http-equiv=\"Content-Type\" content=\"text/html; charset=%ls\"%s>" : "<meta charset=\"%ls\">\n", tmpwstr, voidend);
  printoptstr(fp, "", conf.head_start, "\n");
  fputs("<title>", fp);
  if (title == NULL)
    fputs("Documentation", fp);
  else
    xhtml_para(fp, title);
  fputs("</title>\n", fp);
  for (pussl = conf.meta_append; pussl; pussl = pussl->next)
    fprintf(fp, "<meta %ls%s>", pussl->string, voidend);
  printoptstr(fp, "", conf.head_middle, "\n");
  fprintf(fp, "<meta name=\"generator\" content=\"Halibut %s\"%s>\n", version, voidend);
  if (conf.author)
    fprintf(fp, "<meta name=\"author\" content=\"%ls\"%s>\n", conf.author, voidend);
  if (conf.description)
    fprintf(fp, "<meta name=\"description\" content=\"%ls\"%s>\n", conf.description, voidend);
  printoptstr(fp, "", conf.head_end, "\n");
  fputs("</head>\n", fp);
  fprintf(fp, "%ls\n", conf.body ? conf.body : L"<body>");
  if (conf.body_start)
    fprintf(fp, "%ls\n", conf.body_start);
}

static void chm_doheader(FILE * fp, word * title)
{
	fputs("<HTML><BODY><UL><LI><OBJECT type=\"text/sitemap\"><param name=\"Name\" value=\"", fp);
	xhtml_para(fp, title);
	fputs("\"><param name=\"Local\" value=\"Contents.html\"></OBJECT></li>\n", fp);
}

/*
 * Output a footer for this XHTML file.
 */
static void xhtml_dofooter(FILE * fp)
{
  int hr = conf.body_end || !conf.suppress_address;

  if (hr)
    fprintf(fp, "\n%s\n\n", gettagtxt_hr());

  if (conf.body_end)
    fprintf(fp, "%ls\n", conf.body_end);
  if (!conf.suppress_address)
  {
    fputs("<address>\n", fp);
    if (conf.address_start)
      fprintf(fp, "%ls\n", conf.address_start);
    /* Do the version ID */
    if (conf.include_version_id)
    {
      paragraph *p;
      int started = 0;
      for (p = sourceparas; p; p = p->next)
        if (p->type == para_VersionID)
        {
          xhtml_versionid(fp, p->words, started);
          started = 1;
        }
    }
    if (conf.address_end)
      fprintf(fp, "%ls\n", conf.address_end);
    fputs("</address>\n", fp);
  }
  fputs("</body></html>\n", fp);
}
static void chm_dofooter(FILE * fp)
{
	fputs("</ul></BODY></HTML>\n", fp);
}

/*
 * Output the versionid paragraph. Typically this is a version control
 * ID string (such as $Id...$ in RCS).
 */
static void xhtml_versionid(FILE * fp, word * text, int started)
{
  rdstringc t = { 0, 0, NULL };

  rdaddc(&t, '[');              /* FIXME: configurability */
  xhtml_rdaddwc(&t, text, NULL);
  rdaddc(&t, ']');              /* FIXME: configurability */

  if (started)
    fprintf(fp, "%s\n", gettagtxt_br());
  fprintf(fp, "%s\n", t.text);
  sfree(t.text);
}

/* Is this an XHTML reserved character? */
static int xhtml_reservedchar(int c)
{
  if (c == '&' || c == '<' || c == '>' || c == '"')
    return TRUE;
  else
    return FALSE;
}

/*
 * Convert a wide string into valid XHTML: Anything outside ASCII will
 * be fixed up as an entity. Currently we don't worry about constraining the
 * encoded character set, which we should probably do at some point (we can
 * still fix up and return FALSE - see the last comment here). We also don't
 * currently
 *
 * Because this is only used for words, spaces are HARD spaces (any other
 * spaces will be word_Whitespace not word_Normal). So they become &nbsp;
 * Unless hard_spaces is FALSE, of course (code paragraphs break the above
 * rule).
 *
 * If `result' is non-NULL, mallocs the resulting string and stores a pointer to
 * it in `*result'. If `result' is NULL, merely checks whether all
 * characters in the string are feasible.
 *
 * Return is nonzero if all characters are OK. If not all
 * characters are OK but `result' is non-NULL, a result _will_
 * still be generated!
 */
static int xhtml_convert(wchar_t * s, char **result, int hard_spaces)
{
  int doing = (result != 0);
  int ok = TRUE;
  char *p = NULL;
  int plen = 0, psize = 0;

  for (; *s; s++)
  {
    wchar_t c = *s;

#define ensure_size(i) if (i>=psize) { psize = i+256; p = resize(p, psize); }

    if (((c == 32 && !hard_spaces)
         || (c > 32 && c <= 126 && !xhtml_reservedchar(c))))
    {
      /* Char is OK. */
      if (doing)
      {
        ensure_size(plen);
        p[plen++] = (char) c;
      }
    } else
    {
      /* Char needs fixing up. */
      /* ok = FALSE; -- currently we never return FALSE; we
       * might want to when considering a character set for the
       * encoded document.
       */
      if (doing)
      {
        if (c == 32)
        {                       /* a space in a word is a hard space */
          ensure_size(plen + 7);        /* includes space for the NUL, which is subsequently stomped on */
          sprintf(p + plen, "&nbsp;");
          plen += 6;
        } else
        {
          switch (c)
          {
          case '&':
            ensure_size(plen + 6);      /* includes space for the NUL, which is subsequently stomped on */
            plen += sprintf(p + plen, "&amp;");
            break;
          case '"':
            ensure_size(plen + 7);      /* includes space for the NUL, which is subsequently stomped on */
            plen += sprintf(p + plen, "&quot;");
            break;
          case '<':
            if (plen > 1 && *(s - 1) == '\\' && *(s - 2) == '\\')
            {
              ensure_size(--plen);
              p[plen - 1] = (char) c;
              p[plen] = 0;
            } else
            {
              ensure_size(plen + 5);    /* includes space for the NUL, which is subsequently stomped on */
              plen += sprintf(p + plen, "&lt;");
            }
            break;
          case '>':
            if (plen > 1 && *(s - 1) == '\\' && *(s - 2) == '\\')
            {
              ensure_size(--plen);
              p[plen - 1] = (char) c;
              p[plen] = 0;
            } else
            {
              ensure_size(plen + 5);    /* includes space for the NUL, which is subsequently stomped on */
              plen += sprintf(p + plen, "&gt;");
            }
            break;
          default:
            ensure_size(plen + 8);      /* includes space for the NUL, which is subsequently stomped on */
            plen += sprintf(p + plen, "&#%04i;", (int) c);
            break;
          }
        }
      }
    }
  }
  if (doing)
  {
    p = resize(p, plen + 1);
    p[plen] = '\0';
    *result = p;
  }

  return ok;
}

/*
 * This formats the given words as XHTML.
 */
static void xhtml_rdaddwc(rdstringc * rs, word * text, word * end)
{
  char *c;
  keyword *kwl;
  xhtmlsection *sect;
  indextag *itag;
  int ti, style, type;
  wchar_t *s;

  for (; text && text != end; text = text->next)
  {
    switch (text->type)
    {
    case word_HyperLink:
      xhtml_utostr(text->text, &c);
      rdaddsc(rs, "<a href=\"");
      if(chm_toc && *c == '.' && *(c+1) == '.')
        rdaddsc(rs, c + 1);
      else
        rdaddsc(rs, c);
      rdaddsc(rs, "\">");
      sfree(c);
      break;

    case word_LocalHyperLink:
      xhtml_utostr(text->text, &c);
      rdaddsc(rs, "<a href=\"");
      if (conf.rlink_prefix)
      {
        char *c2;
        xhtml_utostr(conf.rlink_prefix, &c2);
        rdaddsc(rs, c2);
        sfree(c2);
      }
      rdaddsc(rs, c);
      if (conf.rlink_suffix)
      {
        char *c2;
        xhtml_utostr(conf.rlink_suffix, &c2);
        rdaddsc(rs, c2);
        sfree(c2);
      }
      rdaddsc(rs, "\">");
      sfree(c);
      break;

    case word_UpperXref:
    case word_LowerXref:
    case word_FreeTextXref:
      kwl = kw_lookup(keywords, text->text);
      if (kwl)
      {
        sect = xhtml_find_section(kwl->para);
        if (sect)
        {
          char fragmentbuf[NAMEDFRAGMENT_MAXLEN];
          rdaddsc(rs, "<a href=\"");
          rdaddsc(rs, conf.leaf_level ? sect->file->filename : "");
          rdaddc(rs, '#');
          rdaddsc(rs, xhtml_get_fragmentname(sect, fragmentbuf));
          rdaddsc(rs, "\">");
        } else
        {
          rdaddsc(rs,
                  "<a href=\"Apologies.html\"><!-- probably a bibliography cross reference -->");
          error(err_whatever,
                "Couldn't locate cross-reference! (Probably a bibliography entry.)");
        }
      } else
      {
        rdaddsc(rs,
                "<a href=\"Apologies.html\"><!-- unknown cross-reference -->");
        error(err_whatever,
              "Couldn't locate cross-reference! (Wasn't in source file.)");
      }
      break;

    case word_IndexRef:        /* in theory we could make an index target here */
/*        rdaddsc(rs, "<a name=\"idx-");
        xhtml_utostr(text->text, &c);
        rdaddsc(rs, c);
        sfree(c);
        rdaddsc(rs, "\"></a>");*/
      /* what we _do_ need to do is to fix up the backend data
       * for any indexentry this points to.
       */
      for (ti = 0;
           (itag = (indextag *) index234(idx->tags, ti)) != NULL; ti++)
      {
        /* FIXME: really ustricmp() and not ustrcmp()? */
        if (ustricmp(itag->name, text->text) == 0)
        {
          break;
        }
      }
      if (itag != NULL)
      {
        if (itag->refs != NULL)
        {
          int i;
          for (i = 0; i < itag->nrefs; i++)
          {
            xhtmlindex *idx_ref;
            indexentry *ientry;

            ientry = itag->refs[i];
            if (ientry->backend_data == NULL)
            {
              idx_ref = (xhtmlindex *) smalloc(sizeof(xhtmlindex));
              if (idx_ref == NULL)
                fatal(err_nomemory);
              idx_ref->nsection = 0;
              idx_ref->size = 4;
              idx_ref->sections =
                  (xhtmlsection **) smalloc(idx_ref->size *
                                            sizeof(xhtmlsection *));
              if (idx_ref->sections == NULL)
                fatal(err_nomemory);
              ientry->backend_data = idx_ref;
            } else
            {
              idx_ref = ientry->backend_data;
              if (idx_ref->nsection + 1 > idx_ref->size)
              {
                int new_size = idx_ref->size * 2;
                idx_ref->sections =
                    srealloc(idx_ref->sections,
                             new_size * sizeof(xhtmlsection));
                if (idx_ref->sections == NULL)
                {
                  fatal(err_nomemory);
                }
                idx_ref->size = new_size;
              }
            }
            idx_ref->sections[idx_ref->nsection++] = currentsection;
#if 0
#endif
          }
        } else
        {
          fatal(err_whatever, "Index tag had no entries!");
        }
      } else
      {
        fprintf(stderr, "Looking for index entry '%ls'\n", text->text);
        fatal(err_whatever,
              "Couldn't locate index entry! (Wasn't in index.)");
      }
      break;

    case word_HyperEnd:
    case word_XrefEnd:
      rdaddsc(rs, "</a>");
      break;

    case word_Html:
      xhtml_utostr(text->text, &c);
      rdaddsc(rs, c);
      sfree(c);
      break;

    case word_Normal:
    case word_Emph:
    case word_Strong:
    case word_Bold:
    case word_Code:
    case word_WeakCode:
    case word_WhiteSpace:
    case word_EmphSpace:
    case word_StrongSpace:
    case word_BoldSpace:
    case word_CodeSpace:
    case word_WkCodeSpace:
    case word_Quote:
    case word_EmphQuote:
    case word_StrongQuote:
    case word_BoldQuote:
    case word_CodeQuote:
    case word_WkCodeQuote:
      assert(text->type != word_CodeQuote &&
             text->type != word_WkCodeQuote);
      style = towordstyle(text->type);
      type = removeattr(text->type);

      switch((attraux(text->aux) == attr_First || attraux(text->aux) == attr_Only) ? style : word_NotWordType)
      {
      case word_Emph: element_open(rs, "em"); break;
      case word_Strong: element_open(rs, "strong"); break;
      case word_Bold: element_open(rs, "b"); break;
      case word_Code: element_open(rs, "code"); break;
      case word_WeakCode: element_open(rs, "code"); break;
      }

      if (removeattr(text->type) == word_Normal)
      {
        static int dont_convert = 0;
        if (dont_convert)
        {
          char buf[2] = " ";
          dont_convert = 0;
          s = text->text;
          for (; *s; s++)
          {
            buf[0] = (char) *s;
            rdaddsc(rs, buf);
          }
          buf[0] = 0;
          rdaddsc(rs, buf);
        } else
        {
          if (*text->text == '\\' && text->next
              && text->next->text && (*text->next->text == '&'
                                      || *text->next->text == '<'
                                      || *text->next->text == '>'
                                      || *text->next->text == '"'))
            dont_convert = 1;
          else
          {
            if (xhtml_convert(text->text, &c, TRUE))    /* spaces in the word are hard */
              rdaddsc(rs, c);
            else
              xhtml_rdaddwc(rs, text->alt, NULL);
            sfree(c);
          }
        }
      } else if (removeattr(text->type) == word_WhiteSpace)
      {
        rdaddc(rs, ' ');
      } else if (removeattr(text->type) == word_Quote)
      {
        rdaddsc(rs, "&quot;");
      }

      switch((attraux(text->aux) == attr_Last || attraux(text->aux) == attr_Only) ? style : word_NotWordType)
      {
      case word_Emph: element_close(rs, "em"); break;
      case word_Strong: element_close(rs, "strong"); break;
      case word_Bold: element_close(rs, "b"); break;
      case word_Code: element_close(rs, "code"); break;
      case word_WeakCode: element_close(rs, "code"); break;
      }
      break;
    }
  }
}

/* Output a heading, formatted as XHTML.
 */
static void xhtml_heading(FILE * fp, paragraph * p)
{
  rdstringc t = { 0, 0, NULL };
  word *tprefix = p->kwtext;
  word *nprefix = p->kwtext2;
  word *text = p->words;
  int level = xhtml_para_level(p);
  xhtmlsection *sect = xhtml_find_section(p);
  xhtmlheadfmt *fmt;
  char fragmentbuf[NAMEDFRAGMENT_MAXLEN], *fragment;
  if (sect)
  {
    fragment = xhtml_get_fragmentname(sect, fragmentbuf);
  } else
  {
    if (p->type == para_Title)
      fragment = "title";
    else
    {
      fragment = "";            /* FIXME: what else can we do? */
      error(err_whatever, "Couldn't locate heading cross-reference!");
    }
  }

  if (p->type == para_Title)
    fmt = NULL;
  else if (level == 1)
    fmt = &conf.fchapter;
  else if (level - 1 < conf.nfsect)
    fmt = &conf.fsect[level - 1];
  else
    fmt = &conf.fsect[conf.nfsect - 1];

  if (fmt && fmt->just_numbers && nprefix)
  {
    xhtml_rdaddwc(&t, nprefix, NULL);
    if (fmt)
    {
      char *c;
      if (xhtml_convert(fmt->number_suffix, &c, FALSE))
      {
        rdaddsc(&t, c);
        sfree(c);
      }
    }
  } else if (fmt && !fmt->just_numbers && tprefix)
  {
    xhtml_rdaddwc(&t, tprefix, NULL);
    if (fmt)
    {
      char *c;
      if (xhtml_convert(fmt->number_suffix, &c, FALSE))
      {
        rdaddsc(&t, c);
        sfree(c);
      }
    }
  }
  xhtml_rdaddwc(&t, text, NULL);
  /*
   * If we're outputting in single-file mode, we need to lower
   * the level of each heading by one, because the overall
   * document title will be sitting right at the top as an <h1>
   * and so chapters and sections should start at <h2>.
   * 
   * Even if not, the document title will come back from
   * xhtml_para_level() as level zero, so we must increment that
   * no matter what leaf_level is set to.
   */
  if (conf.leaf_level == 0 || level == 0)
    level++;
  fprintf(fp, "<a name=\"%s\"></a><h%i>%s</h%i>\n", fragment, level,
          t.text, level);
  sfree(t.text);
}

/* Output a paragraph. Styles are handled by xhtml_rdaddwc().
 * This looks pretty simple; I may have missed something ...
 */
static void xhtml_para(FILE * fp, word * text)
{
  rdstringc out = { 0, 0, NULL };
  xhtml_rdaddwc(&out, text, NULL);
  fprintf(fp, "%s", out.text);
  sfree(out.text);
}

/* Output a code paragraph. I'm treating this as preformatted, which
 * may not be entirely correct. See xhtml_para() for my worries about
 * this being overly-simple; however I think that most of the complexity
 * of the text backend came entirely out of word wrapping anyway.
 */
static void xhtml_codepara(FILE * fp, word * text)
{
  fputs("<pre>", fp);
  for (; text; text = text->next)
    if (text->type == word_WeakCode)
    {
      char *c;
      xhtml_convert(text->text, &c, FALSE);
      fprintf(fp, "%s\n", c);
      sfree(c);
    }
  fputs("</pre>\n", fp);
}
