shithub: riscv

ref: 29b6ad33305dc4a69940d6f636d6f129931cf9f1
dir: /sys/src/cmd/aux/antiword/xml.c/

View raw version
/*
 * 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 */