/*
* xml.c
* Copyright (C) 2002-2005 A.J. van Os; Released under GNU GPL
*
* Description:
* Functions to deal with the XML/DocBook format
*
*/

#include <string.h>
#include "antiword.h"


#define vAddEndTagsUntil1(p,t)  vAddEndTagsUntil2(p,t,TAG_NOTAG)

#if defined(DEBUG)
#define vStackTrace()   __vStackTrace(__LINE__)
#else
#define vStackTrace()   /* EMPTY */
#endif /* DEBUG */

/* The character set */
static encoding_type    eEncoding = encoding_neutral;
/* Word version */
static int      iWordVersion = -1;
/* Special treatment for files from Word 4/5/6 on an Apple Macintosh */
static BOOL     bOldMacFile = FALSE;
/* Text is emphasised */
static BOOL     bEmphasisOpen = FALSE;
/* Text is superscript */
static BOOL     bSuperscriptOpen = FALSE;
/* Text is subscript */
static BOOL     bSubscriptOpen = FALSE;
/* Title is open */
static BOOL     bTitleOpen = FALSE;
/* Table is open */
static BOOL     bTableOpen = FALSE;
/* Footnote is open */
static BOOL     bFootnoteOpen = FALSE;
/* Current paragraph level */
static UINT     uiParagraphLevel = 0;
/* Current list level */
static UINT     uiListLevel = 0;
/* Current list level is still empty */
static BOOL     bEmptyListLevel = TRUE;
/* Current header level */
static USHORT   usHeaderLevelCurrent = 0;
/* Current header level is still empty */
static BOOL     bEmptyHeaderLevel = TRUE;
/* Number of columns in the current table */
static int      iTableColumnsCurrent = 0;
/* Footnote number */
static UINT     uiFootnoteNumber = 0;

/* Constants for the stack */
#define INITIAL_STACK_SIZE      10
#if defined(DEBUG)
#define EXTENSION_STACK_SIZE     2
#else
#define EXTENSION_STACK_SIZE    10
#endif /* DEBUG */

/* Variables for the stack */
static UCHAR    *aucStack = NULL;
static size_t   tStacksize = 0;
static size_t   tStackNextFree = 0;

/* Constants for the tags */
#define TAG_NOTAG               (UCHAR)0
#define TAG_AUTHOR              (UCHAR)1
#define TAG_BEGINPAGE           (UCHAR)2
#define TAG_BOOK                (UCHAR)3
#define TAG_BOOKINFO            (UCHAR)4
#define TAG_CHAPTER             (UCHAR)5
#define TAG_COLSPEC             (UCHAR)6
#define TAG_CORPNAME            (UCHAR)7
#define TAG_DATE                (UCHAR)8
#define TAG_EMPHASIS            (UCHAR)9
#define TAG_ENTRY               (UCHAR)10
#define TAG_FILENAME            (UCHAR)11
#define TAG_FOOTNOTE            (UCHAR)12
#define TAG_INFORMALTABLE       (UCHAR)13
#define TAG_ITEMIZEDLIST        (UCHAR)14
#define TAG_LISTITEM            (UCHAR)15
#define TAG_ORDEREDLIST         (UCHAR)16
#define TAG_PARA                (UCHAR)17
#define TAG_ROW                 (UCHAR)18
#define TAG_SECT1               (UCHAR)19
#define TAG_SECT2               (UCHAR)20
#define TAG_SECT3               (UCHAR)21
#define TAG_SECT4               (UCHAR)22
#define TAG_SECT5               (UCHAR)23
#define TAG_SUBSCRIPT           (UCHAR)24
#define TAG_SUBTITLE            (UCHAR)25
#define TAG_SUPERSCRIPT         (UCHAR)26
#define TAG_SURNAME             (UCHAR)27
#define TAG_TBODY               (UCHAR)28
#define TAG_TGROUP              (UCHAR)29
#define TAG_TITLE               (UCHAR)30

typedef struct docbooktags_tag {
       UCHAR   ucTagnumber;
       char    szTagname[15];
       BOOL    bAddNewlineStart;
       BOOL    bAddNewlineEnd;
} docbooktags_type;

static const docbooktags_type atDocBookTags[] = {
       {       TAG_NOTAG,              "!ERROR!",      TRUE,   TRUE    },
       {       TAG_AUTHOR,             "author",       TRUE,   TRUE    },
       {       TAG_BEGINPAGE,          "beginpage",    TRUE,   TRUE    },
       {       TAG_BOOK,               "book",         TRUE,   TRUE    },
       {       TAG_BOOKINFO,           "bookinfo",     TRUE,   TRUE    },
       {       TAG_CHAPTER,            "chapter",      TRUE,   TRUE    },
       {       TAG_COLSPEC,            "colspec",      TRUE,   TRUE    },
       {       TAG_CORPNAME,           "corpname",     FALSE,  FALSE   },
       {       TAG_DATE,               "date",         FALSE,  FALSE   },
       {       TAG_EMPHASIS,           "emphasis",     FALSE,  FALSE   },
       {       TAG_ENTRY,              "entry",        TRUE,   TRUE    },
       {       TAG_FILENAME,           "filename",     FALSE,  FALSE   },
       {       TAG_FOOTNOTE,           "footnote",     FALSE,  FALSE   },
       {       TAG_INFORMALTABLE,      "informaltable",TRUE,   TRUE    },
       {       TAG_ITEMIZEDLIST,       "itemizedlist", TRUE,   TRUE    },
       {       TAG_LISTITEM,           "listitem",     TRUE,   TRUE    },
       {       TAG_ORDEREDLIST,        "orderedlist",  TRUE,   TRUE    },
       {       TAG_PARA,               "para",         TRUE,   TRUE    },
       {       TAG_ROW,                "row",          TRUE,   TRUE    },
       {       TAG_SECT1,              "sect1",        TRUE,   TRUE    },
       {       TAG_SECT2,              "sect2",        TRUE,   TRUE    },
       {       TAG_SECT3,              "sect3",        TRUE,   TRUE    },
       {       TAG_SECT4,              "sect4",        TRUE,   TRUE    },
       {       TAG_SECT5,              "sect5",        TRUE,   TRUE    },
       {       TAG_SUBSCRIPT,          "subscript",    FALSE,  FALSE   },
       {       TAG_SUBTITLE,           "subtitle",     FALSE,  FALSE   },
       {       TAG_SUPERSCRIPT,        "superscript",  FALSE,  FALSE   },
       {       TAG_SURNAME,            "surname",      FALSE,  FALSE   },
       {       TAG_TBODY,              "tbody",        TRUE,   TRUE    },
       {       TAG_TGROUP,             "tgroup",       TRUE,   TRUE    },
       {       TAG_TITLE,              "title",        FALSE,  FALSE   },
};

