xref: /plan9/sys/src/cmd/aux/antiword/xml.c (revision 9b943567965ba040fd275927fbe088656eb8ce4f)
1 /*
2  * xml.c
3  * Copyright (C) 2002,2003 A.J. van Os; Released under GNU GPL
4  *
5  * Description:
6  * Functions to deal with the XML/DocBook format
7  *
8  */
9 
10 #include <string.h>
11 #include "antiword.h"
12 
13 
14 #define vAddEndTagsUntil1(p,t)	vAddEndTagsUntil2(p,t,TAG_NOTAG)
15 
16 #if defined(DEBUG)
17 #define vStackTrace()	__vStackTrace(__LINE__)
18 #else
19 #define vStackTrace()	/* EMPTY */
20 #endif /* DEBUG */
21 
22 /* Text is emphasised */
23 static BOOL	bEmphasisOpen = FALSE;
24 /* Text is superscript */
25 static BOOL	bSuperscriptOpen = FALSE;
26 /* Text is subscript */
27 static BOOL	bSubscriptOpen = FALSE;
28 /* Title is open */
29 static BOOL	bTitleOpen = FALSE;
30 /* Table is open */
31 static BOOL	bTableOpen = FALSE;
32 /* Current paragraph level */
33 static UINT	uiParagraphLevel = 0;
34 /* Current list level */
35 static UINT	uiListLevel = 0;
36 /* Current list level is still empty */
37 static BOOL	bEmptyListLevel = TRUE;
38 /* Current header level */
39 static USHORT	usHeaderLevelCurrent = 0;
40 /* Current header level is still empty */
41 static BOOL	bEmptyHeaderLevel = TRUE;
42 /* Number of columns in the current table */
43 static int	iTableColumnsCurrent = 0;
44 
45 /* Constants for the stack */
46 #define INITIAL_STACK_SIZE	10
47 #if defined(DEBUG)
48 #define EXTENSION_STACK_SIZE	 2
49 #else
50 #define EXTENSION_STACK_SIZE	10
51 #endif /* DEBUG */
52 
53 /* Variables for the stack */
54 static UCHAR	*aucStack = NULL;
55 static size_t	tStacksize = 0;
56 static size_t	tStackNextFree = 0;
57 
58 /* Constants for the tags */
59 #define TAG_NOTAG		(UCHAR)0
60 #define TAG_AUTHOR		(UCHAR)1
61 #define TAG_BEGINPAGE		(UCHAR)2
62 #define TAG_BOOK		(UCHAR)3
63 #define TAG_BOOKINFO		(UCHAR)4
64 #define TAG_CHAPTER		(UCHAR)5
65 #define TAG_COLSPEC		(UCHAR)6
66 #define TAG_CORPNAME		(UCHAR)7
67 #define TAG_DATE		(UCHAR)8
68 #define TAG_EMPHASIS		(UCHAR)9
69 #define TAG_ENTRY		(UCHAR)10
70 #define TAG_FILENAME		(UCHAR)11
71 #define TAG_INFORMALTABLE	(UCHAR)12
72 #define TAG_ITEMIZEDLIST	(UCHAR)13
73 #define TAG_LISTITEM		(UCHAR)14
74 #define TAG_ORDEREDLIST		(UCHAR)15
75 #define TAG_PARA		(UCHAR)16
76 #define TAG_ROW			(UCHAR)17
77 #define TAG_SECT1		(UCHAR)18
78 #define TAG_SECT2		(UCHAR)19
79 #define TAG_SECT3		(UCHAR)20
80 #define TAG_SECT4		(UCHAR)21
81 #define TAG_SECT5		(UCHAR)22
82 #define TAG_SUBSCRIPT		(UCHAR)23
83 #define TAG_SUBTITLE		(UCHAR)24
84 #define TAG_SUPERSCRIPT		(UCHAR)25
85 #define TAG_SURNAME		(UCHAR)26
86 #define TAG_TBODY		(UCHAR)27
87 #define TAG_TGROUP		(UCHAR)28
88 #define TAG_TITLE		(UCHAR)29
89 
90 typedef struct docbooktags_tag {
91 	UCHAR	ucTagnumber;
92 	char	szTagname[15];
93 	BOOL	bAddNewline;
94 } docbooktags_type;
95 
96 static const docbooktags_type atDocBookTags[] = {
97 	{	TAG_NOTAG, 		"!ERROR!",	TRUE	},
98 	{	TAG_AUTHOR,		"author",	TRUE	},
99 	{	TAG_BEGINPAGE,		"beginpage",	TRUE	},
100 	{	TAG_BOOK, 		"book",		TRUE	},
101 	{	TAG_BOOKINFO, 		"bookinfo",	TRUE	},
102 	{	TAG_CHAPTER, 		"chapter",	TRUE	},
103 	{	TAG_COLSPEC,		"colspec",	TRUE	},
104 	{	TAG_CORPNAME,		"corpname",	FALSE	},
105 	{	TAG_DATE,		"date",		FALSE	},
106 	{	TAG_EMPHASIS,		"emphasis",	FALSE	},
107 	{	TAG_ENTRY,		"entry",	TRUE	},
108 	{	TAG_FILENAME,		"filename",	FALSE	},
109 	{	TAG_INFORMALTABLE,	"informaltable",TRUE	},
110 	{	TAG_ITEMIZEDLIST,	"itemizedlist",	TRUE	},
111 	{	TAG_LISTITEM,		"listitem",	TRUE	},
112 	{	TAG_ORDEREDLIST,	"orderedlist",	TRUE	},
113 	{	TAG_PARA, 		"para",		TRUE	},
114 	{	TAG_ROW,		"row",		TRUE	},
115 	{	TAG_SECT1, 		"sect1",	TRUE	},
116 	{	TAG_SECT2, 		"sect2",	TRUE	},
117 	{	TAG_SECT3, 		"sect3",	TRUE	},
118 	{	TAG_SECT4, 		"sect4",	TRUE	},
119 	{	TAG_SECT5, 		"sect5",	TRUE	},
120 	{	TAG_SUBSCRIPT,		"subscript",	FALSE	},
121 	{	TAG_SUBTITLE,		"subtitle",	FALSE	},
122 	{	TAG_SUPERSCRIPT,	"superscript",	FALSE	},
123 	{	TAG_SURNAME,		"surname",	FALSE	},
124 	{	TAG_TBODY,		"tbody",	TRUE	},
125 	{	TAG_TGROUP,		"tgroup",	TRUE	},
126 	{	TAG_TITLE, 		"title",	FALSE	},
127 };
128 
129 
130 #if defined(DEBUG)
131 /*
132  * vCheckTagTable - check the tag table
133  */
134 static void
135 vCheckTagTable(void)
136 {
137 	size_t	tIndex;
138 
139 	for (tIndex = 0; tIndex < elementsof(atDocBookTags); tIndex++) {
140 		if (tIndex != (size_t)atDocBookTags[tIndex].ucTagnumber) {
141 			DBG_DEC(tIndex);
142 			werr(1, "Array atDocBookTags is broken");
143 		}
144 	}
145 } /* end of vCheckTagTable */
146 
147 /*
148  * __vStackTrace - show a stack trace
149  */
150 static void
151 __vStackTrace(int iLine)
152 {
153 	int	iIndex;
154 
155 	fprintf(stderr, "%s[%3d]:\n", __FILE__, iLine);
156 
157 	if (tStackNextFree == 0) {
158 		fprintf(stderr, "The stack is empty\n");
159 		return;
160 	}
161 	for (iIndex = (int)tStackNextFree - 1; iIndex >= 0; iIndex--) {
162 		fprintf(stderr, "%2d: %2d: '%s'\n",
163 			iIndex,
164 			(int)atDocBookTags[(UINT)aucStack[iIndex]].ucTagnumber,
165 			atDocBookTags[(UINT)aucStack[iIndex]].szTagname);
166 	}
167 } /* end of __vStackTrace */
168 #endif /* DEBUG */
169 
170 /*
171  * vPushStack - push a tag onto the stack
172  */
173 static void
174 vPushStack(UCHAR ucTag)
175 {
176 	fail(tStackNextFree > tStacksize);
177 
178 	if (tStackNextFree == tStacksize) {
179 		/* The stack is full; enlarge the stack */
180 		tStacksize += EXTENSION_STACK_SIZE;
181 		aucStack = xrealloc(aucStack, tStacksize * sizeof(UCHAR));
182 		DBG_DEC(tStacksize);
183 	}
184 
185 	fail(tStackNextFree >= tStacksize);
186 
187 	aucStack[tStackNextFree++] = ucTag;
188 } /* end of vPushStack */
189 
190 /*
191  * vPopStack - pop a tag from the stack
192  */
193 static UCHAR
194 ucPopStack(void)
195 {
196 	DBG_DEC_C(tStackNextFree > tStacksize, tStackNextFree);
197 	DBG_DEC_C(tStackNextFree > tStacksize, tStacksize);
198 	fail(tStackNextFree > tStacksize);
199 	fail(tStackNextFree == 0);
200 
201 	if (tStackNextFree == 0) {
202 		werr(1, "The stack is empty, unable to continue");
203 		return TAG_NOTAG;
204 	}
205 	return aucStack[--tStackNextFree];
206 } /* end of ucPopStack */
207 
208 /*
209  * vReadStack - read a tag from the top of the stack
210  */
211 static UCHAR
212 ucReadStack(void)
213 {
214 	DBG_DEC_C(tStackNextFree > tStacksize, tStackNextFree);
215 	DBG_DEC_C(tStackNextFree > tStacksize, tStacksize);
216 	fail(tStackNextFree > tStacksize);
217 
218 	if (tStackNextFree == 0) {
219 		/* The stack is empty */
220 		return TAG_NOTAG;
221 	}
222 	return aucStack[tStackNextFree - 1];
223 } /* end of ucReadStack */
224 
225 /*
226  * vPrintLevel - print the tag level
227  */
228 static void
229 vPrintLevel(FILE *pOutFile)
230 {
231 	size_t	tIndex;
232 
233 	fail(pOutFile == NULL);
234 
235 	for (tIndex = 0; tIndex < tStackNextFree; tIndex++) {
236 		(void)putc(' ', pOutFile);
237 	}
238 } /* end of vPrintLevel */
239 
240 /*
241  * vPrintChar - print a character with XML encoding
242  */
243 static void
244 vPrintChar(FILE *pOutFile, char cChar)
245 {
246 	switch (cChar) {
247 	case '<':
248 		fprintf(pOutFile, "%s", "&lt;");
249 		break;
250 	case '>':
251 		fprintf(pOutFile, "%s", "&gt;");
252 		break;
253 	case '&':
254 		fprintf(pOutFile, "%s", "&amp;");
255 		break;
256 	default:
257 		(void)putc(cChar, pOutFile);
258 		break;
259 	}
260 } /* end of vPrintChar */
261 
262 /*
263  * vPrintSpecial
264  */
265 static void
266 vPrintSpecial(FILE *pOutFile, const char *szString,
267 	int iWordVersion, encoding_type eEncoding)
268 {
269 	ULONG	ulChar;
270 	size_t	tLen, tIndex;
271 	int	iIndex;
272 	BOOL	bOldMacFile;
273 	USHORT	usChar;
274 	char	szResult[4];
275 
276 	fail(pOutFile == NULL);
277 	fail(szString == NULL);
278 
279 	bOldMacFile = bIsOldMacFile();
280 
281 	for (iIndex = 0; szString[iIndex] != '\0'; iIndex++) {
282 		usChar = (USHORT)(UCHAR)szString[iIndex];
283 		ulChar = ulTranslateCharacters(usChar, 0, iWordVersion,
284 				conversion_xml, eEncoding, bOldMacFile);
285 		tLen = tUcs2Utf8(ulChar, szResult, sizeof(szResult));
286 		if (tLen == 1) {
287 			vPrintChar(pOutFile, szResult[0]);
288 		} else {
289 			for (tIndex = 0; tIndex < tLen; tIndex++) {
290 				(void)putc(szResult[tIndex], pOutFile);
291 			}
292 		}
293 	}
294 } /* end of vPrintSpecial */
295 
296 /*
297  * vAddStartTag - add the specified start tag to the file
298  */
299 static void
300 vAddStartTag(diagram_type *pDiag, UCHAR ucTag, const char *szAttribute)
301 {
302 	fail(pDiag == NULL);
303 	fail(pDiag->pOutFile == NULL);
304 	fail((size_t)ucTag >= elementsof(atDocBookTags));
305 
306 	if (atDocBookTags[(UINT)ucTag].bAddNewline) {
307 		fprintf(pDiag->pOutFile, "\n");
308 		vPrintLevel(pDiag->pOutFile);
309 	}
310 
311 	if (szAttribute == NULL || szAttribute[0] == '\0') {
312 		fprintf(pDiag->pOutFile, "<%s>",
313 			atDocBookTags[(UINT)ucTag].szTagname);
314 	} else {
315 		fprintf(pDiag->pOutFile, "<%s %s>",
316 			atDocBookTags[(UINT)ucTag].szTagname, szAttribute);
317 	}
318 
319 	if (atDocBookTags[(UINT)ucTag].bAddNewline) {
320 		fprintf(pDiag->pOutFile, "\n");
321 		pDiag->lXleft = 0;
322 	}
323 
324 	vPushStack(ucTag);
325 
326 	/* Set global variables */
327 	switch (ucTag) {
328 	case TAG_CHAPTER:
329 		usHeaderLevelCurrent = 1;
330 		bEmptyHeaderLevel = TRUE;
331 		break;
332 	case TAG_SECT1:
333 		usHeaderLevelCurrent = 2;
334 		bEmptyHeaderLevel = TRUE;
335 		break;
336 	case TAG_SECT2:
337 		usHeaderLevelCurrent = 3;
338 		bEmptyHeaderLevel = TRUE;
339 		break;
340 	case TAG_SECT3:
341 		usHeaderLevelCurrent = 4;
342 		bEmptyHeaderLevel = TRUE;
343 		break;
344 	case TAG_SECT4:
345 		usHeaderLevelCurrent = 5;
346 		bEmptyHeaderLevel = TRUE;
347 		break;
348 	case TAG_SECT5:
349 		usHeaderLevelCurrent = 6;
350 		bEmptyHeaderLevel = TRUE;
351 		break;
352 	case TAG_TITLE:
353 		fail(uiParagraphLevel != 0);
354 		bTitleOpen = TRUE;
355 		break;
356 	case TAG_PARA:
357 		fail(bTitleOpen);
358 		uiParagraphLevel++;
359 		bEmptyHeaderLevel = FALSE;
360 		break;
361 	case TAG_EMPHASIS:
362 		bEmphasisOpen = TRUE;
363 		break;
364 	case TAG_ITEMIZEDLIST:
365 	case TAG_ORDEREDLIST:
366 		uiListLevel++;
367 		bEmptyListLevel = TRUE;
368 		bEmptyHeaderLevel = FALSE;
369 		break;
370 	case TAG_LISTITEM:
371 		bEmptyListLevel = FALSE;
372 		break;
373 	case TAG_SUPERSCRIPT:
374 		bSuperscriptOpen = TRUE;
375 		break;
376 	case TAG_SUBSCRIPT:
377 		bSubscriptOpen = TRUE;
378 		break;
379 	case TAG_INFORMALTABLE:
380 		bTableOpen = TRUE;
381 		bEmptyHeaderLevel = FALSE;
382 		break;
383 	default:
384 		break;
385 	}
386 } /* end of vAddStartTag */
387 
388 /*
389  * vAddEndTag - add the specified end tag to the file
390  */
391 static void
392 vAddEndTag(diagram_type *pDiag, UCHAR ucTag)
393 {
394 	UCHAR	ucTopTag;
395 
396 	fail(pDiag == NULL);
397 	fail(pDiag->pOutFile == NULL);
398 	fail((size_t)ucTag >= elementsof(atDocBookTags));
399 
400 #if defined(DEBUG)
401 	ucTopTag = ucReadStack();
402 	if (ucTag != ucTopTag) {
403 		DBG_DEC(ucTag);
404 		DBG_MSG(atDocBookTags[(UINT)ucTag].szTagname);
405 		vStackTrace();
406 	}
407 #endif /* DEBUG */
408 
409 	ucTopTag = ucPopStack();
410 	fail((size_t)ucTopTag >= elementsof(atDocBookTags));
411 	if (ucTag != ucTopTag) {
412 		DBG_DEC(ucTag);
413 		DBG_DEC(ucTopTag);
414 		DBG_FIXME();
415 		werr(1, "Impossible tag sequence, unable to continue");
416 	}
417 
418 	if (atDocBookTags[(UINT)ucTag].bAddNewline) {
419 		fprintf(pDiag->pOutFile, "\n");
420 		vPrintLevel(pDiag->pOutFile);
421 	}
422 
423 	fprintf(pDiag->pOutFile, "</%s>", atDocBookTags[(UINT)ucTag].szTagname);
424 
425 	if (atDocBookTags[(UINT)ucTag].bAddNewline) {
426 		fprintf(pDiag->pOutFile, "\n");
427 		pDiag->lXleft = 0;
428 	}
429 
430 	/* Set global variables */
431 	switch (ucTag) {
432 	case TAG_CHAPTER:
433 		usHeaderLevelCurrent = 0;
434 		break;
435 	case TAG_SECT1:
436 		usHeaderLevelCurrent = 1;
437 		break;
438 	case TAG_SECT2:
439 		usHeaderLevelCurrent = 2;
440 		break;
441 	case TAG_SECT3:
442 		usHeaderLevelCurrent = 3;
443 		break;
444 	case TAG_SECT4:
445 		usHeaderLevelCurrent = 4;
446 		break;
447 	case TAG_SECT5:
448 		usHeaderLevelCurrent = 5;
449 		break;
450 	case TAG_TITLE:
451 		bTitleOpen = FALSE;
452 		break;
453 	case TAG_PARA:
454 		uiParagraphLevel--;
455 		break;
456 	case TAG_EMPHASIS:
457 		bEmphasisOpen = FALSE;
458 		break;
459 	case TAG_SUPERSCRIPT:
460 		bSuperscriptOpen = FALSE;
461 		break;
462 	case TAG_ITEMIZEDLIST:
463 	case TAG_ORDEREDLIST:
464 		uiListLevel--;
465 		break;
466 	case TAG_SUBSCRIPT:
467 		bSubscriptOpen = FALSE;
468 		break;
469 	case TAG_INFORMALTABLE:
470 		bTableOpen = FALSE;
471 		iTableColumnsCurrent = 0;
472 		break;
473 	default:
474 		break;
475 	}
476 } /* end of vAddEndTag */
477 
478 /*
479  * vAddEndTagOptional - add the specified end tag to the file if needed
480  */
481 static void
482 vAddEndTagOptional(diagram_type *pDiag, UCHAR ucTag)
483 {
484 	UCHAR	ucTopTag;
485 
486 	ucTopTag = ucReadStack();
487 	if (ucTag == ucTopTag) {
488 		vAddEndTag(pDiag, ucTag);
489 	}
490 } /* end of vAddEndTagOptional */
491 
492 /*
493  * vAddCombinedTag - add the specified start and end tag to the file
494  */
495 static void
496 vAddCombinedTag(diagram_type *pDiag, UCHAR ucTag, const char *szAttribute)
497 {
498 	fail(pDiag == NULL);
499 	fail(pDiag->pOutFile == NULL);
500 	fail((size_t)ucTag >= elementsof(atDocBookTags));
501 
502 	if (atDocBookTags[(UINT)ucTag].bAddNewline) {
503 		fprintf(pDiag->pOutFile, "\n");
504 		vPrintLevel(pDiag->pOutFile);
505 	}
506 
507 	if (szAttribute == NULL || szAttribute[0] == '\0') {
508 		fprintf(pDiag->pOutFile, "<%s/>",
509 			atDocBookTags[(UINT)ucTag].szTagname);
510 	} else {
511 		fprintf(pDiag->pOutFile, "<%s %s/>",
512 			atDocBookTags[(UINT)ucTag].szTagname, szAttribute);
513 	}
514 
515 	if (atDocBookTags[(UINT)ucTag].bAddNewline) {
516 		fprintf(pDiag->pOutFile, "\n");
517 		pDiag->lXleft = 0;
518 	}
519 } /* end of vAddCombinedTag */
520 
521 /*
522  * vAddEndTagsUntil2 - add end tags until one the specified tags is seen
523  */
524 static void
525 vAddEndTagsUntil2(diagram_type *pDiag, UCHAR ucTag1, UCHAR ucTag2)
526 {
527 	UCHAR	ucTopTag;
528 
529 	do {
530 		ucTopTag = ucReadStack();
531 		switch (ucTopTag) {
532                 case TAG_CHAPTER:
533                 case TAG_SECT1:
534                 case TAG_SECT2:
535                 case TAG_SECT3:
536                 case TAG_SECT4:
537                 case TAG_SECT5:
538 			if (bEmptyHeaderLevel) {
539 				/*
540 				 * An empty chapter is legal in Word,
541 				 * but not in DocBook.
542 				 */
543 				vAddCombinedTag(pDiag, TAG_PARA, NULL);
544 				bEmptyHeaderLevel = FALSE;
545 			}
546 			break;
547 		case TAG_ITEMIZEDLIST:
548 		case TAG_ORDEREDLIST:
549 			if (bEmptyListLevel) {
550 				/*
551 				 * A list without items is legal in Word,
552 				 * but not in DocBook. (Nor are empty items)
553 				 */
554 				vAddStartTag(pDiag, TAG_LISTITEM, NULL);
555 				vAddCombinedTag(pDiag, TAG_PARA, NULL);
556 				vAddEndTag(pDiag, TAG_LISTITEM);
557 				bEmptyListLevel = FALSE;
558 			}
559 			break;
560 		default:
561 			break;
562 		}
563 		vAddEndTag(pDiag, ucTopTag);
564 	} while (ucTopTag != ucTag1 && ucTopTag != ucTag2);
565 } /* end of vAddEndTagsUntil2 */
566 
567 /*
568  * vCreateBookIntro - create title and bookinfo
569  */
570 void
571 vCreateBookIntro(diagram_type *pDiag, int iWordVersion, encoding_type eEncoding)
572 {
573 	const char	*szTitle, *szSubject, *szAuthor;
574 	const char	*szLastSaveDtm, *szCompany;
575 	const char	*szLanguage;
576 	char		szTmp[13];
577 
578 	fail(pDiag == NULL);
579 	fail(pDiag->pOutFile == NULL);
580 	fail(iWordVersion < 0);
581 	fail(eEncoding == encoding_neutral);
582 
583 	szTitle = szGetTitle();
584 	szSubject = szGetSubject();
585 	szAuthor = szGetAuthor();
586 	szLastSaveDtm = szGetLastSaveDtm();
587 	szCompany = szGetCompany();
588 
589 	/* Start Book */
590 	szLanguage = szGetLanguage();
591 	if (szLanguage != NULL) {
592 		DBG_MSG(szLanguage);
593 		sprintf(szTmp, "lang='%.5s'", szLanguage);
594 		szLanguage = szTmp;
595 	}
596 	vAddStartTag(pDiag, TAG_BOOK, szLanguage);
597 
598 	/* Book title */
599 	if (szTitle != NULL && szTitle[0] != '\0') {
600 		vAddStartTag(pDiag, TAG_TITLE, NULL);
601 		vPrintSpecial(pDiag->pOutFile,
602 				szTitle, iWordVersion, eEncoding);
603 		vAddEndTag(pDiag, TAG_TITLE);
604 	}
605 	/* Bookinfo */
606 	if ((szTitle != NULL && szTitle[0] != '\0') ||
607 	    (szSubject != NULL && szSubject[0] != '\0') ||
608 	    (szAuthor != NULL && szAuthor[0] != '\0') ||
609 	    (szLastSaveDtm != NULL && szLastSaveDtm[0] != '\0') ||
610 	    (szCompany != NULL && szCompany[0] != '\0')) {
611 		vAddStartTag(pDiag, TAG_BOOKINFO, NULL);
612 		if (szTitle != NULL && szTitle[0] != '\0') {
613 			vAddStartTag(pDiag, TAG_TITLE, NULL);
614 			vPrintSpecial(pDiag->pOutFile,
615 					szTitle, iWordVersion, eEncoding);
616 			vAddEndTag(pDiag, TAG_TITLE);
617 		}
618 		if (szSubject != NULL && szSubject[0] != '\0') {
619 			vAddStartTag(pDiag, TAG_SUBTITLE, NULL);
620 			vPrintSpecial(pDiag->pOutFile,
621 					szSubject, iWordVersion, eEncoding);
622 			vAddEndTag(pDiag, TAG_SUBTITLE);
623 		}
624 		if (szAuthor != NULL && szAuthor[0] != '\0') {
625 			vAddStartTag(pDiag, TAG_AUTHOR, NULL);
626 			vAddStartTag(pDiag, TAG_SURNAME, NULL);
627 			vPrintSpecial(pDiag->pOutFile,
628 					szAuthor, iWordVersion, eEncoding);
629 			vAddEndTag(pDiag, TAG_SURNAME);
630 			vAddEndTag(pDiag, TAG_AUTHOR);
631 		}
632 		if (szLastSaveDtm != NULL && szLastSaveDtm[0] != '\0') {
633 			vAddStartTag(pDiag, TAG_DATE, NULL);
634 			vPrintSpecial(pDiag->pOutFile,
635 					szLastSaveDtm, iWordVersion, eEncoding);
636 			vAddEndTag(pDiag, TAG_DATE);
637 		}
638 		if (szCompany != NULL && szCompany[0] != '\0') {
639 			vAddStartTag(pDiag, TAG_CORPNAME, NULL);
640 			vPrintSpecial(pDiag->pOutFile,
641 					szCompany, iWordVersion, eEncoding);
642 			vAddEndTag(pDiag, TAG_CORPNAME);
643 		}
644 		vAddEndTag(pDiag, TAG_BOOKINFO);
645 	}
646 } /* end of vCreateBookIntro */
647 
648 /*
649  * vPrologueXML - perform the XML initialization
650  */
651 void
652 vPrologueXML(diagram_type *pDiag)
653 {
654 
655 	fail(pDiag == NULL);
656 	fail(pDiag->pOutFile == NULL);
657 
658 #if defined(DEBUG)
659 	vCheckTagTable();
660 #endif /* DEBUG */
661 
662 	/* Set global variables to their start values */
663 	bEmphasisOpen = FALSE;
664 	bSuperscriptOpen = FALSE;
665 	bSubscriptOpen = FALSE;
666 	bTitleOpen = FALSE;
667 	bTableOpen = FALSE;
668 	uiParagraphLevel = 0;
669 	uiListLevel = 0;
670 	bEmptyListLevel = TRUE;
671 	usHeaderLevelCurrent = 0;
672 	bEmptyHeaderLevel = TRUE;
673 	iTableColumnsCurrent = 0;
674 
675 	pDiag->lXleft = 0;
676 	pDiag->lYtop = 0;
677 
678 	/* Create an empty stack */
679 	tStacksize = INITIAL_STACK_SIZE;
680 	aucStack = xcalloc(tStacksize, sizeof(UCHAR));
681 	tStackNextFree = 0;
682 } /* end of vPrologueXML */
683 
684 /*
685  * vEpilogueXML - clean up after everything is done
686  */
687 void
688 vEpilogueXML(diagram_type *pDiag)
689 {
690 	vStackTrace();
691 
692 	vAddEndTagsUntil1(pDiag, TAG_BOOK);
693 
694 	vStackTrace();
695 
696 	/* Destroy the stack */
697 	fail(tStackNextFree != 0);
698 	tStacksize = 0;
699 	aucStack = xfree(aucStack);
700 	tStackNextFree = 0;
701 } /* end of vEpilogueXML */
702 
703 /*
704  * vPrintXML - print a XML string
705  */
706 static void
707 vPrintXML(diagram_type *pDiag, const char *szString, size_t tStringLength,
708 		USHORT usFontstyle)
709 {
710 	int	iCount;
711 	size_t	tNextFree;
712 	BOOL	bNotReady, bEmphasisNew, bSuperscriptNew, bSubscriptNew;
713 	UCHAR	ucTopTag, aucStorage[3];
714 
715 	fail(szString == NULL);
716 
717 	if (szString == NULL || szString[0] == '\0' || tStringLength == 0) {
718 		return;
719 	}
720 
721 	bEmphasisNew = bIsBold(usFontstyle) ||
722 			bIsItalic(usFontstyle) ||
723 			bIsUnderline(usFontstyle) ||
724 			bIsStrike(usFontstyle);
725 	bSuperscriptNew = bIsSuperscript(usFontstyle);
726 	bSubscriptNew = bIsSubscript(usFontstyle);
727 
728 	/* End what has to be ended (or more to keep the stack happy) */
729 	tNextFree = 0;
730 	bNotReady = TRUE;
731 	do {
732 		ucTopTag = ucReadStack();
733 		switch (ucTopTag) {
734 		case TAG_EMPHASIS:
735 			fail(!bEmphasisOpen);
736 			if (bEmphasisNew) {
737 				aucStorage[tNextFree++] = ucTopTag;
738 			}
739 			vAddEndTag(pDiag, ucTopTag);
740 			break;
741 		case TAG_SUPERSCRIPT:
742 			fail(!bSuperscriptOpen);
743 			if (bSuperscriptNew) {
744 				aucStorage[tNextFree++] = ucTopTag;
745 			}
746 			vAddEndTag(pDiag, ucTopTag);
747 			break;
748 		case TAG_SUBSCRIPT:
749 			fail(!bSubscriptOpen);
750 			if (bSubscriptNew) {
751 				aucStorage[tNextFree++] = ucTopTag;
752 			}
753 			vAddEndTag(pDiag, ucTopTag);
754 			break;
755 		default:
756 			bNotReady = FALSE;
757 			break;
758 		}
759 		fail(tNextFree > elementsof(aucStorage));
760 		fail(bNotReady && tNextFree == elementsof(aucStorage));
761 	} while (bNotReady);
762 
763 	/* Just te make sure */
764 	vStartOfParagraphXML(pDiag, 1);
765 
766 	/* Restart to keep the stack happy */
767 	for (iCount = (int)tNextFree - 1; iCount > 0; iCount--) {
768 		vAddStartTag(pDiag, aucStorage[iCount], NULL);
769 	}
770 
771 	/* Start what has to be started */
772 	if (bEmphasisNew && !bEmphasisOpen) {
773 		vAddStartTag(pDiag, TAG_EMPHASIS, NULL);
774 	}
775 	if (bSuperscriptNew && !bSuperscriptOpen) {
776 		vAddStartTag(pDiag, TAG_SUPERSCRIPT, NULL);
777 	}
778 	if (bSubscriptNew && !bSubscriptOpen) {
779 		vAddStartTag(pDiag, TAG_SUBSCRIPT, NULL);
780 	}
781 
782 	/* The print the string */
783 	for (iCount = 0; iCount < (int)tStringLength; iCount++) {
784 		vPrintChar(pDiag->pOutFile, szString[iCount]);
785 	}
786 } /* end of vPrintXML */
787 
788 /*
789  * vMove2NextLineXML - move to the next line
790  */
791 void
792 vMove2NextLineXML(diagram_type *pDiag)
793 {
794 	fail(pDiag == NULL);
795 
796 	if (uiParagraphLevel != 0) {
797 		vEndOfParagraphXML(pDiag, INT_MAX);
798 		vStartOfParagraphXML(pDiag, INT_MAX);
799 	}
800 } /* end of vMove2NextLineXML */
801 
802 /*
803  * vSubstringXML - put a sub string into a diagram
804  */
805 void
806 vSubstringXML(diagram_type *pDiag,
807 	const char *szString, size_t tStringLength, long lStringWidth,
808 	USHORT usFontstyle)
809 {
810 	fail(pDiag == NULL || szString == NULL);
811 	fail(pDiag->pOutFile == NULL);
812 	fail(pDiag->lXleft < 0);
813 	fail(tStringLength != strlen(szString));
814 
815 	if (szString[0] == '\0' || tStringLength == 0) {
816 		return;
817 	}
818 
819 	vPrintXML(pDiag, szString, tStringLength, usFontstyle);
820 	pDiag->lXleft += lStringWidth;
821 } /* end of vSubstringXML */
822 
823 /*
824  * Create an start of a paragraph
825  * Only works on paragraph level one, because Word doesn't allow paragraphs
826  * in paragraphs. Other paragraph levels result from DocBooks special needs.
827  */
828 void
829 vStartOfParagraphXML(diagram_type *pDiag, int iMaxLevel)
830 {
831 	fail(pDiag == NULL);
832 
833 	if (uiParagraphLevel >= (UINT)iMaxLevel || bTitleOpen) {
834 		/* To Word titles are just paragraphs */
835 		return;
836 	}
837 	if (uiListLevel != 0 && bEmptyListLevel) {
838 		/* No paragraphs in a list before the first listitem */
839 		return;
840 	}
841 	if (usHeaderLevelCurrent == 0) {
842 		/* No paragraphs without an open header */
843 		vAddStartTag(pDiag, TAG_CHAPTER, NULL);
844 		/* Dummy title */
845 		vAddCombinedTag(pDiag, TAG_TITLE, NULL);
846 	}
847 	vAddStartTag(pDiag, TAG_PARA, NULL);
848 } /* end of vStartOfParagraphXML */
849 
850 /*
851  * Create an end of a paragraph
852  * Only for paragraph level one and for titles
853  */
854 void
855 vEndOfParagraphXML(diagram_type *pDiag, int iMaxLevel)
856 {
857 	UCHAR	ucTopTag;
858 
859 	fail(pDiag == NULL);
860 
861 	if (uiParagraphLevel > (UINT)iMaxLevel) {
862 		DBG_DEC(uiParagraphLevel);
863 		return;
864 	}
865 
866 	for(;;) {
867 		ucTopTag = ucReadStack();
868 		switch (ucTopTag) {
869 		case TAG_EMPHASIS:
870 			fail(!bEmphasisOpen);
871 			vAddEndTag(pDiag, TAG_EMPHASIS);
872 			break;
873 		case TAG_SUPERSCRIPT:
874 			fail(!bSuperscriptOpen);
875 			vAddEndTag(pDiag, TAG_SUPERSCRIPT);
876 			break;
877 		case TAG_SUBSCRIPT:
878 			fail(!bSubscriptOpen);
879 			vAddEndTag(pDiag, TAG_SUBSCRIPT);
880 			break;
881 		case TAG_TITLE:
882 			fail(!bTitleOpen);
883 			vAddEndTag(pDiag, TAG_TITLE);
884 			return;
885 		case TAG_PARA:
886 			fail(uiParagraphLevel == 0);
887 			vAddEndTag(pDiag, TAG_PARA);
888 			return;
889 		case TAG_TBODY:
890 		case TAG_TGROUP:
891 		case TAG_INFORMALTABLE:
892 			fail(!bTableOpen);
893 			vAddEndTag(pDiag, ucTopTag);
894 			break;
895 		case TAG_NOTAG:
896 			DBG_FIXME();
897 			werr(1, "Impossible tag sequence, unable to continue");
898 			break;
899 		default:
900 			DBG_DEC(ucTopTag);
901 			DBG_MSG_C((size_t)ucTopTag < elementsof(atDocBookTags),
902 				atDocBookTags[(UINT)ucTopTag].szTagname);
903 			return;
904 		}
905 	}
906 } /* end of vEndOfParagraphXML */
907 
908 /*
909  * Create an end of a page
910  */
911 void
912 vEndOfPageXML(diagram_type *pDiag)
913 {
914 	if (bTableOpen || usHeaderLevelCurrent == 0) {
915 		/* No beginpage in a table or outside a chapter */
916 		return;
917 	}
918 	if (bTitleOpen) {
919 		/* A beginpage is not allowed when in a title */
920 		/* So start a new paragraph */
921 		vEndOfParagraphXML(pDiag, INT_MAX);
922 		vStartOfParagraphXML(pDiag, INT_MAX);
923 		return;
924 	}
925 	vAddCombinedTag(pDiag, TAG_BEGINPAGE, NULL);
926 } /* end of vEndOfPageXML */
927 
928 /*
929  * vCloseHeaderLevels - close the specified header levels
930  */
931 static void
932 vCloseHeaderLevels(diagram_type *pDiag, USHORT usIstd)
933 {
934 	BOOL	bNotReady;
935 	UCHAR	ucTopTag;
936 
937 	DBG_MSG("vCloseHeaderLevels");
938 	DBG_DEC(usIstd);
939 	DBG_DEC(usHeaderLevelCurrent);
940 
941 	vStackTrace();
942 
943 	bNotReady = TRUE;
944 	do {
945 		ucTopTag = ucReadStack();
946 		switch (ucTopTag) {
947 		case TAG_TITLE:
948 		case TAG_PARA:
949 			vAddEndTag(pDiag, ucTopTag);
950 			break;
951 		default:
952 			bNotReady = FALSE;
953 			break;
954 		}
955 	} while (bNotReady);
956 
957 	vStackTrace();
958 
959 	while (usHeaderLevelCurrent >= usIstd) {
960 		if (bEmptyHeaderLevel) {
961 			vAddCombinedTag(pDiag, TAG_PARA, NULL);
962 			bEmptyHeaderLevel = FALSE;
963 		}
964 		switch (usHeaderLevelCurrent) {
965 		case 1: vAddEndTag(pDiag, TAG_CHAPTER); break;
966 		case 2: vAddEndTag(pDiag, TAG_SECT1); break;
967 		case 3: vAddEndTag(pDiag, TAG_SECT2); break;
968 		case 4: vAddEndTag(pDiag, TAG_SECT3); break;
969 		case 5: vAddEndTag(pDiag, TAG_SECT4); break;
970 		case 6: vAddEndTag(pDiag, TAG_SECT5); break;
971 		default:
972 			DBG_DEC(usHeaderLevelCurrent);
973 			DBG_FIXME();
974 			return;
975 		}
976 	}
977 
978 	DBG_DEC(usHeaderLevelCurrent);
979 
980 	vStackTrace();
981 } /* end of vCloseHeaderLevels */
982 
983 /*
984  * vSetHeadersXML - set the headers
985  */
986 void
987 vSetHeadersXML(diagram_type *pDiag, USHORT usIstd)
988 {
989 	fail(pDiag == NULL);
990 
991 	if (usIstd == 0 || usIstd > 6) {
992 		DBG_DEC_C(usIstd != 0 && usIstd <= 9, usIstd);
993 		return;
994 	}
995 	DBG_DEC(usIstd);
996 
997 	if (bTableOpen || uiListLevel != 0) {
998 		/* No headers when you're in a table or in a list */
999 		return;
1000 	}
1001 
1002 	/* Close levels */
1003 	vCloseHeaderLevels(pDiag, usIstd);
1004 
1005 	DBG_DEC(usHeaderLevelCurrent);
1006 
1007 	/* Open levels */
1008 	while (usHeaderLevelCurrent < usIstd) {
1009 		switch (usHeaderLevelCurrent) {
1010 		case 0: vAddStartTag(pDiag, TAG_CHAPTER, NULL); break;
1011 		case 1: vAddStartTag(pDiag, TAG_SECT1, NULL); break;
1012 		case 2: vAddStartTag(pDiag, TAG_SECT2, NULL); break;
1013 		case 3: vAddStartTag(pDiag, TAG_SECT3, NULL); break;
1014 		case 4: vAddStartTag(pDiag, TAG_SECT4, NULL); break;
1015 		case 5: vAddStartTag(pDiag, TAG_SECT5, NULL); break;
1016 		default:
1017 			DBG_DEC(usHeaderLevelCurrent);
1018 			DBG_FIXME();
1019 			return;
1020 		}
1021 		fail(usIstd == 0);
1022 		/* The next paragraph should be a title */
1023 		if (usHeaderLevelCurrent < usIstd) {
1024 			/* This chapter level is not in the Word document */
1025 			vAddCombinedTag(pDiag, TAG_TITLE, NULL);
1026 		} else {
1027 			vAddStartTag(pDiag, TAG_TITLE, NULL);
1028 		}
1029 	}
1030 } /* end of vSetHeadersXML */
1031 
1032 /*
1033  * Create a start of a list
1034  */
1035 void
1036 vStartOfListXML(diagram_type *pDiag, UCHAR ucNFC, BOOL bIsEndOfTable)
1037 {
1038 	const char	*szAttr;
1039 	UCHAR		ucTag;
1040 
1041 	fail(pDiag == NULL);
1042 
1043 	if (bIsEndOfTable) {
1044 		/* FIXME: until a list in a table is allowed */
1045 		vEndOfTableXML(pDiag);
1046 	}
1047 
1048 	if (bTableOpen) {
1049 		/* FIXME: a list in a table should be allowed */
1050 		return;
1051 	}
1052 
1053 	if (usHeaderLevelCurrent == 0) {
1054 		/* No list without an open header */
1055 		vAddStartTag(pDiag, TAG_CHAPTER, NULL);
1056 		/* Dummy title */
1057 		vAddCombinedTag(pDiag, TAG_TITLE, NULL);
1058 	}
1059 
1060 	switch (ucNFC) {
1061 	case LIST_ARABIC_NUM:
1062 	case LIST_ORDINAL_NUM:
1063 		ucTag = TAG_ORDEREDLIST;
1064 		szAttr = "numeration='arabic'";
1065 		break;
1066 	case LIST_UPPER_ROMAN:
1067 		ucTag = TAG_ORDEREDLIST;
1068 		szAttr = "numeration='upperroman'";
1069 		break;
1070 	case LIST_LOWER_ROMAN:
1071 		ucTag = TAG_ORDEREDLIST;
1072 		szAttr = "numeration='lowerroman'";
1073 		break;
1074 	case LIST_UPPER_ALPHA:
1075 		ucTag = TAG_ORDEREDLIST;
1076 		szAttr = "numeration='upperalpha'";
1077 		break;
1078 	case LIST_LOWER_ALPHA:
1079 		ucTag = TAG_ORDEREDLIST;
1080 		szAttr = "numeration='loweralpha'";
1081 		break;
1082 	case LIST_SPECIAL:
1083 	case LIST_BULLETS:
1084 		ucTag = TAG_ITEMIZEDLIST;
1085 		szAttr = "mark='bullet'";
1086 		break;
1087 	default:
1088 		ucTag = TAG_ORDEREDLIST;
1089 		szAttr = "numeration='arabic'";
1090 		DBG_DEC(ucNFC);
1091 		DBG_FIXME();
1092 		break;
1093 	}
1094 	vAddStartTag(pDiag, ucTag, szAttr);
1095 } /* end of vStartOfListXML */
1096 
1097 /*
1098  * Create an end of a list
1099  */
1100 void
1101 vEndOfListXML(diagram_type *pDiag)
1102 {
1103 	fail(pDiag == NULL);
1104 
1105 	if (bTableOpen) {
1106 		/* FIXME: a list in a table should be allowed */
1107 		return;
1108 	}
1109 
1110 	if (uiListLevel != 0) {
1111 		vStackTrace();
1112 		vAddEndTagsUntil2(pDiag, TAG_ITEMIZEDLIST, TAG_ORDEREDLIST);
1113 		vStackTrace();
1114 	}
1115 } /* end of vEndOfListXML */
1116 
1117 /*
1118  * Create a start of a list item
1119  */
1120 void
1121 vStartOfListItemXML(diagram_type *pDiag, BOOL bNoMarks)
1122 {
1123 	const char	*szAttr;
1124 	UCHAR	ucTopTag;
1125 
1126 	fail(pDiag == NULL);
1127 
1128 	if (bTableOpen) {
1129 		/* FIXME: a list in a table should be allowed */
1130 		return;
1131 	}
1132 
1133 	ucTopTag = ucReadStack();
1134 	if (ucTopTag != TAG_ITEMIZEDLIST && ucTopTag != TAG_ORDEREDLIST) {
1135 		/* Must end a previous list item first */
1136 		vAddEndTagsUntil1(pDiag, TAG_LISTITEM);
1137 	}
1138 
1139 	DBG_DEC_C(ucReadStack() != TAG_ITEMIZEDLIST &&
1140 		ucReadStack() != TAG_ORDEREDLIST, ucReadStack());
1141 
1142 	/* Start a new list item */
1143 	szAttr = bNoMarks ? "override='none'" : NULL;
1144 	vAddStartTag(pDiag, TAG_LISTITEM, szAttr);
1145 	/* Start a new paragraph (independant of level) */
1146 	vAddStartTag(pDiag, TAG_PARA, NULL);
1147 } /* end of vStartOfListItemXML */
1148 
1149 /*
1150  * Create a start of a table
1151  */
1152 static void
1153 vStartOfTable(diagram_type *pDiag, UCHAR ucBorderInfo)
1154 {
1155 	const char	*szFrame;
1156 	BOOL	bNotReady;
1157 	UCHAR	ucTopTag;
1158 	char	cColSep, cRowSep;
1159 	char	szAttr[40];
1160 
1161 	fail(pDiag == NULL);
1162 
1163 	/* Close elements that cannot contain a table */
1164 	bNotReady = TRUE;
1165 	do {
1166 		ucTopTag = ucReadStack();
1167 		switch (ucTopTag) {
1168 		case TAG_TITLE:
1169 			fail(!bTitleOpen);
1170 			vAddEndTag(pDiag, TAG_TITLE);
1171 			break;
1172 		case TAG_EMPHASIS:
1173 			fail(!bEmphasisOpen);
1174 			vAddEndTag(pDiag, TAG_EMPHASIS);
1175 			break;
1176 		case TAG_SUPERSCRIPT:
1177 			fail(!bSuperscriptOpen);
1178 			vAddEndTag(pDiag, TAG_SUPERSCRIPT);
1179 			break;
1180 		case TAG_SUBSCRIPT:
1181 			fail(!bSubscriptOpen);
1182 			vAddEndTag(pDiag, TAG_SUBSCRIPT);
1183 			break;
1184 		default:
1185 			bNotReady = FALSE;
1186 			break;
1187 		}
1188 	} while (bNotReady);
1189 
1190 	/* Create table attributes */
1191 	switch (ucBorderInfo) {
1192 	case TABLE_BORDER_TOP:
1193 		szFrame = "top";
1194 		break;
1195 	case TABLE_BORDER_LEFT|TABLE_BORDER_RIGHT:
1196 		szFrame = "sides";
1197 		break;
1198 	case TABLE_BORDER_TOP|TABLE_BORDER_BOTTOM:
1199 		szFrame = "topbot";
1200 		break;
1201 	case TABLE_BORDER_BOTTOM:
1202 		szFrame = "bottom";
1203 		break;
1204 	case TABLE_BORDER_TOP|TABLE_BORDER_LEFT|
1205 	     TABLE_BORDER_BOTTOM|TABLE_BORDER_RIGHT:
1206 		szFrame = "all";
1207 		break;
1208 	default:
1209 		szFrame = "none";
1210 		break;
1211 	}
1212 	cColSep = bIsTableBorderLeft(ucBorderInfo) ||
1213 		  bIsTableBorderRight(ucBorderInfo) ? '1' : '0';
1214 	cRowSep = bIsTableBorderTop(ucBorderInfo) ||
1215 		  bIsTableBorderBottom(ucBorderInfo) ? '1' : '0';
1216 
1217 	sprintf(szAttr, "frame='%.6s' colsep='%c' rowsep='%c'",
1218 			szFrame, cColSep, cRowSep);
1219 
1220 	if (usHeaderLevelCurrent == 0) {
1221 		/* No table without an open header */
1222 		vAddStartTag(pDiag, TAG_CHAPTER, NULL);
1223 		/* Dummy title */
1224 		vAddCombinedTag(pDiag, TAG_TITLE, NULL);
1225 	}
1226 	vAddStartTag(pDiag, TAG_INFORMALTABLE, szAttr);
1227 } /* end of vStartOfTable */
1228 
1229 /*
1230  * Create a start of a table group
1231  */
1232 static void
1233 vStartOfTableGroup(diagram_type *pDiag,
1234 	int iNbrOfColumns, const short *asColumnWidth)
1235 {
1236 	double	dWidth;
1237 	int	iIndex;
1238 	char	szCols[6 + 3 * sizeof(int) + 1 + 1];
1239 	char	szColWidth[10 + 3 * sizeof(short) + 3 + 3 + 1];
1240 
1241 	fail(iNbrOfColumns < 1);
1242 	fail(asColumnWidth == NULL);
1243 
1244 	sprintf(szCols, "cols='%d'", iNbrOfColumns);
1245 	vAddStartTag(pDiag, TAG_TGROUP, szCols);
1246 
1247 	for (iIndex= 0; iIndex < iNbrOfColumns; iIndex++) {
1248 		fail(asColumnWidth[iIndex] < 0);
1249 		dWidth = dTwips2Points(asColumnWidth[iIndex]);
1250 		if (dWidth <= 1.0) {
1251 			strcpy(szColWidth, "colwidth='1.00pt'");
1252 		} else {
1253 			sprintf(szColWidth, "colwidth='%.2fpt'", dWidth);
1254 		}
1255 		vAddCombinedTag(pDiag, TAG_COLSPEC, szColWidth);
1256 	}
1257 } /* end of vStartOfTableGroup */
1258 
1259 /*
1260  * Create an end of a table
1261  */
1262 void
1263 vEndOfTableXML(diagram_type *pDiag)
1264 {
1265 	fail(pDiag == NULL);
1266 
1267 	if (bTableOpen) {
1268 		vAddEndTag(pDiag, TAG_TBODY);
1269 		vAddEndTag(pDiag, TAG_TGROUP);
1270 		vAddEndTag(pDiag, TAG_INFORMALTABLE);
1271 	}
1272 } /* end of vEndOfTableXML */
1273 
1274 /*
1275  * Add a table row
1276  */
1277 void
1278 vAddTableRowXML(diagram_type *pDiag, char **aszColTxt,
1279 	int iNbrOfColumns, const short *asColumnWidth, UCHAR ucBorderInfo)
1280 {
1281 	size_t	tCount, tStringLength;
1282 	int	iIndex;
1283 
1284 	fail(pDiag == NULL);
1285 	fail(pDiag->pOutFile == NULL);
1286 	fail(aszColTxt == NULL);
1287 	fail(iNbrOfColumns < 1);
1288 	fail(asColumnWidth == NULL);
1289 
1290 	if (iNbrOfColumns != iTableColumnsCurrent) {
1291 		/* A new number of columns */
1292 		/* End the old table body and table group (if they exist) */
1293 		vAddEndTagOptional(pDiag, TAG_TBODY);
1294 		vAddEndTagOptional(pDiag, TAG_TGROUP);
1295 		if (!bTableOpen) {
1296 			/* No table yet. Start a new table */
1297 			vStartOfTable(pDiag, ucBorderInfo);
1298 		}
1299 		/* Start a new table group and a new table body */
1300 		vStartOfTableGroup(pDiag, iNbrOfColumns, asColumnWidth);
1301 		vAddStartTag(pDiag, TAG_TBODY, NULL);
1302 		iTableColumnsCurrent = iNbrOfColumns;
1303 	}
1304 
1305 	/* Add the table row */
1306 	vAddStartTag(pDiag, TAG_ROW, NULL);
1307 	for (iIndex = 0; iIndex < iNbrOfColumns; iIndex++) {
1308 		/* Add a table cell */
1309 		fail(aszColTxt[iIndex] == NULL);
1310 		vAddStartTag(pDiag, TAG_ENTRY, NULL);
1311 		tStringLength = strlen(aszColTxt[iIndex]);
1312 		for (tCount = 0; tCount < tStringLength; tCount++) {
1313 			vPrintChar(pDiag->pOutFile, aszColTxt[iIndex][tCount]);
1314 		}
1315 		vAddEndTag(pDiag, TAG_ENTRY);
1316 	}
1317 	vAddEndTag(pDiag, TAG_ROW);
1318 } /* end of vAddTableRowXML */
1319