/* * xhtml backend for Halibut * (initial implementation by James Aylett) * * Still to do: * * +++ doesn't handle non-breaking hyphens. Not sure how to yet. * +++ entity names (from a file -- ideally supply normal SGML files) * +++ configuration directive to file split where the current layout * code wouldn't. Needs changes to _ponder_layout() and _do_paras(), * perhaps others. * * Limitations: * * +++ biblio/index references target the nearest section marker, rather * than having a dedicated target themselves. In large bibliographies * this will cause problems. (The solution is to fake up a response * from xhtml_find_section(), probably linking it into the sections * chain just in case we need it again, and to make freeing it up * easier.) docsrc.pl used to work as we do, however, and SGT agrees that * this is acceptable for now. * +++ can't cope with leaf-level == 0. It's all to do with the * top-level file not being normal, probably not even having a valid * section level, and stuff like that. I question whether this is an * issue, frankly; small manuals that fit on one page should probably * not be written in halibut at all. */ #include #include #include #include #include "halibut.h" /* * FILENAME_TEMPLATE (overridable in config of course) allows you * to choose the general form for your HTML file names. It is * slightly printf-styled (% followed by a single character is a * formatting directive, %% is a literal %). Formatting directives * are: * * - %n is the section type-plus-number, minus whitespace (`Chapter1.2'). * - %b is the section number on its own (`1.2'). * - %k is the section's _internal_ keyword. * - %N is the section's visible title in the output, again minus * whitespace. * * %n, %b and %k will all default to %N if the section is * unnumbered (`Bibliography' is often a good example). * * FRAGMENT_TEMPLATE is the same, but defines the * markers used to cross-reference to particular subsections of a * file. */ #define FILENAME_SINGLE "Manual.html" #define FILENAME_CONTENTS "Contents.html" #define FILENAME_INDEX "IndexPage.html" #define FILENAME_TEMPLATE "%n.html" #define FRAGMENT_TEMPLATE "%b" struct xhtmlsection_Struct { struct xhtmlsection_Struct *next; /* next sibling (NULL if split across files) */ struct xhtmlsection_Struct *child; /* NULL if split across files */ struct xhtmlsection_Struct *parent; /* NULL if split across files */ struct xhtmlsection_Struct *chain; /* single structure independent of weird trees */ paragraph *para; struct xhtmlfile_Struct *file; /* which file is this a part of? */ char *fragment; /* fragment id within the file */ int level; }; struct xhtmlfile_Struct { struct xhtmlfile_Struct *next; struct xhtmlfile_Struct *child; struct xhtmlfile_Struct *parent; char *filename; struct xhtmlsection_Struct *sections; /* sections within this file (only one for non-leaf) */ int is_leaf; /* is this file a leaf file, ie does it not have any children? */ }; typedef struct xhtmlsection_Struct xhtmlsection; typedef struct xhtmlfile_Struct xhtmlfile; typedef struct xhtmlindex_Struct xhtmlindex; struct xhtmlindex_Struct { int nsection; int size; xhtmlsection **sections; }; typedef struct { int just_numbers; wchar_t *number_suffix; } xhtmlheadfmt; typedef struct { int contents_depth[6]; int leaf_contains_contents; int leaf_level; int leaf_smallest_contents; int include_version_id; wchar_t *author, *description; wchar_t *head_end, *body, *body_start, *body_end, *address_start, *address_end, *nav_attrs; int suppress_address; xhtmlheadfmt fchapter, *fsect; int nfsect; char *contents_filename, *index_filename; char *single_filename, *template_filename, *template_fragment; } xhtmlconfig; /*static void xhtml_level(paragraph *, int); static void xhtml_level_0(paragraph *); static void xhtml_docontents(FILE *, paragraph *, int); static void xhtml_dosections(FILE *, paragraph *, int); static void xhtml_dobody(FILE *, paragraph *, int);*/ static void xhtml_doheader(FILE *, word *); static void xhtml_dofooter(FILE *); static void xhtml_versionid(FILE *, word *, int); static void xhtml_utostr(wchar_t *, char **); static int xhtml_para_level(paragraph *); static int xhtml_reservedchar(int); static int xhtml_convert(wchar_t *, int, char **, int); static void xhtml_rdaddwc(rdstringc *, word *, word *, int); static void xhtml_para(FILE *, word *, int); static void xhtml_codepara(FILE *, word *); static void xhtml_heading(FILE *, paragraph *, int); /* File-global variables are much easier than passing these things * all over the place. Evil, but easier. We can replace this with a single * structure at some point. */ static xhtmlconfig conf; static keywordlist *keywords; static indexdata *idx; static xhtmlfile *topfile; static xhtmlsection *topsection; static paragraph *sourceparas; static xhtmlfile *lastfile; static xhtmlfile *xhtml_last_file = NULL; static int last_level=-1, start_level; static xhtmlsection *currentsection; static xhtmlconfig xhtml_configure(paragraph *source) { xhtmlconfig ret; /* * Defaults. */ ret.contents_depth[0] = 2; ret.contents_depth[1] = 3; ret.contents_depth[2] = 4; ret.contents_depth[3] = 5; ret.contents_depth[4] = 6; ret.contents_depth[5] = 7; ret.leaf_level = 2; ret.leaf_smallest_contents = 4; ret.leaf_contains_contents = FALSE; ret.include_version_id = TRUE; ret.author = NULL; ret.description = NULL; ret.head_end = NULL; ret.body = NULL; ret.body_start = NULL; ret.body_end = NULL; ret.address_start = NULL; ret.address_end = NULL; ret.nav_attrs = NULL; ret.suppress_address = FALSE; ret.fchapter.just_numbers = FALSE; ret.fchapter.number_suffix = L": "; ret.nfsect = 2; ret.fsect = mknewa(xhtmlheadfmt, ret.nfsect); ret.fsect[0].just_numbers = FALSE; ret.fsect[0].number_suffix = L": "; ret.fsect[1].just_numbers = TRUE; ret.fsect[1].number_suffix = L" "; ret.contents_filename = strdup(FILENAME_CONTENTS); ret.single_filename = strdup(FILENAME_SINGLE); ret.index_filename = strdup(FILENAME_INDEX); ret.template_filename = strdup(FILENAME_TEMPLATE); ret.template_fragment = strdup(FRAGMENT_TEMPLATE); for (; source; source = source->next) { if (source->type == para_Config) { if (!ustricmp(source->keyword, L"xhtml-contents-filename")) { sfree(ret.contents_filename); ret.contents_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-single-filename")) { sfree(ret.single_filename); ret.single_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-index-filename")) { sfree(ret.index_filename); ret.index_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-template-filename")) { sfree(ret.template_filename); ret.template_filename = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-template-fragment")) { sfree(ret.template_fragment); ret.template_fragment = utoa_dup(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-0")) { ret.contents_depth[0] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-1")) { ret.contents_depth[1] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-2")) { ret.contents_depth[2] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-3")) { ret.contents_depth[3] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-4")) { ret.contents_depth[4] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-contents-depth-5")) { ret.contents_depth[5] = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-leaf-level")) { ret.leaf_level = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-leaf-smallest-contents")) { ret.leaf_smallest_contents = utoi(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-versionid")) { ret.include_version_id = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-leaf-contains-contents")) { ret.leaf_contains_contents = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-suppress-address")) { ret.suppress_address = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-author")) { ret.author = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-description")) { ret.description = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-head-end")) { ret.head_end = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-body-start")) { ret.body_start = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-body-tag")) { ret.body = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-body-end")) { ret.body_end = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-address-start")) { ret.address_start = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-address-end")) { ret.address_end = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-navigation-attributes")) { ret.nav_attrs = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-chapter-numeric")) { ret.fchapter.just_numbers = utob(uadv(source->keyword)); } else if (!ustricmp(source->keyword, L"xhtml-chapter-suffix")) { ret.fchapter.number_suffix = uadv(source->keyword); } else if (!ustricmp(source->keyword, L"xhtml-section-numeric")) { wchar_t *p = uadv(source->keyword); int n = 0; if (uisdigit(*p)) { n = utoi(p); p = uadv(p); } if (n >= ret.nfsect) { int i; ret.fsect = resize(ret.fsect, n+1); for (i = ret.nfsect; i <= n; i++) ret.fsect[i] = ret.fsect[ret.nfsect-1]; ret.nfsect = n+1; } ret.fsect[n].just_numbers = utob(p); } else if (!ustricmp(source->keyword, L"xhtml-section-suffix")) { wchar_t *p = uadv(source->keyword); int n = 0; if (uisdigit(*p)) { n = utoi(p); p = uadv(p); } if (n >= ret.nfsect) { int i; ret.fsect = resize(ret.fsect, n+1); for (i = ret.nfsect; i <= n; i++) ret.fsect[i] = ret.fsect[ret.nfsect-1]; ret.nfsect = n+1; } ret.fsect[n].number_suffix = p; } } } /* printf(" !!! leaf_level = %i\n", ret.leaf_level); printf(" !!! contentdepth-0 = %i\n", ret.contents_depth[0]); printf(" !!! contentdepth-1 = %i\n", ret.contents_depth[1]); printf(" !!! contentdepth-2 = %i\n", ret.contents_depth[2]); printf(" !!! contentdepth-3 = %i\n", ret.contents_depth[3]); printf(" !!! contentdepth-4 = %i\n", ret.contents_depth[4]); printf(" !!! contentdepth-5 = %i\n", ret.contents_depth[5]); printf(" !!! leaf_contains_contents = %i\n", ret.leaf_contains_contents);*/ return ret; } paragraph *xhtml_config_filename(char *filename) { /* * If the user passes in a single filename as a parameter to * the `--html' command-line option, then we should assume it * to imply _two_ config directives: * \cfg{xhtml-single-filename}{whatever} and * \cfg{xhtml-leaf-level}{0}; the rationale being that the user * wants their output _in that file_. */ paragraph *p[2]; int i, len; wchar_t *ufilename, *up; for (i = 0; i < 2; i++) { p[i] = mknew(paragraph); memset(p[i], 0, sizeof(*p[i])); p[i]->type = para_Config; p[i]->next = NULL; p[i]->fpos.filename = ""; p[i]->fpos.line = p[i]->fpos.col = -1; } ufilename = ufroma_dup(filename); len = ustrlen(ufilename) + 2 + lenof(L"xhtml-single-filename"); p[0]->keyword = mknewa(wchar_t, len); up = p[0]->keyword; ustrcpy(up, L"xhtml-single-filename"); up = uadv(up); ustrcpy(up, ufilename); up = uadv(up); *up = L'\0'; assert(up - p[0]->keyword < len); sfree(ufilename); len = lenof(L"xhtml-leaf-level") + lenof(L"0") + 1; p[1]->keyword = mknewa(wchar_t, len); up = p[1]->keyword; ustrcpy(up, L"xhtml-leaf-level"); up = uadv(up); ustrcpy(up, L"0"); up = uadv(up); *up = L'\0'; assert(up - p[1]->keyword < len); p[0]->next = p[1]; return p[0]; } static xhtmlsection *xhtml_new_section(xhtmlsection *last) { xhtmlsection *ret = mknew(xhtmlsection); ret->next=NULL; ret->child=NULL; ret->parent=NULL; ret->chain=last; ret->para=NULL; ret->file=NULL; ret->fragment=NULL; ret->level=-1; /* marker: end of chain */ return ret; } /* Returns NULL or the section that marks that paragraph */ static xhtmlsection *xhtml_find_section(paragraph *p) { xhtmlsection *ret = topsection; if (xhtml_para_level(p)==-1) { /* first, we back-track to a section paragraph */ paragraph *p2 = sourceparas; paragraph *p3 = NULL; while (p2 && p2!=p) { if (xhtml_para_level(p2)!=-1) { p3 = p2; } p2=p2->next; } if (p3==NULL) { /* for some reason, we couldn't find a section before this paragraph ... ? */ /* Note that this can happen, if you have a cross-reference to before the first chapter starts. * So don't do that, then. */ return NULL; } p=p3; } while (ret && ret->para != p) { /* printf(" xhtml_find_section(): checking %s for para @ %p\n", ret->fragment, p);*/ ret=ret->chain; } return ret; } static void xhtml_format(paragraph *p, char *template_string, rdstringc *r) { char *c, *t; word *w; wchar_t *ws; t = template_string; while (*t) { if (*t == '%' && t[1]) { int fmt; t++; fmt = *t++; if (fmt == '%') { rdaddc(r, fmt); continue; } w = NULL; ws = NULL; if (p->kwtext && fmt == 'n') w = p->kwtext; else if (p->kwtext2 && fmt == 'b') w = p->kwtext2; else if (p->keyword && *p->keyword && fmt == 'k') ws = p->keyword; else w = p->words; while (w) { switch (removeattr(w->type)) { case word_Normal: /*case word_Emph: case word_Code: case word_WeakCode:*/ xhtml_utostr(w->text, &c); rdaddsc(r,c); sfree(c); break; } w = w->next; } if (ws) { xhtml_utostr(ws, &c); rdaddsc(r,c); sfree(c); } } else { rdaddc(r, *t++); } } } static xhtmlfile *xhtml_new_file(xhtmlsection *sect) { xhtmlfile *ret = mknew(xhtmlfile); ret->next=NULL; ret->child=NULL; ret->parent=NULL; ret->filename=NULL; ret->sections=sect; ret->is_leaf=(sect!=NULL && sect->level==conf.leaf_level); if (sect==NULL) { if (conf.leaf_level==0) { /* currently unused */ ret->filename = smalloc(strlen(conf.single_filename)+1); sprintf(ret->filename, conf.single_filename); } else { ret->filename = smalloc(strlen(conf.contents_filename)+1); sprintf(ret->filename, conf.contents_filename); } } else { paragraph *p = sect->para; rdstringc fname_c = { 0, 0, NULL }; xhtml_format(p, conf.template_filename, &fname_c); ret->filename = rdtrimc(&fname_c); } /* printf(" ! new file '%s', is_leaf == %s\n", ret->filename, (ret->is_leaf)?("true"):("false"));*/ return ret; } /* * Walk the tree fixing up files which are actually leaf (ie * have no children) but aren't at leaf level, so they have the * leaf flag set. */ void xhtml_fixup_layout(xhtmlfile* file) { if (file->child==NULL) { file->is_leaf = TRUE; } else { xhtml_fixup_layout(file->child); } if (file->next) xhtml_fixup_layout(file->next); } /* * Create the tree structure so we know where everything goes. * Method: * * Ignoring file splitting, we have three choices with each new section: * * +-----------------+-----------------+ * | | | * X +----X----+ (1) * | | * Y (2) * | * (3) * * Y is the last section we added (currentsect). * If sect is the section we want to add, then: * * (1) if sect->level < currentsect->level * (2) if sect->level == currentsect->level * (3) if sect->level > currentsect->level * * This requires the constraint that you never skip section numbers * (so you can't have a.b.c.d without all of a, a.b and a.b.c existing). * * Note that you _can_ have 1.1.1.1 followed by 1.2 - you can change * more than one level at a time. Lots of asserts, and probably part of * the algorithm here, rely on this being true. (It currently isn't * enforced by halibut, however.) * * File splitting makes this harder. For instance, say we added at (3) * above and now need to add another section. We are splitting at level * 2, ie the level of Y. Z is the last section we added: * * +-----------------+-----------------+ * | | | * X +----X----+ (1) * | | * +----Y----+ (1) * | | * Z (2) * | * (3) * * The (1) case is now split; we need to search upwards to find where * to actually link in. The other two cases remain the same (and will * always be like this). * * File splitting makes this harder, however. The decision of whether * to split to a new file is always on the same condition, however (is * the level of this section higher than the leaf_level configuration * value or not). * * Treating the cases backwards: * * (3) same file if sect->level > conf.leaf_level, otherwise new file * * if in the same file, currentsect->child points to sect * otherwise the linking is done through the file tree (which works * in more or less the same way, ie currentfile->child points to * the new file) * * (2) same file if sect->level > conf.leaf_level, otherwise new file * * if in the same file, currentsect->next points to sect * otherwise file linking and currentfile->next points to the new * file (we know that Z must have caused a new file to be created) * * (1) same file if sect->level > conf.leaf_level, otherwise new file * * this is actually effectively the same case as (2) here, * except that we first have to travel up the sections to figure * out which section this new one will be a sibling of. In doing * so, we may disappear off the top of a file and have to go up * to its parent in the file tree. * */ static void xhtml_ponder_layout(paragraph *p) { xhtmlsection *lastsection; xhtmlsection *currentsect; xhtmlfile *currentfile; lastfile = NULL; topsection = xhtml_new_section(NULL); topfile = xhtml_new_file(NULL); lastsection = topsection; currentfile = topfile; currentsect = topsection; if (conf.leaf_level == 0) { topfile->is_leaf = 1; topfile->sections = topsection; topsection->file = topfile; } for (; p; p=p->next) { int level = xhtml_para_level(p); if (level>0) /* actually a section */ { xhtmlsection *sect; rdstringc frag_c = { 0, 0, NULL }; sect = xhtml_new_section(lastsection); lastsection = sect; sect->para = p; xhtml_format(p, conf.template_fragment, &frag_c); sect->fragment = rdtrimc(&frag_c); sect->level = level; /* printf(" ! adding para @ %p as sect %s, level %i\n", sect->para, sect->fragment, level);*/ if (level>currentsect->level) { /* case (3) */ if (level>conf.leaf_level) { /* same file */ assert(currentfile->is_leaf); currentsect->child = sect; sect->parent=currentsect; sect->file=currentfile; /* printf("connected '%s' to existing file '%s' [I]\n", sect->fragment, currentfile->filename);*/ currentsect=sect; } else { /* new file */ xhtmlfile *file = xhtml_new_file(sect); assert(!currentfile->is_leaf); currentfile->child=file; sect->file=file; file->parent=currentfile; /* printf("connected '%s' to new file '%s' [I]\n", sect->fragment, file->filename);*/ currentfile=file; currentsect=sect; } } else if (level >= currentsect->file->sections->level) { /* Case (1) or (2) *AND* still under the section that starts * the current file. * * I'm not convinced that this couldn't be rolled in with the * final else {} leg further down. It seems a lot of effort * this way. */ if (level>conf.leaf_level) { /* stick within the same file */ assert(currentfile->is_leaf); sect->file = currentfile; while (currentsect && currentsect->level > level && currentsect->file==currentsect->parent->file) { currentsect = currentsect->parent; } assert(currentsect); currentsect->next = sect; assert(currentsect->level == sect->level); sect->parent = currentsect->parent; currentsect = sect; /* printf("connected '%s' to existing file '%s' [II]\n", sect->fragment, currentfile->filename);*/ } else { /* new file */ xhtmlfile *file = xhtml_new_file(sect); sect->file=file; currentfile->next=file; file->parent=currentfile->parent; file->is_leaf=(level==conf.leaf_level); file->sections=sect; /* printf("connected '%s' to new file '%s' [II]\n", sect->fragment, file->filename);*/ currentfile=file; currentsect=sect; } } else { /* Case (1) or (2) and we must move up the file tree first */ /* this loop is now probably irrelevant - we know we can't connect * to anything in the current file */ while (currentsect && levellevel) { currentsect=currentsect->parent; if (currentsect) { /* printf(" * up one level to '%s'\n", currentsect->fragment);*/ } else { /* printf(" * up one level (off top of current file)\n");*/ } } if (currentsect) { /* I'm pretty sure this can now never fire */ assert(currentfile->is_leaf); /* printf("connected '%s' to existing file '%s' [III]\n", sect->fragment, currentfile->filename);*/ sect->file = currentfile; currentsect->next=sect; currentsect=sect; } else { /* find a file we can attach to */ while (currentfile && currentfile->sections && levelsections->level) { currentfile=currentfile->parent; if (currentfile) { /* printf(" * up one file level to '%s'\n", currentfile->filename);*/ } else { /* printf(" * up one file level (off top of tree)\n");*/ } } if (currentfile) { /* new file (we had to skip up a file to get here, so we must be dealing with a level no lower than the configured leaf_level */ xhtmlfile *file = xhtml_new_file(sect); currentfile->next=file; sect->file=file; file->parent=currentfile->parent; file->is_leaf=(level==conf.leaf_level); file->sections=sect; /* printf("connected '%s' to new file '%s' [III]\n", sect->fragment, file->filename);*/ currentfile=file; currentsect=sect; } else { fatal(err_whatever, "Ran off the top trying to connect sibling: strange document."); } } } } } topsection = lastsection; /* get correct end of the chain */ xhtml_fixup_layout(topfile); /* leaf files not at leaf level marked as such */ } static void xhtml_do_index(); static void xhtml_do_file(xhtmlfile *file); static void xhtml_do_top_file(xhtmlfile *file, paragraph *sourceform); static void xhtml_do_paras(FILE *fp, paragraph *p, paragraph *end, int indexable); static int xhtml_do_contents_limit(FILE *fp, xhtmlfile *file, int limit); static int xhtml_do_contents_section_limit(FILE *fp, xhtmlsection *section, int limit); static int xhtml_add_contents_entry(FILE *fp, xhtmlsection *section, int limit); static int xhtml_do_contents(FILE *fp, xhtmlfile *file); static int xhtml_do_naked_contents(FILE *fp, xhtmlfile *file); static void xhtml_do_sections(FILE *fp, xhtmlsection *sections); /* * Do all the files in this structure. */ static void xhtml_do_files(xhtmlfile *file) { xhtml_do_file(file); if (file->child) xhtml_do_files(file->child); if (file->next) xhtml_do_files(file->next); } /* * Free up all memory used by the file tree from 'xfile' downwards */ static void xhtml_free_file(xhtmlfile* xfile) { if (xfile==NULL) { return; } if (xfile->filename) { sfree(xfile->filename); } xhtml_free_file(xfile->child); xhtml_free_file(xfile->next); sfree(xfile); } /* * Main function. */ void xhtml_backend(paragraph *sourceform, keywordlist *in_keywords, indexdata *in_idx) { /* int i;*/ indexentry *ientry; int ti; xhtmlsection *xsect; sourceparas = sourceform; conf = xhtml_configure(sourceform); keywords = in_keywords; idx = in_idx; /* Clear up the index entries backend data pointers */ for (ti=0; (ientry = (indexentry *)index234(idx->entries, ti))!=NULL; ti++) { ientry->backend_data=NULL; } xhtml_ponder_layout(sourceform); /* old system ... (writes to *.alt, but gets some stuff wrong and is ugly) */ /* xhtml_level_0(sourceform); for (i=1; i<=conf.leaf_level; i++) { xhtml_level(sourceform, i); }*/ /* new system ... (writes to *.html, but isn't fully trusted) */ xhtml_do_top_file(topfile, sourceform); assert(!topfile->next); /* shouldn't have a sibling at all */ if (topfile->child) { xhtml_do_files(topfile->child); xhtml_do_index(); } /* release file, section, index data structures */ xsect = topsection; while (xsect) { xhtmlsection *tmp = xsect->chain; if (xsect->fragment) { sfree(xsect->fragment); } sfree(xsect); xsect = tmp; } xhtml_free_file(topfile); for (ti = 0; (ientry=(indexentry *)index234(idx->entries, ti))!=NULL; ti++) { if (ientry->backend_data!=NULL) { xhtmlindex *xi = (xhtmlindex*) ientry->backend_data; if (xi->sections!=NULL) { sfree(xi->sections); } sfree(xi); } ientry->backend_data = NULL; } sfree(conf.fsect); } static int xhtml_para_level(paragraph *p) { switch (p->type) { case para_Title: return 0; break; case para_UnnumberedChapter: case para_Chapter: case para_Appendix: return 1; break; /* case para_BiblioCited: return 2; break;*/ case para_Heading: case para_Subsect: return p->aux+2; break; default: return -1; break; } } /* Output the nav links for the current file. * file == NULL means we're doing the index */ static void xhtml_donavlinks(FILE *fp, xhtmlfile *file) { xhtmlfile *xhtml_next_file = NULL; fprintf(fp, "", conf.nav_attrs); } else { fprintf(fp, ">"); } if (xhtml_last_file==NULL) { fprintf(fp, "Previous | "); } else { fprintf(fp, "Previous | ", xhtml_last_file->filename); } fprintf(fp, "Contents | ", conf.contents_filename); if (file == NULL) { fprintf(fp, "Index | "); } else { fprintf(fp, "Index | ", conf.index_filename); } if (file != NULL) { /* otherwise we're doing nav links for the index */ if (xhtml_next_file==NULL) xhtml_next_file = file->child; if (xhtml_next_file==NULL) xhtml_next_file = file->next; if (xhtml_next_file==NULL) xhtml_next_file = file->parent->next; } if (xhtml_next_file==NULL) { if (file==NULL) { /* index, so no next file */ fprintf(fp, "Next "); } else { fprintf(fp, "Next", conf.index_filename); } } else { fprintf(fp, "Next", xhtml_next_file->filename); } fprintf(fp, "