static void     vAddStartTag(diagram_type *, UCHAR, const char *);
static void     vAddEndTag(diagram_type *, UCHAR);
static void     vAddCombinedTag(diagram_type *, UCHAR, const char *);
static void     vPrintChar(diagram_type *, char);


#if defined(DEBUG)
/*
* vCheckTagTable - check the tag table
*/
static void
vCheckTagTable(void)
{
       size_t  tIndex;

       for (tIndex = 0; tIndex < elementsof(atDocBookTags); tIndex++) {
               if (tIndex != (size_t)atDocBookTags[tIndex].ucTagnumber) {
                       DBG_DEC(tIndex);
                       werr(1, "Array atDocBookTags is broken");
               }
       }
} /* end of vCheckTagTable */

/*
* __vStackTrace - show a stack trace
*/
static void
__vStackTrace(int iLine)
{
       int     iIndex;

       fprintf(stderr, "%s[%3d]:\n", __FILE__, iLine);

       if (tStackNextFree == 0) {
               fprintf(stderr, "The stack is empty\n");
               return;
       }
       for (iIndex = (int)tStackNextFree - 1; iIndex >= 0; iIndex--) {
               fprintf(stderr, "%2d: %2d: '%s'\n",
                       iIndex,
                       (int)atDocBookTags[(UINT)aucStack[iIndex]].ucTagnumber,
                       atDocBookTags[(UINT)aucStack[iIndex]].szTagname);
       }
} /* end of __vStackTrace */
#endif /* DEBUG */

/*
* vPushStack - push a tag onto the stack
*/
static void
vPushStack(UCHAR ucTag)
{
       fail(tStackNextFree > tStacksize);

       if (tStackNextFree == tStacksize) {
               /* The stack is full; enlarge the stack */
               tStacksize += EXTENSION_STACK_SIZE;
               aucStack = xrealloc(aucStack, tStacksize * sizeof(UCHAR));
               DBG_DEC(tStacksize);
       }

       fail(tStackNextFree >= tStacksize);

       aucStack[tStackNextFree++] = ucTag;
} /* end of vPushStack */

/*
* vPopStack - pop a tag from the stack
*/
static UCHAR
ucPopStack(void)
{
       DBG_DEC_C(tStackNextFree > tStacksize, tStackNextFree);
       DBG_DEC_C(tStackNextFree > tStacksize, tStacksize);
       fail(tStackNextFree > tStacksize);
       fail(tStackNextFree == 0);

       if (tStackNextFree == 0) {
               werr(1, "The stack is empty, unable to continue");
               return TAG_NOTAG;
       }
       return aucStack[--tStackNextFree];
} /* end of ucPopStack */

/*
* vReadStack - read a tag from the top of the stack
*/
static UCHAR
ucReadStack(void)
{
       DBG_DEC_C(tStackNextFree > tStacksize, tStackNextFree);
       DBG_DEC_C(tStackNextFree > tStacksize, tStacksize);
       fail(tStackNextFree > tStacksize);

       if (tStackNextFree == 0) {
               /* The stack is empty */
               return TAG_NOTAG;
       }
       return aucStack[tStackNextFree - 1];
} /* end of ucReadStack */

/*
* vPrintLevel - print the tag level
*/
static void
vPrintLevel(FILE *pOutFile)
{
       size_t  tIndex;

       fail(pOutFile == NULL);

       for (tIndex = 0; tIndex < tStackNextFree; tIndex++) {
               (void)putc(' ', pOutFile);
       }
} /* end of vPrintLevel */

/*
* vPrintFootnote - print a footnote
*/
static void
vPrintFootnote(diagram_type *pDiag, UINT uiFootnoteIndex)
{
       const char      *szText, *pcTmp;
       BOOL    bSuScript;
       UCHAR   ucTopTag;

       TRACE_MSG("vPrintFootnote");

       szText = szGetFootnootText(uiFootnoteIndex);

       if (szText == NULL) {
               szText = "";
       }

       /* Remove the subscript/superscript (if any) */
       ucTopTag = ucReadStack();
       bSuScript = ucTopTag == TAG_SUBSCRIPT || ucTopTag == TAG_SUPERSCRIPT;
       if (bSuScript) {
               vAddEndTag(pDiag, ucTopTag);
       }

       /* Start a footnote */
       vAddStartTag(pDiag, TAG_FOOTNOTE, NULL);
       vAddStartTag(pDiag, TAG_PARA, NULL);

       /* Print a footnote */
       for (pcTmp = szText; *pcTmp != '\0'; pcTmp++) {
               if (*pcTmp == PAR_END) {
                       if (*(pcTmp + 1) != PAR_END && *(pcTmp + 1) != '\0') {
                               /* PAR_END is not empty and not last */
                               vAddEndTag(pDiag, TAG_PARA);
                               vAddStartTag(pDiag, TAG_PARA, NULL);
                       }
               } else {
                       vPrintChar(pDiag, *pcTmp);
               }
       }

       /* End a footnote */
       vAddEndTag(pDiag, TAG_PARA);
       vAddEndTag(pDiag, TAG_FOOTNOTE);

       /* Repair the subscript/superscript (if any) */
       if (bSuScript) {
               vAddStartTag(pDiag, ucTopTag, NULL);
       }
} /* end of vPrintFootnote */

/*
* vPrintChar - print a character with XML encoding
*/
static void
vPrintChar(diagram_type *pDiag, char cChar)
{
       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);

       switch (cChar) {
       case FOOTNOTE_OR_ENDNOTE:
               uiFootnoteNumber++;
               vPrintFootnote(pDiag, uiFootnoteNumber - 1);
               break;
       case '<':
               fprintf(pDiag->pOutFile, "%s", "&lt;");
               break;
       case '>':
               fprintf(pDiag->pOutFile, "%s", "&gt;");
               break;
       case '&':
               fprintf(pDiag->pOutFile, "%s", "&amp;");
               break;
       default:
               (void)putc(cChar, pDiag->pOutFile);
               break;
       }
} /* end of vPrintChar */

/*
* vPrintSpecialChar - convert and print a character
*/
static void
vPrintSpecialChar(diagram_type *pDiag, USHORT usChar)
{
       ULONG   ulChar;
       size_t  tLen, tIndex;
       char    szResult[4];

       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail(iWordVersion < 0);
       fail(eEncoding == encoding_neutral);

       ulChar = ulTranslateCharacters(usChar, 0, iWordVersion,
                               conversion_xml, eEncoding, bOldMacFile);
       tLen = tUcs2Utf8(ulChar, szResult, sizeof(szResult));
       if (tLen == 1) {
               vPrintChar(pDiag, szResult[0]);
       } else {
               for (tIndex = 0; tIndex < tLen; tIndex++) {
                       (void)putc(szResult[tIndex], pDiag->pOutFile);
               }
       }
} /* end of vPrintSpecialChar */

/*
* vPrintSpecialString - convert and print a string
*/
static void
vPrintSpecialString(diagram_type *pDiag, const char *szString)
{
       int     iIndex;
       USHORT  usChar;

       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail(szString == NULL);

       for (iIndex = 0; szString[iIndex] != '\0'; iIndex++) {
               usChar = (USHORT)(UCHAR)szString[iIndex];
               vPrintSpecialChar(pDiag, usChar);
       }
} /* end of vPrintSpecialString */

/*
* vAddStartTag - add the specified start tag to the file
*/
static void
vAddStartTag(diagram_type *pDiag, UCHAR ucTag, const char *szAttribute)
{
       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail((size_t)ucTag >= elementsof(atDocBookTags));

       if (atDocBookTags[(UINT)ucTag].bAddNewlineStart) {
               fprintf(pDiag->pOutFile, "\n");
               vPrintLevel(pDiag->pOutFile);
       }

       if (szAttribute == NULL || szAttribute[0] == '\0') {
               fprintf(pDiag->pOutFile, "<%s>",
                       atDocBookTags[(UINT)ucTag].szTagname);
       } else {
               fprintf(pDiag->pOutFile, "<%s %s>",
                       atDocBookTags[(UINT)ucTag].szTagname, szAttribute);
       }

       if (atDocBookTags[(UINT)ucTag].bAddNewlineEnd) {
               fprintf(pDiag->pOutFile, "\n");
               pDiag->lXleft = 0;
       }

       vPushStack(ucTag);

       /* Set global variables */
       switch (ucTag) {
       case TAG_CHAPTER:
               usHeaderLevelCurrent = 1;
               bEmptyHeaderLevel = TRUE;
               break;
       case TAG_SECT1:
               usHeaderLevelCurrent = 2;
               bEmptyHeaderLevel = TRUE;
               break;
       case TAG_SECT2:
               usHeaderLevelCurrent = 3;
               bEmptyHeaderLevel = TRUE;
               break;
       case TAG_SECT3:
               usHeaderLevelCurrent = 4;
               bEmptyHeaderLevel = TRUE;
               break;
       case TAG_SECT4:
               usHeaderLevelCurrent = 5;
               bEmptyHeaderLevel = TRUE;
               break;
       case TAG_SECT5:
               usHeaderLevelCurrent = 6;
               bEmptyHeaderLevel = TRUE;
               break;
       case TAG_TITLE:
               fail(uiParagraphLevel != 0);
               bTitleOpen = TRUE;
               break;
       case TAG_FOOTNOTE:
               bFootnoteOpen = TRUE;
               break;
       case TAG_PARA:
               fail(bTitleOpen && !bFootnoteOpen);
               uiParagraphLevel++;
               bEmptyHeaderLevel = FALSE;
               break;
       case TAG_EMPHASIS:
               bEmphasisOpen = TRUE;
               break;
       case TAG_ITEMIZEDLIST:
       case TAG_ORDEREDLIST:
               uiListLevel++;
               bEmptyListLevel = TRUE;
               bEmptyHeaderLevel = FALSE;
               break;
       case TAG_LISTITEM:
               bEmptyListLevel = FALSE;
               break;
       case TAG_SUPERSCRIPT:
               bSuperscriptOpen = TRUE;
               break;
       case TAG_SUBSCRIPT:
               bSubscriptOpen = TRUE;
               break;
       case TAG_INFORMALTABLE:
               bTableOpen = TRUE;
               bEmptyHeaderLevel = FALSE;
               break;
       default:
               break;
       }
} /* end of vAddStartTag */

/*
* vAddEndTag - add the specified end tag to the file
*/
static void
vAddEndTag(diagram_type *pDiag, UCHAR ucTag)
{
       UCHAR   ucTopTag;

       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail((size_t)ucTag >= elementsof(atDocBookTags));

#if defined(DEBUG)
       ucTopTag = ucReadStack();
       if (ucTag != ucTopTag) {
               DBG_DEC(ucTag);
               DBG_MSG(atDocBookTags[(UINT)ucTag].szTagname);
               vStackTrace();
       }
#endif /* DEBUG */

       ucTopTag = ucPopStack();
       fail((size_t)ucTopTag >= elementsof(atDocBookTags));
       if (ucTag != ucTopTag) {
               DBG_DEC(ucTag);
               DBG_DEC(ucTopTag);
               DBG_FIXME();
               werr(1, "Impossible tag sequence, unable to continue");
       }

       if (atDocBookTags[(UINT)ucTag].bAddNewlineEnd) {
               fprintf(pDiag->pOutFile, "\n");
               vPrintLevel(pDiag->pOutFile);
       }

       fprintf(pDiag->pOutFile, "</%s>", atDocBookTags[(UINT)ucTag].szTagname);

       if (atDocBookTags[(UINT)ucTag].bAddNewlineStart) {
               fprintf(pDiag->pOutFile, "\n");
               pDiag->lXleft = 0;
       }

       /* Set global variables */
       switch (ucTag) {
       case TAG_CHAPTER:
               usHeaderLevelCurrent = 0;
               break;
       case TAG_SECT1:
               usHeaderLevelCurrent = 1;
               break;
       case TAG_SECT2:
               usHeaderLevelCurrent = 2;
               break;
       case TAG_SECT3:
               usHeaderLevelCurrent = 3;
               break;
       case TAG_SECT4:
               usHeaderLevelCurrent = 4;
               break;
       case TAG_SECT5:
               usHeaderLevelCurrent = 5;
               break;
       case TAG_TITLE:
               bTitleOpen = FALSE;
               break;
       case TAG_FOOTNOTE:
               bFootnoteOpen = FALSE;
               break;
       case TAG_PARA:
               uiParagraphLevel--;
               break;
       case TAG_EMPHASIS:
               bEmphasisOpen = FALSE;
               break;
       case TAG_SUPERSCRIPT:
               bSuperscriptOpen = FALSE;
               break;
       case TAG_ITEMIZEDLIST:
       case TAG_ORDEREDLIST:
               uiListLevel--;
               break;
       case TAG_SUBSCRIPT:
               bSubscriptOpen = FALSE;
               break;
       case TAG_INFORMALTABLE:
               bTableOpen = FALSE;
               iTableColumnsCurrent = 0;
               break;
       default:
               break;
       }
} /* end of vAddEndTag */