\n"); } /* Write out the index file */ static void xhtml_do_index_body(FILE *fp) { indexentry *y; int ti; if (count234(idx->entries) == 0) return; /* don't write anything at all */ fprintf(fp, "
\n"); /* iterate over idx->entries using the tree functions and display everything */ for (ti = 0; (y = (indexentry *)index234(idx->entries, ti)) != NULL; ti++) { if (y->backend_data) { int i; xhtmlindex *xi; fprintf(fp, "
"); xhtml_para(fp, y->text, FALSE); fprintf(fp, "
\n
"); xi = (xhtmlindex*) y->backend_data; for (i=0; insection; i++) { xhtmlsection *sect = xi->sections[i]; if (sect) { fprintf(fp, "", sect->file->filename, sect->fragment); if (sect->para->kwtext) { xhtml_para(fp, sect->para->kwtext, FALSE); } else if (sect->para->words) { xhtml_para(fp, sect->para->words, FALSE); } fprintf(fp, ""); if (i+1nsection) { fprintf(fp, ", "); } } } fprintf(fp, "
\n"); } } fprintf(fp, "
\n"); } static void xhtml_do_index() { word temp_word = { NULL, NULL, word_Normal, 0, 0, L"Index", { NULL, 0, 0} }; FILE *fp = fopen(conf.index_filename, "w"); if (fp==NULL) fatal(err_cantopenw, conf.index_filename); xhtml_doheader(fp, &temp_word); xhtml_donavlinks(fp, NULL); xhtml_do_index_body(fp); xhtml_donavlinks(fp, NULL); xhtml_dofooter(fp); fclose(fp); } /* Output the given file. This includes whatever contents at beginning and end, etc. etc. */ static void xhtml_do_file(xhtmlfile *file) { FILE *fp = fopen(file->filename, "w"); if (fp==NULL) fatal(err_cantopenw, file->filename); if (file->sections->para->words) { xhtml_doheader(fp, file->sections->para->words); } else if (file->sections->para->kwtext) { xhtml_doheader(fp, file->sections->para->kwtext); } else { xhtml_doheader(fp, NULL); } xhtml_donavlinks(fp, file); if (file->is_leaf && conf.leaf_contains_contents && xhtml_do_contents(NULL, file)>=conf.leaf_smallest_contents) xhtml_do_contents(fp, file); xhtml_do_sections(fp, file->sections); if (!file->is_leaf) xhtml_do_naked_contents(fp, file); xhtml_donavlinks(fp, file); xhtml_dofooter(fp); fclose(fp); xhtml_last_file = file; } /* Output the top-level file. */ static void xhtml_do_top_file(xhtmlfile *file, paragraph *sourceform) { paragraph *p; int done=FALSE; FILE *fp = fopen(file->filename, "w"); if (fp==NULL) fatal(err_cantopenw, file->filename); /* Do the title -- only one allowed */ for (p = sourceform; p && !done; p = p->next) { if (p->type == para_Title) { xhtml_doheader(fp, p->words); done=TRUE; } } if (!done) xhtml_doheader(fp, NULL /* Eek! */); /* * Display the title. */ for (p = sourceform; p; p = p->next) { if (p->type == para_Title) { xhtml_heading(fp, p, FALSE); break; } } /* Do the preamble */ for (p = sourceform; p; p = p->next) { if (p->type == para_Chapter || p->type == para_Heading || p->type == para_Subsect || p->type == para_Appendix || p->type == para_UnnumberedChapter) { /* * We've found the end of the preamble. Do every normal * paragraph up to there. */ xhtml_do_paras(fp, sourceform, p, FALSE); break; } } xhtml_do_contents(fp, file); xhtml_do_sections(fp, file->sections); /* * Put the index in the top file if we're in single-file mode * (leaf-level 0). */ if (conf.leaf_level == 0 && count234(idx->entries) > 0) { fprintf(fp, "