/*
* vAddEndTagOptional - add the specified end tag to the file if needed
*/
static void
vAddEndTagOptional(diagram_type *pDiag, UCHAR ucTag)
{
       UCHAR   ucTopTag;

       ucTopTag = ucReadStack();
       if (ucTag == ucTopTag) {
               vAddEndTag(pDiag, ucTag);
       }
} /* end of vAddEndTagOptional */

/*
* vAddCombinedTag - add the specified start and end tag to the file
*/
static void
vAddCombinedTag(diagram_type *pDiag, UCHAR ucTag, const char *szAttribute)
{
       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail((size_t)ucTag >= elementsof(atDocBookTags));

       if (atDocBookTags[(UINT)ucTag].bAddNewlineStart) {
               fprintf(pDiag->pOutFile, "\n");
               vPrintLevel(pDiag->pOutFile);
       }

       if (szAttribute == NULL || szAttribute[0] == '\0') {
               fprintf(pDiag->pOutFile, "<%s/>",
                       atDocBookTags[(UINT)ucTag].szTagname);
       } else {
               fprintf(pDiag->pOutFile, "<%s %s/>",
                       atDocBookTags[(UINT)ucTag].szTagname, szAttribute);
       }

       if (atDocBookTags[(UINT)ucTag].bAddNewlineStart) {
               fprintf(pDiag->pOutFile, "\n");
               pDiag->lXleft = 0;
       }
} /* end of vAddCombinedTag */

/*
* vAddEndTagsUntil2 - add end tags until one the specified tags is seen
*/
static void
vAddEndTagsUntil2(diagram_type *pDiag, UCHAR ucTag1, UCHAR ucTag2)
{
       UCHAR   ucTopTag;

       do {
               ucTopTag = ucReadStack();
               switch (ucTopTag) {
               case TAG_CHAPTER:
               case TAG_SECT1:
               case TAG_SECT2:
               case TAG_SECT3:
               case TAG_SECT4:
               case TAG_SECT5:
                       if (bEmptyHeaderLevel) {
                               /*
                                * An empty chapter is legal in Word,
                                * but not in DocBook.
                                */
                               vAddCombinedTag(pDiag, TAG_PARA, NULL);
                               bEmptyHeaderLevel = FALSE;
                       }
                       break;
               case TAG_ITEMIZEDLIST:
               case TAG_ORDEREDLIST:
                       if (bEmptyListLevel) {
                               /*
                                * A list without items is legal in Word,
                                * but not in DocBook. (Nor are empty items)
                                */
                               vAddStartTag(pDiag, TAG_LISTITEM, NULL);
                               vAddCombinedTag(pDiag, TAG_PARA, NULL);
                               vAddEndTag(pDiag, TAG_LISTITEM);
                               bEmptyListLevel = FALSE;
                       }
                       break;
               default:
                       break;
               }
               vAddEndTag(pDiag, ucTopTag);
       } while (ucTopTag != ucTag1 && ucTopTag != ucTag2);
} /* end of vAddEndTagsUntil2 */

/*
* vCreateBookIntro - create title and bookinfo
*/
void
vCreateBookIntro(diagram_type *pDiag, int iVersion)
{
       const char      *szTitle, *szSubject, *szAuthor;
       const char      *szLastSaveDtm, *szCompany;
       const char      *szLanguage;
       char            szTmp[13];

       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail(iVersion < 0);
       fail(eEncoding == encoding_neutral);

       iWordVersion = iVersion;
       bOldMacFile = bIsOldMacFile();
       szTitle = szGetTitle();
       szSubject = szGetSubject();
       szAuthor = szGetAuthor();
       szLastSaveDtm = szGetLastSaveDtm();
       szCompany = szGetCompany();

       /* Start Book */
       szLanguage = szGetLanguage();
       if (szLanguage != NULL) {
               DBG_MSG(szLanguage);
               sprintf(szTmp, "lang='%.5s'", szLanguage);
               szLanguage = szTmp;
       }
       vAddStartTag(pDiag, TAG_BOOK, szLanguage);

       /* Book title */
       if (szTitle != NULL && szTitle[0] != '\0') {
               vAddStartTag(pDiag, TAG_TITLE, NULL);
               vPrintSpecialString(pDiag, szTitle);
               vAddEndTag(pDiag, TAG_TITLE);
       }
       /* Bookinfo */
       if ((szTitle != NULL && szTitle[0] != '\0') ||
           (szSubject != NULL && szSubject[0] != '\0') ||
           (szAuthor != NULL && szAuthor[0] != '\0') ||
           (szLastSaveDtm != NULL && szLastSaveDtm[0] != '\0') ||
           (szCompany != NULL && szCompany[0] != '\0')) {
               vAddStartTag(pDiag, TAG_BOOKINFO, NULL);
               if (szTitle != NULL && szTitle[0] != '\0') {
                       vAddStartTag(pDiag, TAG_TITLE, NULL);
                       vPrintSpecialString(pDiag, szTitle);
                       vAddEndTag(pDiag, TAG_TITLE);
               }
               if (szSubject != NULL && szSubject[0] != '\0') {
                       vAddStartTag(pDiag, TAG_SUBTITLE, NULL);
                       vPrintSpecialString(pDiag, szSubject);
                       vAddEndTag(pDiag, TAG_SUBTITLE);
               }
               if (szAuthor != NULL && szAuthor[0] != '\0') {
                       vAddStartTag(pDiag, TAG_AUTHOR, NULL);
                       vAddStartTag(pDiag, TAG_SURNAME, NULL);
                       vPrintSpecialString(pDiag, szAuthor);
                       vAddEndTag(pDiag, TAG_SURNAME);
                       vAddEndTag(pDiag, TAG_AUTHOR);
               }
               if (szLastSaveDtm != NULL && szLastSaveDtm[0] != '\0') {
                       vAddStartTag(pDiag, TAG_DATE, NULL);
                       vPrintSpecialString(pDiag, szLastSaveDtm);
                       vAddEndTag(pDiag, TAG_DATE);
               }
               if (szCompany != NULL && szCompany[0] != '\0') {
                       vAddStartTag(pDiag, TAG_CORPNAME, NULL);
                       vPrintSpecialString(pDiag, szCompany);
                       vAddEndTag(pDiag, TAG_CORPNAME);
               }
               vAddEndTag(pDiag, TAG_BOOKINFO);
       }
} /* end of vCreateBookIntro */

/*
* vPrologueXML - perform the XML initialization
*/
void
vPrologueXML(diagram_type *pDiag, const options_type *pOptions)
{

       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail(pOptions == NULL);

#if defined(DEBUG)
       vCheckTagTable();
#endif /* DEBUG */

       /* Set global variables to their start values */
       eEncoding = pOptions->eEncoding;
       bEmphasisOpen = FALSE;
       bSuperscriptOpen = FALSE;
       bSubscriptOpen = FALSE;
       bTitleOpen = FALSE;
       bTableOpen = FALSE;
       bFootnoteOpen = FALSE;
       uiParagraphLevel = 0;
       uiListLevel = 0;
       bEmptyListLevel = TRUE;
       usHeaderLevelCurrent = 0;
       bEmptyHeaderLevel = TRUE;
       iTableColumnsCurrent = 0;
       uiFootnoteNumber = 0;

       pDiag->lXleft = 0;
       pDiag->lYtop = 0;

       /* Create an empty stack */
       tStacksize = INITIAL_STACK_SIZE;
       aucStack = xcalloc(tStacksize, sizeof(UCHAR));
       tStackNextFree = 0;
} /* end of vPrologueXML */

/*
* vEpilogueXML - clean up after everything is done
*/
void
vEpilogueXML(diagram_type *pDiag)
{
       vStackTrace();

       vAddEndTagsUntil1(pDiag, TAG_BOOK);

       vStackTrace();

       /* Destroy the stack */
       fail(tStackNextFree != 0);
       tStacksize = 0;
       aucStack = xfree(aucStack);
       tStackNextFree = 0;
} /* end of vEpilogueXML */

/*
* vPrintXML - print a XML string
*/
static void
vPrintXML(diagram_type *pDiag, const char *szString, size_t tStringLength,
               USHORT usFontstyle)
{
       const char      *szAttr;
       int     iCount;
       size_t  tNextFree;
       BOOL    bNotReady, bEmphasisNew, bSuperscriptNew, bSubscriptNew;
       UCHAR   ucTopTag, aucStorage[3];

       fail(szString == NULL);

       if (szString == NULL || szString[0] == '\0' || tStringLength == 0) {
               return;
       }

       if (tStringLength == 1 && szString[0] == FOOTNOTE_OR_ENDNOTE) {
               /* Don't do anything special for just a single footnote */
               bEmphasisNew = FALSE;
               bSuperscriptNew = FALSE;
               bSubscriptNew = FALSE;
       } else {
               /* Situation normal */
               bEmphasisNew = bIsBold(usFontstyle) ||
                               bIsItalic(usFontstyle) ||
                               bIsUnderline(usFontstyle) ||
                               bIsStrike(usFontstyle);
               bSuperscriptNew = bIsSuperscript(usFontstyle);
               bSubscriptNew = bIsSubscript(usFontstyle);
       }

       /* End what has to be ended (or more to keep the stack happy) */
       tNextFree = 0;
       bNotReady = TRUE;
       do {
               ucTopTag = ucReadStack();
               switch (ucTopTag) {
               case TAG_EMPHASIS:
                       fail(!bEmphasisOpen);
                       if (bEmphasisNew) {
                               aucStorage[tNextFree++] = ucTopTag;
                       }
                       vAddEndTag(pDiag, ucTopTag);
                       break;
               case TAG_SUPERSCRIPT:
                       fail(!bSuperscriptOpen);
                       if (bSuperscriptNew) {
                               aucStorage[tNextFree++] = ucTopTag;
                       }
                       vAddEndTag(pDiag, ucTopTag);
                       break;
               case TAG_SUBSCRIPT:
                       fail(!bSubscriptOpen);
                       if (bSubscriptNew) {
                               aucStorage[tNextFree++] = ucTopTag;
                       }
                       vAddEndTag(pDiag, ucTopTag);
                       break;
               default:
                       bNotReady = FALSE;
                       break;
               }
               fail(tNextFree > elementsof(aucStorage));
               fail(bNotReady && tNextFree == elementsof(aucStorage));
       } while (bNotReady);

       /* Just te make sure */
       vStartOfParagraphXML(pDiag, 1);

       /* Restart to keep the stack happy */
       for (iCount = (int)tNextFree - 1; iCount > 0; iCount--) {
               vAddStartTag(pDiag, aucStorage[iCount], NULL);
       }

       /* Start what has to be started */
       if (bEmphasisNew && !bEmphasisOpen) {
               if (bIsBold(usFontstyle)) {
                       szAttr = "role='bold'";
               } else if (bIsItalic(usFontstyle)) {
                       szAttr = NULL;
               } else if (bIsUnderline(usFontstyle)) {
                       szAttr = "role='underline'";
               } else if (bIsStrike(usFontstyle)) {
                       szAttr = "role='strikethrough'";
               } else {
                       szAttr = NULL;
               }
               vAddStartTag(pDiag, TAG_EMPHASIS, szAttr);
       }
       if (bSuperscriptNew && !bSuperscriptOpen) {
               vAddStartTag(pDiag, TAG_SUPERSCRIPT, NULL);
       }
       if (bSubscriptNew && !bSubscriptOpen) {
               vAddStartTag(pDiag, TAG_SUBSCRIPT, NULL);
       }

       /* The print the string */
       for (iCount = 0; iCount < (int)tStringLength; iCount++) {
               vPrintChar(pDiag, szString[iCount]);
       }
} /* end of vPrintXML */