Index

\n"); xhtml_do_index_body(fp); } xhtml_dofooter(fp); fclose(fp); } /* Convert a Unicode string to an ASCII one. '?' is * used for unmappable characters. */ static void xhtml_utostr(wchar_t *in, char **out) { int l = ustrlen(in); int i; *out = smalloc(l+1); for (i=0; i=32 && in[i]<=126) (*out)[i]=(char)in[i]; else (*out)[i]='?'; } (*out)[i]=0; } /* * Write contents for the given file, and subfiles, down to * the appropriate contents depth. Returns the number of * entries written. */ static int xhtml_do_contents(FILE *fp, xhtmlfile *file) { int level, limit, count = 0; if (!file) return 0; level = (file->sections)?(file->sections->level):(0); limit = conf.contents_depth[(level>5)?(5):(level)]; start_level = (file->is_leaf) ? (level-1) : (level); last_level = start_level; count += xhtml_do_contents_section_limit(fp, file->sections, limit); count += xhtml_do_contents_limit(fp, file->child, limit); if (fp!=NULL) { while (last_level > start_level) { last_level--; fprintf(fp, "\n"); } } return count; } /* As above, but doesn't do anything in the current file */ static int xhtml_do_naked_contents(FILE *fp, xhtmlfile *file) { int level, limit, start_level, count = 0; if (!file) return 0; level = (file->sections)?(file->sections->level):(0); limit = conf.contents_depth[(level>5)?(5):(level)]; start_level = (file->is_leaf) ? (level-1) : (level); last_level = start_level; count = xhtml_do_contents_limit(fp, file->child, limit); if (fp!=NULL) { while (last_level > start_level) { last_level--; fprintf(fp, "\n"); } } return count; } /* * Write contents for the given file, children, and siblings, down to * given limit contents depth. */ static int xhtml_do_contents_limit(FILE *fp, xhtmlfile *file, int limit) { int count = 0; while (file) { count += xhtml_do_contents_section_limit(fp, file->sections, limit); count += xhtml_do_contents_limit(fp, file->child, limit); file = file->next; } return count; } /* * Write contents entries for the given section tree, down to the * limit contents depth. */ static int xhtml_do_contents_section_deep_limit(FILE *fp, xhtmlsection *section, int limit) { int count = 0; while (section) { if (!xhtml_add_contents_entry(fp, section, limit)) return 0; else count++; count += xhtml_do_contents_section_deep_limit(fp, section->child, limit); section = section->next; } return count; } /* * Write contents entries for the given section tree, down to the * limit contents depth. */ static int xhtml_do_contents_section_limit(FILE *fp, xhtmlsection *section, int limit) { int count = 0; if (!section) return 0; xhtml_add_contents_entry(fp, section, limit); count=1; count += xhtml_do_contents_section_deep_limit(fp, section->child, limit); /* section=section->child; while (section && xhtml_add_contents_entry(fp, section, limit)) { section = section->next; }*/ return count; } /* * Add a section entry, unless we're exceeding the limit, in which * case return FALSE (otherwise return TRUE). */ static int xhtml_add_contents_entry(FILE *fp, xhtmlsection *section, int limit) { if (!section || section->level > limit) return FALSE; if (fp==NULL || section->level < 0) return TRUE; if (last_level > section->level) { while (last_level > section->level) { last_level--; fprintf(fp, "\n"); } fprintf(fp, "\n"); } else if (last_level < section->level) { assert(last_level == section->level - 1); last_level++; fprintf(fp, "
    \n"); } else { fprintf(fp, "\n"); } fprintf(fp, "
  • ", section->file->filename, section->fragment); if (section->para->kwtext) { xhtml_para(fp, section->para->kwtext, FALSE); if (section->para->words) { fprintf(fp, ": "); } } if (section->para->words) { xhtml_para(fp, section->para->words, FALSE); } fprintf(fp, "\n"); return TRUE; } /* * Write all the sections in this file. Do all paragraphs in this section, then all * children (recursively), then go on to the next one (tail recursively). */ static void xhtml_do_sections(FILE *fp, xhtmlsection *sections) { while (sections) { currentsection = sections; xhtml_do_paras(fp, sections->para, NULL, TRUE); xhtml_do_sections(fp, sections->child); sections = sections->next; } } /* Write this list of paragraphs. Close off all lists at the end. */ static void xhtml_do_paras(FILE *fp, paragraph *p, paragraph *end, int indexable) { int last_type = -1, ptype, first=TRUE; stack lcont_stack = stk_new(); if (!p) return; /* for (; p && (xhtml_para_level(p)>limit || xhtml_para_level(p)==-1 || first); p=p->next) {*/ for (; p && p != end && (xhtml_para_level(p)==-1 || first); p=p->next) { first=FALSE; switch (ptype = p->type) { /* * Things we ignore because we've already processed them or * aren't going to touch them in this pass. */ case para_IM: case para_BR: case para_Biblio: /* only touch BiblioCited */ case para_VersionID: case para_NoCite: case para_Title: break; /* * Chapter titles. */ case para_Chapter: case para_Appendix: case para_UnnumberedChapter: xhtml_heading(fp, p, indexable); break; case para_Heading: case para_Subsect: xhtml_heading(fp, p, indexable); break; case para_Rule: fprintf(fp, "\n
    \n"); break; case para_Normal: case para_Copyright: fprintf(fp, "\n

    "); xhtml_para(fp, p->words, indexable); fprintf(fp, "

    \n"); break; case para_LcontPush: { int *p; p = mknew(int); *p = last_type; stk_push(lcont_stack, p); last_type = para_Normal; } break; case para_LcontPop: { int *p = stk_pop(lcont_stack); assert(p); ptype = last_type = *p; sfree(p); goto closeofflist; /* ick */ } break; case para_QuotePush: fprintf(fp, "
    \n"); break; case para_QuotePop: fprintf(fp, "
    \n"); break; case para_Bullet: case para_NumberedList: case para_Description: case para_DescribedThing: case para_BiblioCited: if (last_type!=p->type && !(last_type==para_DescribedThing && p->type==para_Description) && !(last_type==para_Description && p->type==para_DescribedThing)) { /* start up list if necessary */ if (p->type == para_Bullet) { fprintf(fp, "
      \n"); } else if (p->type == para_NumberedList) { fprintf(fp, "
        \n"); } else if (p->type == para_BiblioCited || p->type == para_DescribedThing || p->type == para_Description) { fprintf(fp, "
        \n"); } } if (p->type == para_Bullet || p->type == para_NumberedList) { fprintf(fp, "
      1. "); } else if (p->type == para_DescribedThing) { fprintf(fp, "
        "); } else if (p->type == para_Description) { fprintf(fp, "
        "); } else if (p->type == para_BiblioCited) { fprintf(fp, "
        "); xhtml_para(fp, p->kwtext, indexable); fprintf(fp, "
        \n
        "); } xhtml_para(fp, p->words, indexable); { paragraph *p2 = p->next; if (p2 && xhtml_para_level(p2)==-1 && p2->type == para_LcontPush) break; } closeofflist: if (ptype == para_BiblioCited) { fprintf(fp, "
        \n"); } else if (ptype == para_DescribedThing) { fprintf(fp, ""); } else if (ptype == para_Description) { fprintf(fp, ""); } else if (ptype == para_Bullet || ptype == para_NumberedList) { fprintf(fp, "
      2. "); } if (ptype == para_Bullet || ptype == para_NumberedList || ptype == para_BiblioCited || ptype == para_Description || ptype == para_DescribedThing) /* close off list if necessary */ { paragraph *p2 = p->next; int close_off=FALSE; /* if (p2 && (xhtml_para_level(p2)>limit || xhtml_para_level(p2)==-1)) {*/ if (p2 && xhtml_para_level(p2)==-1) { if (p2->type != ptype && !(p2->type==para_DescribedThing && ptype==para_Description) && !(p2->type==para_Description && ptype==para_DescribedThing) && p2->type != para_LcontPush) close_off=TRUE; } else { close_off=TRUE; } if (close_off) { if (ptype == para_Bullet) { fprintf(fp, "
    \n"); } else if (ptype == para_NumberedList) { fprintf(fp, "\n"); } else if (ptype == para_BiblioCited || ptype == para_Description || ptype == para_DescribedThing) { fprintf(fp, "\n"); } } } break; case para_Code: xhtml_codepara(fp, p->words); break; } last_type = ptype; } stk_free(lcont_stack); } /* * Output a header for this XHTML file. */ static void xhtml_doheader(FILE *fp, word *title) { fprintf(fp, "\n"); fprintf(fp, "\n\n\n"); if (title==NULL) fprintf(fp, "The thing with no name!"); else xhtml_para(fp, title, FALSE); fprintf(fp, "\n"); fprintf(fp, "\n", version); if (conf.author) fprintf(fp, "\n", conf.author); if (conf.description) fprintf(fp, "\n", conf.description); if (conf.head_end) fprintf(fp, "%ls\n", conf.head_end); fprintf(fp, "\n\n"); if (conf.body) fprintf(fp, "%ls\n", conf.body); else fprintf(fp, "\n"); if (conf.body_start) fprintf(fp, "%ls\n", conf.body_start); } /* * Output a footer for this XHTML file. */ static void xhtml_dofooter(FILE *fp) { fprintf(fp, "\n
    \n\n"); if (conf.body_end) fprintf(fp, "%ls\n", conf.body_end); if (!conf.suppress_address) { fprintf(fp,"
    \n"); if (conf.address_start) fprintf(fp, "%ls\n", conf.address_start); /* Do the version ID */ if (conf.include_version_id) { paragraph *p; int started = 0; for (p = sourceparas; p; p = p->next) if (p->type == para_VersionID) { xhtml_versionid(fp, p->words, started); started = 1; } } if (conf.address_end) fprintf(fp, "%ls\n", conf.address_end); fprintf(fp, "
    \n"); } fprintf(fp, "\n\n\n"); } /* * Output the versionid paragraph. Typically this is a version control * ID string (such as $Id...$ in RCS). */ static void xhtml_versionid(FILE *fp, word *text, int started) { rdstringc t = { 0, 0, NULL }; rdaddc(&t, '['); /* FIXME: configurability */ xhtml_rdaddwc(&t, text, NULL, FALSE); rdaddc(&t, ']'); /* FIXME: configurability */ if (started) fprintf(fp, "
    \n"); fprintf(fp, "%s\n", t.text); sfree(t.text); } /* Is this an XHTML reserved character? */ static int xhtml_reservedchar(int c) { if (c=='&' || c=='<' || c=='>' || c=='"') return TRUE; else return FALSE; } /* * Convert a wide string into valid XHTML: Anything outside ASCII will * be fixed up as an entity. Currently we don't worry about constraining the * encoded character set, which we should probably do at some point (we can * still fix up and return FALSE - see the last comment here). We also don't * currently * * Because this is only used for words, spaces are HARD spaces (any other * spaces will be word_Whitespace not word_Normal). So they become   * Unless hard_spaces is FALSE, of course (code paragraphs break the above * rule). * * If `result' is non-NULL, mallocs the resulting string and stores a pointer to * it in `*result'. If `result' is NULL, merely checks whether all * characters in the string are feasible. * * Return is nonzero if all characters are OK. If not all * characters are OK but `result' is non-NULL, a result _will_ * still be generated! */ static int xhtml_convert(wchar_t *s, int maxlen, char **result, int hard_spaces) { int doing = (result != 0); int ok = TRUE; char *p = NULL; int plen = 0, psize = 0; if (maxlen <= 0) maxlen = -1; for (; *s && maxlen != 0; s++, maxlen--) { wchar_t c = *s; #define ensure_size(i) if (i>=psize) { psize = i+256; p = resize(p, psize); } if (((c == 32 && !hard_spaces) || (c > 32 && c <= 126 && !xhtml_reservedchar(c)))) { /* Char is OK. */ if (doing) { ensure_size(plen); p[plen++] = (char)c; } } else { /* Char needs fixing up. */ /* ok = FALSE; -- currently we never return FALSE; we * might want to when considering a character set for the * encoded document. */ if (doing) { if (c==32) { /* a space in a word is a hard space */ ensure_size(plen+6); /* includes space for the NUL, which is subsequently stomped on */ sprintf(p+plen, " "); plen+=6; } else { /* FIXME: entity names! */ ensure_size(plen+8); /* includes space for the NUL, which is subsequently stomped on */ plen+=sprintf(p+plen, "&#%04i;", (int)c); } } } } if (doing) { p = resize(p, plen+1); p[plen] = '\0'; *result = p; } return ok; } /* * This formats the given words as XHTML. * * `indexable', if FALSE, prohibits adding any index references. * You might use this, for example, if an index reference occurred * in a section title, to prevent phony index references when the * section title is processed in strange places such as contents * sections. */ static void xhtml_rdaddwc(rdstringc *rs, word *text, word *end, int indexable) { char *c; keyword *kwl; xhtmlsection *sect; indextag *itag; int ti; for (; text && text != end; text = text->next) { switch (text->type) { case word_HyperLink: xhtml_utostr(text->text, &c); rdaddsc(rs, ""); sfree(c); break; case word_UpperXref: case word_LowerXref: kwl = kw_lookup(keywords, text->text); if (kwl) { sect=xhtml_find_section(kwl->para); if (sect) { rdaddsc(rs, "file->filename); rdaddc(rs, '#'); rdaddsc(rs, sect->fragment); rdaddsc(rs, "\">"); } else { rdaddsc(rs, ""); error(err_whatever, "Couldn't locate cross-reference! (Probably a bibliography entry.)"); } } else { rdaddsc(rs, ""); error(err_whatever, "Couldn't locate cross-reference! (Wasn't in source file.)"); } break; case word_IndexRef: /* in theory we could make an index target here */ /* rdaddsc(rs, "text, &c); rdaddsc(rs, c); sfree(c); rdaddsc(rs, "\">");*/ /* what we _do_ need to do is to fix up the backend data * for any indexentry this points to. */ if (!indexable) break; for (ti=0; (itag = (indextag *)index234(idx->tags, ti))!=NULL; ti++) { /* FIXME: really ustricmp() and not ustrcmp()? */ if (ustricmp(itag->name, text->text)==0) { break; } } if (itag!=NULL) { if (itag->refs!=NULL) { int i; for (i=0; inrefs; i++) { xhtmlindex *idx_ref; indexentry *ientry; ientry = itag->refs[i]; if (ientry->backend_data==NULL) { idx_ref = (xhtmlindex*) smalloc(sizeof(xhtmlindex)); if (idx_ref==NULL) fatal(err_nomemory); idx_ref->nsection = 0; idx_ref->size = 4; idx_ref->sections = (xhtmlsection**) smalloc(idx_ref->size * sizeof(xhtmlsection*)); if (idx_ref->sections==NULL) fatal(err_nomemory); ientry->backend_data = idx_ref; } else { idx_ref = ientry->backend_data; if (idx_ref->nsection+1 > idx_ref->size) { int new_size = idx_ref->size * 2; idx_ref->sections = srealloc(idx_ref->sections, new_size * sizeof(xhtmlsection)); if (idx_ref->sections==NULL) { fatal(err_nomemory); } idx_ref->size = new_size; } } idx_ref->sections[idx_ref->nsection++] = currentsection; #if 0 #endif } } else { fatal(err_whatever, "Index tag had no entries!"); } } else { fprintf(stderr, "Looking for index entry '%ls'\n", text->text); fatal(err_whatever, "Couldn't locate index entry! (Wasn't in index.)"); } break; case word_HyperEnd: case word_XrefEnd: rdaddsc(rs, ""); break; case word_Normal: case word_Emph: case word_Code: case word_WeakCode: case word_WhiteSpace: case word_EmphSpace: case word_CodeSpace: case word_WkCodeSpace: case word_Quote: case word_EmphQuote: case word_CodeQuote: case word_WkCodeQuote: assert(text->type != word_CodeQuote && text->type != word_WkCodeQuote); if (towordstyle(text->type) == word_Emph && (attraux(text->aux) == attr_First || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); else if ((towordstyle(text->type) == word_Code || towordstyle(text->type) == word_WeakCode) && (attraux(text->aux) == attr_First || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); if (removeattr(text->type) == word_Normal) { if (xhtml_convert(text->text, 0, &c, TRUE)) /* spaces in the word are hard */ rdaddsc(rs, c); else xhtml_rdaddwc(rs, text->alt, NULL, indexable); sfree(c); } else if (removeattr(text->type) == word_WhiteSpace) { rdaddc(rs, ' '); } else if (removeattr(text->type) == word_Quote) { rdaddsc(rs, """); } if (towordstyle(text->type) == word_Emph && (attraux(text->aux) == attr_Last || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); else if ((towordstyle(text->type) == word_Code || towordstyle(text->type) == word_WeakCode) && (attraux(text->aux) == attr_Last || attraux(text->aux) == attr_Only)) rdaddsc(rs, ""); break; } } } /* Output a heading, formatted as XHTML. */ static void xhtml_heading(FILE *fp, paragraph *p, int indexable) { rdstringc t = { 0, 0, NULL }; word *tprefix = p->kwtext; word *nprefix = p->kwtext2; word *text = p->words; int level = xhtml_para_level(p); xhtmlsection *sect = xhtml_find_section(p); xhtmlheadfmt *fmt; char *fragment; if (sect) { fragment = sect->fragment; } else { if (p->type == para_Title) fragment = "title"; else { fragment = ""; /* FIXME: what else can we do? */ error(err_whatever, "Couldn't locate heading cross-reference!"); } } if (p->type == para_Title) fmt = NULL; else if (level == 1) fmt = &conf.fchapter; else if (level-1 < conf.nfsect) fmt = &conf.fsect[level-1]; else fmt = &conf.fsect[conf.nfsect-1]; if (fmt && fmt->just_numbers && nprefix) { xhtml_rdaddwc(&t, nprefix, NULL, indexable); if (fmt) { char *c; if (xhtml_convert(fmt->number_suffix, 0, &c, FALSE)) { rdaddsc(&t, c); sfree(c); } } } else if (fmt && !fmt->just_numbers && tprefix) { xhtml_rdaddwc(&t, tprefix, NULL, indexable); if (fmt) { char *c; if (xhtml_convert(fmt->number_suffix, 0, &c, FALSE)) { rdaddsc(&t, c); sfree(c); } } } xhtml_rdaddwc(&t, text, NULL, indexable); /* * If we're outputting in single-file mode, we need to lower * the level of each heading by one, because the overall * document title will be sitting right at the top as an

    * and so chapters and sections should start at

    . * * Even if not, the document title will come back from * xhtml_para_level() as level zero, so we must increment that * no matter what leaf_level is set to. */ if (conf.leaf_level == 0 || level == 0) level++; fprintf(fp, "%s\n", fragment, level, t.text, level); sfree(t.text); } /* Output a paragraph. Styles are handled by xhtml_rdaddwc(). * This looks pretty simple; I may have missed something ... */ static void xhtml_para(FILE *fp, word *text, int indexable) { rdstringc out = { 0, 0, NULL }; xhtml_rdaddwc(&out, text, NULL, indexable); fprintf(fp, "%s", out.text); sfree(out.text); } /* Output a code paragraph. I'm treating this as preformatted, which * may not be entirely correct. See xhtml_para() for my worries about * this being overly-simple; however I think that most of the complexity * of the text backend came entirely out of word wrapping anyway. */ static void xhtml_codepara(FILE *fp, word *text) { fprintf(fp, "
    ");
        for (; text; text = text->next) if (text->type == word_WeakCode) {
    	word *here, *next;
    	char *c;
    
    	/*
    	 * See if this WeakCode is followed by an Emph to indicate
    	 * emphasis.
    	 */
    	here = text;
    	if (text->next && text->next->type == word_Emph) {
    	    next = text = text->next;
    	} else
    	    next = NULL;
    
    	if (next) {
    	    wchar_t *t, *e;
    	    int n;
    
    	    t = here->text;
    	    e = next->text;
    
    	    while (*e) {
    		int ec = *e;
    
    		for (n = 0; t[n] && e[n] && e[n] == ec; n++);
    		xhtml_convert(t, n, &c, FALSE);
    		fprintf(fp, "%s%s%s",
    			(ec == 'i' ? "" : ec == 'b' ? "" : ""),
    			c,
    			(ec == 'i' ? "" : ec == 'b' ? "" : ""));
    		sfree(c);
    
    		t += n;
    		e += n;
    	    }
    
    	    xhtml_convert(t, 0, &c, FALSE);
    	    fprintf(fp, "%s\n", c);
    	    sfree(c);
    	} else {
    	    xhtml_convert(here->text, 0, &c, FALSE);
    	    fprintf(fp, "%s\n", c);
    	    sfree(c);
    	}
        }
      fprintf(fp, "
    \n"); }