/*
* vMove2NextLineXML - move to the next line
*/
void
vMove2NextLineXML(diagram_type *pDiag)
{
       fail(pDiag == NULL);

       /*
       if (uiParagraphLevel != 0) {
               We need something like HTML's <BR> tag
       }
       */
} /* end of vMove2NextLineXML */

/*
* vSubstringXML - put a sub string into a diagram
*/
void
vSubstringXML(diagram_type *pDiag,
       const char *szString, size_t tStringLength, long lStringWidth,
       USHORT usFontstyle)
{
       fail(pDiag == NULL || szString == NULL);
       fail(pDiag->pOutFile == NULL);
       fail(pDiag->lXleft < 0);
       fail(tStringLength != strlen(szString));

       if (szString[0] == '\0' || tStringLength == 0) {
               return;
       }

       vPrintXML(pDiag, szString, tStringLength, usFontstyle);
       pDiag->lXleft += lStringWidth;
} /* end of vSubstringXML */

/*
* Create an start of a paragraph
* Only works on paragraph level one, because Word doesn't allow paragraphs
* in paragraphs. Other paragraph levels result from DocBooks special needs.
*/
void
vStartOfParagraphXML(diagram_type *pDiag, UINT uiMaxLevel)
{
       fail(pDiag == NULL);

       if (uiParagraphLevel >= uiMaxLevel || bTitleOpen) {
               /* In Word a title is just a paragraph */
               return;
       }
       if (uiListLevel != 0 && bEmptyListLevel) {
               /* No paragraphs in a list before the first listitem */
               return;
       }
       if (usHeaderLevelCurrent == 0) {
               /* No paragraphs without an open header */
               vAddStartTag(pDiag, TAG_CHAPTER, NULL);
               /* Dummy title */
               vAddCombinedTag(pDiag, TAG_TITLE, NULL);
       }
       vAddStartTag(pDiag, TAG_PARA, NULL);
} /* end of vStartOfParagraphXML */

/*
* Create an end of a paragraph
* Only for paragraph level one and for titles
*/
void
vEndOfParagraphXML(diagram_type *pDiag, UINT uiMaxLevel)
{
       UCHAR   ucTopTag;

       fail(pDiag == NULL);

       if (uiParagraphLevel > uiMaxLevel) {
               DBG_DEC(uiParagraphLevel);
               return;
       }

       for(;;) {
               ucTopTag = ucReadStack();
               switch (ucTopTag) {
               case TAG_EMPHASIS:
                       fail(!bEmphasisOpen);
                       vAddEndTag(pDiag, TAG_EMPHASIS);
                       break;
               case TAG_SUPERSCRIPT:
                       fail(!bSuperscriptOpen);
                       vAddEndTag(pDiag, TAG_SUPERSCRIPT);
                       break;
               case TAG_SUBSCRIPT:
                       fail(!bSubscriptOpen);
                       vAddEndTag(pDiag, TAG_SUBSCRIPT);
                       break;
               case TAG_TITLE:
                       fail(!bTitleOpen);
                       vAddEndTag(pDiag, TAG_TITLE);
                       return;
               case TAG_PARA:
                       fail(uiParagraphLevel == 0);
                       vAddEndTag(pDiag, TAG_PARA);
                       return;
               case TAG_TBODY:
               case TAG_TGROUP:
               case TAG_INFORMALTABLE:
                       fail(!bTableOpen);
                       vAddEndTag(pDiag, ucTopTag);
                       break;
               case TAG_NOTAG:
                       DBG_FIXME();
                       werr(1, "Impossible tag sequence, unable to continue");
                       break;
               default:
                       DBG_DEC(ucTopTag);
                       DBG_MSG_C((size_t)ucTopTag < elementsof(atDocBookTags),
                               atDocBookTags[(UINT)ucTopTag].szTagname);
                       return;
               }
       }
} /* end of vEndOfParagraphXML */

/*
* Create an end of a page
*/
void
vEndOfPageXML(diagram_type *pDiag)
{
       if (bTableOpen || usHeaderLevelCurrent == 0) {
               /* No beginpage in a table or outside a chapter */
               return;
       }
       if (bTitleOpen) {
               /* A beginpage is not allowed when in a title */
               /* So start a new paragraph */
               vEndOfParagraphXML(pDiag, UINT_MAX);
               vStartOfParagraphXML(pDiag, UINT_MAX);
               return;
       }
       vAddCombinedTag(pDiag, TAG_BEGINPAGE, NULL);
} /* end of vEndOfPageXML */

/*
* vCloseHeaderLevels - close the specified header levels
*/
static void
vCloseHeaderLevels(diagram_type *pDiag, USHORT usIstd)
{
       BOOL    bNotReady;
       UCHAR   ucTopTag;

       DBG_MSG("vCloseHeaderLevels");
       DBG_DEC(usIstd);
       DBG_DEC(usHeaderLevelCurrent);

       vStackTrace();

       bNotReady = TRUE;
       do {
               ucTopTag = ucReadStack();
               switch (ucTopTag) {
               case TAG_TITLE:
               case TAG_PARA:
                       vAddEndTag(pDiag, ucTopTag);
                       break;
               default:
                       bNotReady = FALSE;
                       break;
               }
       } while (bNotReady);

       vStackTrace();

       while (usHeaderLevelCurrent >= usIstd) {
               if (bEmptyHeaderLevel) {
                       vAddCombinedTag(pDiag, TAG_PARA, NULL);
                       bEmptyHeaderLevel = FALSE;
               }
               switch (usHeaderLevelCurrent) {
               case 1: vAddEndTag(pDiag, TAG_CHAPTER); break;
               case 2: vAddEndTag(pDiag, TAG_SECT1); break;
               case 3: vAddEndTag(pDiag, TAG_SECT2); break;
               case 4: vAddEndTag(pDiag, TAG_SECT3); break;
               case 5: vAddEndTag(pDiag, TAG_SECT4); break;
               case 6: vAddEndTag(pDiag, TAG_SECT5); break;
               default:
                       DBG_DEC(usHeaderLevelCurrent);
                       DBG_FIXME();
                       return;
               }
       }

       DBG_DEC(usHeaderLevelCurrent);

       vStackTrace();
} /* end of vCloseHeaderLevels */

/*
* vSetHeadersXML - set the headers
*/
void
vSetHeadersXML(diagram_type *pDiag, USHORT usIstd)
{
       fail(pDiag == NULL);

       if (usIstd == 0 || usIstd > 6) {
               DBG_DEC_C(usIstd != 0 && usIstd <= 9, usIstd);
               return;
       }
       DBG_DEC(usIstd);

       if (bTableOpen || uiListLevel != 0) {
               /* No headers when you're in a table or in a list */
               return;
       }

       /* Close levels */
       vCloseHeaderLevels(pDiag, usIstd);

       DBG_DEC(usHeaderLevelCurrent);

       /* Open levels */
       while (usHeaderLevelCurrent < usIstd) {
               switch (usHeaderLevelCurrent) {
               case 0: vAddStartTag(pDiag, TAG_CHAPTER, NULL); break;
               case 1: vAddStartTag(pDiag, TAG_SECT1, NULL); break;
               case 2: vAddStartTag(pDiag, TAG_SECT2, NULL); break;
               case 3: vAddStartTag(pDiag, TAG_SECT3, NULL); break;
               case 4: vAddStartTag(pDiag, TAG_SECT4, NULL); break;
               case 5: vAddStartTag(pDiag, TAG_SECT5, NULL); break;
               default:
                       DBG_DEC(usHeaderLevelCurrent);
                       DBG_FIXME();
                       return;
               }
               fail(usIstd == 0);
               /* The next paragraph should be a title */
               if (usHeaderLevelCurrent < usIstd) {
                       /* This chapter level is not in the Word document */
                       vAddCombinedTag(pDiag, TAG_TITLE, NULL);
               } else {
                       vAddStartTag(pDiag, TAG_TITLE, NULL);
               }
       }
} /* end of vSetHeadersXML */

/*
* Create a start of a list
*/
void
vStartOfListXML(diagram_type *pDiag, UCHAR ucNFC, BOOL bIsEndOfTable)
{
       const char      *szAttr;
       UCHAR           ucTag;

       fail(pDiag == NULL);

       if (bIsEndOfTable) {
               /* FIXME: until a list in a table is allowed */
               vEndOfTableXML(pDiag);
       }

       if (bTableOpen) {
               /* FIXME: a list in a table should be allowed */
               return;
       }

       if (usHeaderLevelCurrent == 0) {
               /* No list without an open header */
               vAddStartTag(pDiag, TAG_CHAPTER, NULL);
               /* Dummy title */
               vAddCombinedTag(pDiag, TAG_TITLE, NULL);
       }

       switch (ucNFC) {
       case LIST_ARABIC_NUM:
       case LIST_ORDINAL_NUM:
       case LIST_NUMBER_TXT:
       case LIST_ORDINAL_TXT:
       case LIST_OUTLINE_NUM:
               ucTag = TAG_ORDEREDLIST;
               szAttr = "numeration='arabic'";
               break;
       case LIST_UPPER_ROMAN:
               ucTag = TAG_ORDEREDLIST;
               szAttr = "numeration='upperroman'";
               break;
       case LIST_LOWER_ROMAN:
               ucTag = TAG_ORDEREDLIST;
               szAttr = "numeration='lowerroman'";
               break;
       case LIST_UPPER_ALPHA:
               ucTag = TAG_ORDEREDLIST;
               szAttr = "numeration='upperalpha'";
               break;
       case LIST_LOWER_ALPHA:
               ucTag = TAG_ORDEREDLIST;
               szAttr = "numeration='loweralpha'";
               break;
       case LIST_SPECIAL:
       case LIST_SPECIAL2:
       case LIST_BULLETS:
               ucTag = TAG_ITEMIZEDLIST;
               szAttr = "mark='bullet'";
               break;
       default:
               ucTag = TAG_ORDEREDLIST;
               szAttr = "numeration='arabic'";
               DBG_HEX(ucNFC);
               DBG_FIXME();
               break;
       }
       vAddStartTag(pDiag, ucTag, szAttr);
} /* end of vStartOfListXML */

/*
* Create an end of a list
*/
void
vEndOfListXML(diagram_type *pDiag)
{
       fail(pDiag == NULL);

       if (bTableOpen) {
               /* FIXME: a list in a table should be allowed */
               return;
       }

       if (uiListLevel != 0) {
               vStackTrace();
               vAddEndTagsUntil2(pDiag, TAG_ITEMIZEDLIST, TAG_ORDEREDLIST);
               vStackTrace();
       }
} /* end of vEndOfListXML */

/*
* Create a start of a list item
*/
void
vStartOfListItemXML(diagram_type *pDiag, BOOL bNoMarks)
{
       const char      *szAttr;
       UCHAR   ucTopTag;

       fail(pDiag == NULL);

       if (bTableOpen) {
               /* FIXME: a list in a table should be allowed */
               return;
       }

       ucTopTag = ucReadStack();
       if (ucTopTag != TAG_ITEMIZEDLIST && ucTopTag != TAG_ORDEREDLIST) {
               /* Must end a previous list item first */
               vAddEndTagsUntil1(pDiag, TAG_LISTITEM);
       }

       DBG_DEC_C(ucReadStack() != TAG_ITEMIZEDLIST &&
               ucReadStack() != TAG_ORDEREDLIST, ucReadStack());

       /* Start a new list item */
       szAttr = bNoMarks ? "override='none'" : NULL;
       vAddStartTag(pDiag, TAG_LISTITEM, szAttr);
       /* Start a new paragraph (independant of level) */
       vAddStartTag(pDiag, TAG_PARA, NULL);
} /* end of vStartOfListItemXML */

/*
* Create a start of a table
*/
static void
vStartOfTable(diagram_type *pDiag, UCHAR ucBorderInfo)
{
       const char      *szFrame;
       BOOL    bNotReady;
       UCHAR   ucTopTag;
       char    cColSep, cRowSep;
       char    szAttr[40];

       fail(pDiag == NULL);

       /* Close elements that cannot contain a table */
       bNotReady = TRUE;
       do {
               ucTopTag = ucReadStack();
               switch (ucTopTag) {
               case TAG_TITLE:
                       fail(!bTitleOpen);
                       vAddEndTag(pDiag, TAG_TITLE);
                       break;
               case TAG_EMPHASIS:
                       fail(!bEmphasisOpen);
                       vAddEndTag(pDiag, TAG_EMPHASIS);
                       break;
               case TAG_SUPERSCRIPT:
                       fail(!bSuperscriptOpen);
                       vAddEndTag(pDiag, TAG_SUPERSCRIPT);
                       break;
               case TAG_SUBSCRIPT:
                       fail(!bSubscriptOpen);
                       vAddEndTag(pDiag, TAG_SUBSCRIPT);
                       break;
               default:
                       bNotReady = FALSE;
                       break;
               }
       } while (bNotReady);

       /* Create table attributes */
       switch (ucBorderInfo) {
       case TABLE_BORDER_TOP:
               szFrame = "top";
               break;
       case TABLE_BORDER_LEFT|TABLE_BORDER_RIGHT:
               szFrame = "sides";
               break;
       case TABLE_BORDER_TOP|TABLE_BORDER_BOTTOM:
               szFrame = "topbot";
               break;
       case TABLE_BORDER_BOTTOM:
               szFrame = "bottom";
               break;
       case TABLE_BORDER_TOP|TABLE_BORDER_LEFT|
            TABLE_BORDER_BOTTOM|TABLE_BORDER_RIGHT:
               szFrame = "all";
               break;
       default:
               szFrame = "none";
               break;
       }
       cColSep = bIsTableBorderLeft(ucBorderInfo) ||
                 bIsTableBorderRight(ucBorderInfo) ? '1' : '0';
       cRowSep = bIsTableBorderTop(ucBorderInfo) ||
                 bIsTableBorderBottom(ucBorderInfo) ? '1' : '0';

       sprintf(szAttr, "frame='%.6s' colsep='%c' rowsep='%c'",
                       szFrame, cColSep, cRowSep);

       if (usHeaderLevelCurrent == 0) {
               /* No table without an open header */
               vAddStartTag(pDiag, TAG_CHAPTER, NULL);
               /* Dummy title */
               vAddCombinedTag(pDiag, TAG_TITLE, NULL);
       }
       vAddStartTag(pDiag, TAG_INFORMALTABLE, szAttr);
} /* end of vStartOfTable */

/*
* Create a start of a table group
*/
static void
vStartOfTableGroup(diagram_type *pDiag,
       int iNbrOfColumns, const short *asColumnWidth)
{
       double  dWidth;
       int     iIndex;
       char    szCols[6 + 3 * sizeof(int) + 1 + 1];
       char    szColWidth[10 + 3 * sizeof(short) + 3 + 3 + 1];

       fail(iNbrOfColumns < 1);
       fail(asColumnWidth == NULL);

       sprintf(szCols, "cols='%d'", iNbrOfColumns);
       vAddStartTag(pDiag, TAG_TGROUP, szCols);

       for (iIndex= 0; iIndex < iNbrOfColumns; iIndex++) {
               fail(asColumnWidth[iIndex] < 0);
               dWidth = dTwips2Points(asColumnWidth[iIndex]);
               if (dWidth <= 1.0) {
                       strcpy(szColWidth, "colwidth='1.00pt'");
               } else {
                       sprintf(szColWidth, "colwidth='%.2fpt'", dWidth);
               }
               vAddCombinedTag(pDiag, TAG_COLSPEC, szColWidth);
       }
} /* end of vStartOfTableGroup */

/*
* Create an end of a table
*/
void
vEndOfTableXML(diagram_type *pDiag)
{
       fail(pDiag == NULL);

       if (bTableOpen) {
               vAddEndTag(pDiag, TAG_TBODY);
               vAddEndTag(pDiag, TAG_TGROUP);
               vAddEndTag(pDiag, TAG_INFORMALTABLE);
       }
} /* end of vEndOfTableXML */

/*
* Add a table row
*/
void
vAddTableRowXML(diagram_type *pDiag, char **aszColTxt,
       int iNbrOfColumns, const short *asColumnWidth, UCHAR ucBorderInfo)
{
       size_t  tCount, tStringLength;
       int     iIndex;

       fail(pDiag == NULL);
       fail(pDiag->pOutFile == NULL);
       fail(aszColTxt == NULL);
       fail(iNbrOfColumns < 1);
       fail(asColumnWidth == NULL);

       if (iNbrOfColumns != iTableColumnsCurrent) {
               /* A new number of columns */
               /* End the old table body and table group (if they exist) */
               vAddEndTagOptional(pDiag, TAG_TBODY);
               vAddEndTagOptional(pDiag, TAG_TGROUP);
               if (!bTableOpen) {
                       /* No table yet. Start a new table */
                       vStartOfTable(pDiag, ucBorderInfo);
               }
               /* Start a new table group and a new table body */
               vStartOfTableGroup(pDiag, iNbrOfColumns, asColumnWidth);
               vAddStartTag(pDiag, TAG_TBODY, NULL);
               iTableColumnsCurrent = iNbrOfColumns;
       }

       /* Add the table row */
       vAddStartTag(pDiag, TAG_ROW, NULL);
       for (iIndex = 0; iIndex < iNbrOfColumns; iIndex++) {
               /* Add a table cell */
               fail(aszColTxt[iIndex] == NULL);
               vAddStartTag(pDiag, TAG_ENTRY, NULL);
               tStringLength = strlen(aszColTxt[iIndex]);
               for (tCount = 0; tCount < tStringLength; tCount++) {
                       vPrintChar(pDiag, aszColTxt[iIndex][tCount]);
               }
               vAddEndTag(pDiag, TAG_ENTRY);
       }
       vAddEndTag(pDiag, TAG_ROW);
} /* end of vAddTableRowXML */