Merge branch 'vendor/MDOCML'
[dragonfly.git] / contrib / mdocml / mdoc_html.c
1 /*      $Id: mdoc_html.c,v 1.328 2019/03/01 10:57:18 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2014-2019 Ingo Schwarze <schwarze@openbsd.org>
5  *
6  * Permission to use, copy, modify, and distribute this software for any
7  * purpose with or without fee is hereby granted, provided that the above
8  * copyright notice and this permission notice appear in all copies.
9  *
10  * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13  * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14  * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15  * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16  * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17  */
18 #include "config.h"
19
20 #include <sys/types.h>
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <stdio.h>
25 #include <stdlib.h>
26 #include <string.h>
27 #include <unistd.h>
28
29 #include "mandoc_aux.h"
30 #include "mandoc.h"
31 #include "roff.h"
32 #include "mdoc.h"
33 #include "out.h"
34 #include "html.h"
35 #include "main.h"
36
37 #define MDOC_ARGS         const struct roff_meta *meta, \
38                           struct roff_node *n, \
39                           struct html *h
40
41 #ifndef MIN
42 #define MIN(a,b)        ((/*CONSTCOND*/(a)<(b))?(a):(b))
43 #endif
44
45 struct  mdoc_html_act {
46         int             (*pre)(MDOC_ARGS);
47         void            (*post)(MDOC_ARGS);
48 };
49
50 static  char             *cond_id(const struct roff_node *);
51 static  void              print_mdoc_head(const struct roff_meta *,
52                                 struct html *);
53 static  void              print_mdoc_node(MDOC_ARGS);
54 static  void              print_mdoc_nodelist(MDOC_ARGS);
55 static  void              synopsis_pre(struct html *,
56                                 const struct roff_node *);
57
58 static  void              mdoc_root_post(const struct roff_meta *,
59                                 struct html *);
60 static  int               mdoc_root_pre(const struct roff_meta *,
61                                 struct html *);
62
63 static  void              mdoc__x_post(MDOC_ARGS);
64 static  int               mdoc__x_pre(MDOC_ARGS);
65 static  int               mdoc_abort_pre(MDOC_ARGS);
66 static  int               mdoc_ad_pre(MDOC_ARGS);
67 static  int               mdoc_an_pre(MDOC_ARGS);
68 static  int               mdoc_ap_pre(MDOC_ARGS);
69 static  int               mdoc_ar_pre(MDOC_ARGS);
70 static  int               mdoc_bd_pre(MDOC_ARGS);
71 static  int               mdoc_bf_pre(MDOC_ARGS);
72 static  void              mdoc_bk_post(MDOC_ARGS);
73 static  int               mdoc_bk_pre(MDOC_ARGS);
74 static  int               mdoc_bl_pre(MDOC_ARGS);
75 static  int               mdoc_cd_pre(MDOC_ARGS);
76 static  int               mdoc_cm_pre(MDOC_ARGS);
77 static  int               mdoc_d1_pre(MDOC_ARGS);
78 static  int               mdoc_dv_pre(MDOC_ARGS);
79 static  int               mdoc_fa_pre(MDOC_ARGS);
80 static  int               mdoc_fd_pre(MDOC_ARGS);
81 static  int               mdoc_fl_pre(MDOC_ARGS);
82 static  int               mdoc_fn_pre(MDOC_ARGS);
83 static  int               mdoc_ft_pre(MDOC_ARGS);
84 static  int               mdoc_em_pre(MDOC_ARGS);
85 static  void              mdoc_eo_post(MDOC_ARGS);
86 static  int               mdoc_eo_pre(MDOC_ARGS);
87 static  int               mdoc_er_pre(MDOC_ARGS);
88 static  int               mdoc_ev_pre(MDOC_ARGS);
89 static  int               mdoc_ex_pre(MDOC_ARGS);
90 static  void              mdoc_fo_post(MDOC_ARGS);
91 static  int               mdoc_fo_pre(MDOC_ARGS);
92 static  int               mdoc_ic_pre(MDOC_ARGS);
93 static  int               mdoc_igndelim_pre(MDOC_ARGS);
94 static  int               mdoc_in_pre(MDOC_ARGS);
95 static  int               mdoc_it_pre(MDOC_ARGS);
96 static  int               mdoc_lb_pre(MDOC_ARGS);
97 static  int               mdoc_li_pre(MDOC_ARGS);
98 static  int               mdoc_lk_pre(MDOC_ARGS);
99 static  int               mdoc_mt_pre(MDOC_ARGS);
100 static  int               mdoc_ms_pre(MDOC_ARGS);
101 static  int               mdoc_nd_pre(MDOC_ARGS);
102 static  int               mdoc_nm_pre(MDOC_ARGS);
103 static  int               mdoc_no_pre(MDOC_ARGS);
104 static  int               mdoc_ns_pre(MDOC_ARGS);
105 static  int               mdoc_pa_pre(MDOC_ARGS);
106 static  void              mdoc_pf_post(MDOC_ARGS);
107 static  int               mdoc_pp_pre(MDOC_ARGS);
108 static  void              mdoc_quote_post(MDOC_ARGS);
109 static  int               mdoc_quote_pre(MDOC_ARGS);
110 static  int               mdoc_rs_pre(MDOC_ARGS);
111 static  int               mdoc_sh_pre(MDOC_ARGS);
112 static  int               mdoc_skip_pre(MDOC_ARGS);
113 static  int               mdoc_sm_pre(MDOC_ARGS);
114 static  int               mdoc_ss_pre(MDOC_ARGS);
115 static  int               mdoc_st_pre(MDOC_ARGS);
116 static  int               mdoc_sx_pre(MDOC_ARGS);
117 static  int               mdoc_sy_pre(MDOC_ARGS);
118 static  int               mdoc_va_pre(MDOC_ARGS);
119 static  int               mdoc_vt_pre(MDOC_ARGS);
120 static  int               mdoc_xr_pre(MDOC_ARGS);
121 static  int               mdoc_xx_pre(MDOC_ARGS);
122
123 static const struct mdoc_html_act mdoc_html_acts[MDOC_MAX - MDOC_Dd] = {
124         {NULL, NULL}, /* Dd */
125         {NULL, NULL}, /* Dt */
126         {NULL, NULL}, /* Os */
127         {mdoc_sh_pre, NULL }, /* Sh */
128         {mdoc_ss_pre, NULL }, /* Ss */
129         {mdoc_pp_pre, NULL}, /* Pp */
130         {mdoc_d1_pre, NULL}, /* D1 */
131         {mdoc_d1_pre, NULL}, /* Dl */
132         {mdoc_bd_pre, NULL}, /* Bd */
133         {NULL, NULL}, /* Ed */
134         {mdoc_bl_pre, NULL}, /* Bl */
135         {NULL, NULL}, /* El */
136         {mdoc_it_pre, NULL}, /* It */
137         {mdoc_ad_pre, NULL}, /* Ad */
138         {mdoc_an_pre, NULL}, /* An */
139         {mdoc_ap_pre, NULL}, /* Ap */
140         {mdoc_ar_pre, NULL}, /* Ar */
141         {mdoc_cd_pre, NULL}, /* Cd */
142         {mdoc_cm_pre, NULL}, /* Cm */
143         {mdoc_dv_pre, NULL}, /* Dv */
144         {mdoc_er_pre, NULL}, /* Er */
145         {mdoc_ev_pre, NULL}, /* Ev */
146         {mdoc_ex_pre, NULL}, /* Ex */
147         {mdoc_fa_pre, NULL}, /* Fa */
148         {mdoc_fd_pre, NULL}, /* Fd */
149         {mdoc_fl_pre, NULL}, /* Fl */
150         {mdoc_fn_pre, NULL}, /* Fn */
151         {mdoc_ft_pre, NULL}, /* Ft */
152         {mdoc_ic_pre, NULL}, /* Ic */
153         {mdoc_in_pre, NULL}, /* In */
154         {mdoc_li_pre, NULL}, /* Li */
155         {mdoc_nd_pre, NULL}, /* Nd */
156         {mdoc_nm_pre, NULL}, /* Nm */
157         {mdoc_quote_pre, mdoc_quote_post}, /* Op */
158         {mdoc_abort_pre, NULL}, /* Ot */
159         {mdoc_pa_pre, NULL}, /* Pa */
160         {mdoc_ex_pre, NULL}, /* Rv */
161         {mdoc_st_pre, NULL}, /* St */
162         {mdoc_va_pre, NULL}, /* Va */
163         {mdoc_vt_pre, NULL}, /* Vt */
164         {mdoc_xr_pre, NULL}, /* Xr */
165         {mdoc__x_pre, mdoc__x_post}, /* %A */
166         {mdoc__x_pre, mdoc__x_post}, /* %B */
167         {mdoc__x_pre, mdoc__x_post}, /* %D */
168         {mdoc__x_pre, mdoc__x_post}, /* %I */
169         {mdoc__x_pre, mdoc__x_post}, /* %J */
170         {mdoc__x_pre, mdoc__x_post}, /* %N */
171         {mdoc__x_pre, mdoc__x_post}, /* %O */
172         {mdoc__x_pre, mdoc__x_post}, /* %P */
173         {mdoc__x_pre, mdoc__x_post}, /* %R */
174         {mdoc__x_pre, mdoc__x_post}, /* %T */
175         {mdoc__x_pre, mdoc__x_post}, /* %V */
176         {NULL, NULL}, /* Ac */
177         {mdoc_quote_pre, mdoc_quote_post}, /* Ao */
178         {mdoc_quote_pre, mdoc_quote_post}, /* Aq */
179         {mdoc_xx_pre, NULL}, /* At */
180         {NULL, NULL}, /* Bc */
181         {mdoc_bf_pre, NULL}, /* Bf */
182         {mdoc_quote_pre, mdoc_quote_post}, /* Bo */
183         {mdoc_quote_pre, mdoc_quote_post}, /* Bq */
184         {mdoc_xx_pre, NULL}, /* Bsx */
185         {mdoc_xx_pre, NULL}, /* Bx */
186         {mdoc_skip_pre, NULL}, /* Db */
187         {NULL, NULL}, /* Dc */
188         {mdoc_quote_pre, mdoc_quote_post}, /* Do */
189         {mdoc_quote_pre, mdoc_quote_post}, /* Dq */
190         {NULL, NULL}, /* Ec */ /* FIXME: no space */
191         {NULL, NULL}, /* Ef */
192         {mdoc_em_pre, NULL}, /* Em */
193         {mdoc_eo_pre, mdoc_eo_post}, /* Eo */
194         {mdoc_xx_pre, NULL}, /* Fx */
195         {mdoc_ms_pre, NULL}, /* Ms */
196         {mdoc_no_pre, NULL}, /* No */
197         {mdoc_ns_pre, NULL}, /* Ns */
198         {mdoc_xx_pre, NULL}, /* Nx */
199         {mdoc_xx_pre, NULL}, /* Ox */
200         {NULL, NULL}, /* Pc */
201         {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
202         {mdoc_quote_pre, mdoc_quote_post}, /* Po */
203         {mdoc_quote_pre, mdoc_quote_post}, /* Pq */
204         {NULL, NULL}, /* Qc */
205         {mdoc_quote_pre, mdoc_quote_post}, /* Ql */
206         {mdoc_quote_pre, mdoc_quote_post}, /* Qo */
207         {mdoc_quote_pre, mdoc_quote_post}, /* Qq */
208         {NULL, NULL}, /* Re */
209         {mdoc_rs_pre, NULL}, /* Rs */
210         {NULL, NULL}, /* Sc */
211         {mdoc_quote_pre, mdoc_quote_post}, /* So */
212         {mdoc_quote_pre, mdoc_quote_post}, /* Sq */
213         {mdoc_sm_pre, NULL}, /* Sm */
214         {mdoc_sx_pre, NULL}, /* Sx */
215         {mdoc_sy_pre, NULL}, /* Sy */
216         {NULL, NULL}, /* Tn */
217         {mdoc_xx_pre, NULL}, /* Ux */
218         {NULL, NULL}, /* Xc */
219         {NULL, NULL}, /* Xo */
220         {mdoc_fo_pre, mdoc_fo_post}, /* Fo */
221         {NULL, NULL}, /* Fc */
222         {mdoc_quote_pre, mdoc_quote_post}, /* Oo */
223         {NULL, NULL}, /* Oc */
224         {mdoc_bk_pre, mdoc_bk_post}, /* Bk */
225         {NULL, NULL}, /* Ek */
226         {NULL, NULL}, /* Bt */
227         {NULL, NULL}, /* Hf */
228         {mdoc_em_pre, NULL}, /* Fr */
229         {NULL, NULL}, /* Ud */
230         {mdoc_lb_pre, NULL}, /* Lb */
231         {mdoc_abort_pre, NULL}, /* Lp */
232         {mdoc_lk_pre, NULL}, /* Lk */
233         {mdoc_mt_pre, NULL}, /* Mt */
234         {mdoc_quote_pre, mdoc_quote_post}, /* Brq */
235         {mdoc_quote_pre, mdoc_quote_post}, /* Bro */
236         {NULL, NULL}, /* Brc */
237         {mdoc__x_pre, mdoc__x_post}, /* %C */
238         {mdoc_skip_pre, NULL}, /* Es */
239         {mdoc_quote_pre, mdoc_quote_post}, /* En */
240         {mdoc_xx_pre, NULL}, /* Dx */
241         {mdoc__x_pre, mdoc__x_post}, /* %Q */
242         {mdoc__x_pre, mdoc__x_post}, /* %U */
243         {NULL, NULL}, /* Ta */
244 };
245
246
247 /*
248  * See the same function in mdoc_term.c for documentation.
249  */
250 static void
251 synopsis_pre(struct html *h, const struct roff_node *n)
252 {
253
254         if (NULL == n->prev || ! (NODE_SYNPRETTY & n->flags))
255                 return;
256
257         if (n->prev->tok == n->tok &&
258             MDOC_Fo != n->tok &&
259             MDOC_Ft != n->tok &&
260             MDOC_Fn != n->tok) {
261                 print_otag(h, TAG_BR, "");
262                 return;
263         }
264
265         switch (n->prev->tok) {
266         case MDOC_Fd:
267         case MDOC_Fn:
268         case MDOC_Fo:
269         case MDOC_In:
270         case MDOC_Vt:
271                 break;
272         case MDOC_Ft:
273                 if (n->tok != MDOC_Fn && n->tok != MDOC_Fo)
274                         break;
275                 /* FALLTHROUGH */
276         default:
277                 print_otag(h, TAG_BR, "");
278                 return;
279         }
280         html_close_paragraph(h);
281         print_otag(h, TAG_P, "c", "Pp");
282 }
283
284 void
285 html_mdoc(void *arg, const struct roff_meta *mdoc)
286 {
287         struct html             *h;
288         struct roff_node        *n;
289         struct tag              *t;
290
291         h = (struct html *)arg;
292         n = mdoc->first->child;
293
294         if ((h->oflags & HTML_FRAGMENT) == 0) {
295                 print_gen_decls(h);
296                 print_otag(h, TAG_HTML, "");
297                 if (n != NULL && n->type == ROFFT_COMMENT)
298                         print_gen_comment(h, n);
299                 t = print_otag(h, TAG_HEAD, "");
300                 print_mdoc_head(mdoc, h);
301                 print_tagq(h, t);
302                 print_otag(h, TAG_BODY, "");
303         }
304
305         mdoc_root_pre(mdoc, h);
306         t = print_otag(h, TAG_DIV, "c", "manual-text");
307         print_mdoc_nodelist(mdoc, n, h);
308         print_tagq(h, t);
309         mdoc_root_post(mdoc, h);
310         print_tagq(h, NULL);
311 }
312
313 static void
314 print_mdoc_head(const struct roff_meta *meta, struct html *h)
315 {
316         char    *cp;
317
318         print_gen_head(h);
319
320         if (meta->arch != NULL && meta->msec != NULL)
321                 mandoc_asprintf(&cp, "%s(%s) (%s)", meta->title,
322                     meta->msec, meta->arch);
323         else if (meta->msec != NULL)
324                 mandoc_asprintf(&cp, "%s(%s)", meta->title, meta->msec);
325         else if (meta->arch != NULL)
326                 mandoc_asprintf(&cp, "%s (%s)", meta->title, meta->arch);
327         else
328                 cp = mandoc_strdup(meta->title);
329
330         print_otag(h, TAG_TITLE, "");
331         print_text(h, cp);
332         free(cp);
333 }
334
335 static void
336 print_mdoc_nodelist(MDOC_ARGS)
337 {
338
339         while (n != NULL) {
340                 print_mdoc_node(meta, n, h);
341                 n = n->next;
342         }
343 }
344
345 static void
346 print_mdoc_node(MDOC_ARGS)
347 {
348         struct tag      *t;
349         int              child;
350
351         if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT)
352                 return;
353
354         html_fillmode(h, n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi);
355
356         child = 1;
357         n->flags &= ~NODE_ENDED;
358         switch (n->type) {
359         case ROFFT_TEXT:
360                 t = h->tag;
361                 t->refcnt++;
362
363                 /* No tables in this mode... */
364                 assert(NULL == h->tblt);
365
366                 /*
367                  * Make sure that if we're in a literal mode already
368                  * (i.e., within a <PRE>) don't print the newline.
369                  */
370                 if (*n->string == ' ' && n->flags & NODE_LINE &&
371                     (h->flags & HTML_NONEWLINE) == 0 &&
372                     (n->flags & NODE_NOFILL) == 0)
373                         print_otag(h, TAG_BR, "");
374                 if (NODE_DELIMC & n->flags)
375                         h->flags |= HTML_NOSPACE;
376                 print_text(h, n->string);
377                 if (NODE_DELIMO & n->flags)
378                         h->flags |= HTML_NOSPACE;
379                 break;
380         case ROFFT_EQN:
381                 t = h->tag;
382                 t->refcnt++;
383                 print_eqn(h, n->eqn);
384                 break;
385         case ROFFT_TBL:
386                 /*
387                  * This will take care of initialising all of the table
388                  * state data for the first table, then tearing it down
389                  * for the last one.
390                  */
391                 print_tbl(h, n->span);
392                 return;
393         default:
394                 /*
395                  * Close out the current table, if it's open, and unset
396                  * the "meta" table state.  This will be reopened on the
397                  * next table element.
398                  */
399                 if (h->tblt != NULL)
400                         print_tblclose(h);
401                 assert(h->tblt == NULL);
402                 t = h->tag;
403                 t->refcnt++;
404                 if (n->tok < ROFF_MAX) {
405                         roff_html_pre(h, n);
406                         t->refcnt--;
407                         print_stagq(h, t);
408                         return;
409                 }
410                 assert(n->tok >= MDOC_Dd && n->tok < MDOC_MAX);
411                 if (mdoc_html_acts[n->tok - MDOC_Dd].pre != NULL &&
412                     (n->end == ENDBODY_NOT || n->child != NULL))
413                         child = (*mdoc_html_acts[n->tok - MDOC_Dd].pre)(meta,
414                             n, h);
415                 break;
416         }
417
418         if (h->flags & HTML_KEEP && n->flags & NODE_LINE) {
419                 h->flags &= ~HTML_KEEP;
420                 h->flags |= HTML_PREKEEP;
421         }
422
423         if (child && n->child != NULL)
424                 print_mdoc_nodelist(meta, n->child, h);
425
426         t->refcnt--;
427         print_stagq(h, t);
428
429         switch (n->type) {
430         case ROFFT_TEXT:
431         case ROFFT_EQN:
432                 break;
433         default:
434                 if (mdoc_html_acts[n->tok - MDOC_Dd].post == NULL ||
435                     n->flags & NODE_ENDED)
436                         break;
437                 (*mdoc_html_acts[n->tok - MDOC_Dd].post)(meta, n, h);
438                 if (n->end != ENDBODY_NOT)
439                         n->body->flags |= NODE_ENDED;
440                 break;
441         }
442
443         if (n->flags & NODE_NOFILL &&
444             (n->next == NULL || n->next->flags & NODE_LINE)) {
445                 h->col++;
446                 print_endline(h);
447         }
448 }
449
450 static void
451 mdoc_root_post(const struct roff_meta *meta, struct html *h)
452 {
453         struct tag      *t, *tt;
454
455         t = print_otag(h, TAG_TABLE, "c", "foot");
456         tt = print_otag(h, TAG_TR, "");
457
458         print_otag(h, TAG_TD, "c", "foot-date");
459         print_text(h, meta->date);
460         print_stagq(h, tt);
461
462         print_otag(h, TAG_TD, "c", "foot-os");
463         print_text(h, meta->os);
464         print_tagq(h, t);
465 }
466
467 static int
468 mdoc_root_pre(const struct roff_meta *meta, struct html *h)
469 {
470         struct tag      *t, *tt;
471         char            *volume, *title;
472
473         if (NULL == meta->arch)
474                 volume = mandoc_strdup(meta->vol);
475         else
476                 mandoc_asprintf(&volume, "%s (%s)",
477                     meta->vol, meta->arch);
478
479         if (NULL == meta->msec)
480                 title = mandoc_strdup(meta->title);
481         else
482                 mandoc_asprintf(&title, "%s(%s)",
483                     meta->title, meta->msec);
484
485         t = print_otag(h, TAG_TABLE, "c", "head");
486         tt = print_otag(h, TAG_TR, "");
487
488         print_otag(h, TAG_TD, "c", "head-ltitle");
489         print_text(h, title);
490         print_stagq(h, tt);
491
492         print_otag(h, TAG_TD, "c", "head-vol");
493         print_text(h, volume);
494         print_stagq(h, tt);
495
496         print_otag(h, TAG_TD, "c", "head-rtitle");
497         print_text(h, title);
498         print_tagq(h, t);
499
500         free(title);
501         free(volume);
502         return 1;
503 }
504
505 static char *
506 cond_id(const struct roff_node *n)
507 {
508         if (n->child != NULL &&
509             n->child->type == ROFFT_TEXT &&
510             (n->prev == NULL ||
511              (n->prev->type == ROFFT_TEXT &&
512               strcmp(n->prev->string, "|") == 0)) &&
513             (n->parent->tok == MDOC_It ||
514              (n->parent->tok == MDOC_Xo &&
515               n->parent->parent->prev == NULL &&
516               n->parent->parent->parent->tok == MDOC_It)))
517                 return html_make_id(n, 1);
518         return NULL;
519 }
520
521 static int
522 mdoc_sh_pre(MDOC_ARGS)
523 {
524         struct roff_node        *sn, *subn;
525         struct tag              *t, *tsec, *tsub;
526         char                    *id;
527         int                      sc;
528
529         switch (n->type) {
530         case ROFFT_BLOCK:
531                 html_close_paragraph(h);
532                 if ((h->oflags & HTML_TOC) == 0 ||
533                     h->flags & HTML_TOCDONE ||
534                     n->sec <= SEC_SYNOPSIS) {
535                         print_otag(h, TAG_SECTION, "c", "Sh");
536                         break;
537                 }
538                 h->flags |= HTML_TOCDONE;
539                 sc = 0;
540                 for (sn = n->next; sn != NULL; sn = sn->next)
541                         if (sn->sec == SEC_CUSTOM)
542                                 if (++sc == 2)
543                                         break;
544                 if (sc < 2)
545                         break;
546                 t = print_otag(h, TAG_H1, "c", "Sh");
547                 print_text(h, "TABLE OF CONTENTS");
548                 print_tagq(h, t);
549                 t = print_otag(h, TAG_UL, "c", "Bl-compact");
550                 for (sn = n; sn != NULL; sn = sn->next) {
551                         tsec = print_otag(h, TAG_LI, "");
552                         id = html_make_id(sn->head, 0);
553                         tsub = print_otag(h, TAG_A, "hR", id);
554                         free(id);
555                         print_mdoc_nodelist(meta, sn->head->child, h);
556                         print_tagq(h, tsub);
557                         tsub = NULL;
558                         for (subn = sn->body->child; subn != NULL;
559                             subn = subn->next) {
560                                 if (subn->tok != MDOC_Ss)
561                                         continue;
562                                 id = html_make_id(subn->head, 0);
563                                 if (id == NULL)
564                                         continue;
565                                 if (tsub == NULL)
566                                         print_otag(h, TAG_UL,
567                                             "c", "Bl-compact");
568                                 tsub = print_otag(h, TAG_LI, "");
569                                 print_otag(h, TAG_A, "hR", id);
570                                 free(id);
571                                 print_mdoc_nodelist(meta,
572                                     subn->head->child, h);
573                                 print_tagq(h, tsub);
574                         }
575                         print_tagq(h, tsec);
576                 }
577                 print_tagq(h, t);
578                 print_otag(h, TAG_SECTION, "c", "Sh");
579                 break;
580         case ROFFT_HEAD:
581                 id = html_make_id(n, 1);
582                 print_otag(h, TAG_H1, "ci", "Sh", id);
583                 if (id != NULL)
584                         print_otag(h, TAG_A, "chR", "permalink", id);
585                 break;
586         case ROFFT_BODY:
587                 if (n->sec == SEC_AUTHORS)
588                         h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
589                 break;
590         default:
591                 break;
592         }
593         return 1;
594 }
595
596 static int
597 mdoc_ss_pre(MDOC_ARGS)
598 {
599         char    *id;
600
601         switch (n->type) {
602         case ROFFT_BLOCK:
603                 html_close_paragraph(h);
604                 print_otag(h, TAG_SECTION, "c", "Ss");
605                 return 1;
606         case ROFFT_HEAD:
607                 break;
608         case ROFFT_BODY:
609                 return 1;
610         default:
611                 abort();
612         }
613
614         id = html_make_id(n, 1);
615         print_otag(h, TAG_H2, "ci", "Ss", id);
616         if (id != NULL)
617                 print_otag(h, TAG_A, "chR", "permalink", id);
618         return 1;
619 }
620
621 static int
622 mdoc_fl_pre(MDOC_ARGS)
623 {
624         char    *id;
625
626         if ((id = cond_id(n)) != NULL)
627                 print_otag(h, TAG_A, "chR", "permalink", id);
628         print_otag(h, TAG_CODE, "ci", "Fl", id);
629
630         print_text(h, "\\-");
631         if (!(n->child == NULL &&
632             (n->next == NULL ||
633              n->next->type == ROFFT_TEXT ||
634              n->next->flags & NODE_LINE)))
635                 h->flags |= HTML_NOSPACE;
636
637         return 1;
638 }
639
640 static int
641 mdoc_cm_pre(MDOC_ARGS)
642 {
643         char    *id;
644
645         if ((id = cond_id(n)) != NULL)
646                 print_otag(h, TAG_A, "chR", "permalink", id);
647         print_otag(h, TAG_CODE, "ci", "Cm", id);
648         return 1;
649 }
650
651 static int
652 mdoc_nd_pre(MDOC_ARGS)
653 {
654         switch (n->type) {
655         case ROFFT_BLOCK:
656                 html_close_paragraph(h);
657                 return 1;
658         case ROFFT_HEAD:
659                 return 0;
660         case ROFFT_BODY:
661                 break;
662         default:
663                 abort();
664         }
665         print_text(h, "\\(em");
666         /* Cannot use TAG_SPAN because it may contain blocks. */
667         print_otag(h, TAG_DIV, "c", "Nd");
668         return 1;
669 }
670
671 static int
672 mdoc_nm_pre(MDOC_ARGS)
673 {
674         switch (n->type) {
675         case ROFFT_BLOCK:
676                 break;
677         case ROFFT_HEAD:
678                 print_otag(h, TAG_TD, "");
679                 /* FALLTHROUGH */
680         case ROFFT_ELEM:
681                 print_otag(h, TAG_CODE, "c", "Nm");
682                 return 1;
683         case ROFFT_BODY:
684                 print_otag(h, TAG_TD, "");
685                 return 1;
686         default:
687                 abort();
688         }
689         html_close_paragraph(h);
690         synopsis_pre(h, n);
691         print_otag(h, TAG_TABLE, "c", "Nm");
692         print_otag(h, TAG_TR, "");
693         return 1;
694 }
695
696 static int
697 mdoc_xr_pre(MDOC_ARGS)
698 {
699         if (NULL == n->child)
700                 return 0;
701
702         if (h->base_man1)
703                 print_otag(h, TAG_A, "chM", "Xr",
704                     n->child->string, n->child->next == NULL ?
705                     NULL : n->child->next->string);
706         else
707                 print_otag(h, TAG_A, "c", "Xr");
708
709         n = n->child;
710         print_text(h, n->string);
711
712         if (NULL == (n = n->next))
713                 return 0;
714
715         h->flags |= HTML_NOSPACE;
716         print_text(h, "(");
717         h->flags |= HTML_NOSPACE;
718         print_text(h, n->string);
719         h->flags |= HTML_NOSPACE;
720         print_text(h, ")");
721         return 0;
722 }
723
724 static int
725 mdoc_ns_pre(MDOC_ARGS)
726 {
727
728         if ( ! (NODE_LINE & n->flags))
729                 h->flags |= HTML_NOSPACE;
730         return 1;
731 }
732
733 static int
734 mdoc_ar_pre(MDOC_ARGS)
735 {
736         print_otag(h, TAG_VAR, "c", "Ar");
737         return 1;
738 }
739
740 static int
741 mdoc_xx_pre(MDOC_ARGS)
742 {
743         print_otag(h, TAG_SPAN, "c", "Ux");
744         return 1;
745 }
746
747 static int
748 mdoc_it_pre(MDOC_ARGS)
749 {
750         const struct roff_node  *bl;
751         enum mdoc_list           type;
752
753         bl = n->parent;
754         while (bl->tok != MDOC_Bl)
755                 bl = bl->parent;
756         type = bl->norm->Bl.type;
757
758         switch (type) {
759         case LIST_bullet:
760         case LIST_dash:
761         case LIST_hyphen:
762         case LIST_item:
763         case LIST_enum:
764                 switch (n->type) {
765                 case ROFFT_HEAD:
766                         return 0;
767                 case ROFFT_BODY:
768                         print_otag(h, TAG_LI, "");
769                         break;
770                 default:
771                         break;
772                 }
773                 break;
774         case LIST_diag:
775         case LIST_hang:
776         case LIST_inset:
777         case LIST_ohang:
778                 switch (n->type) {
779                 case ROFFT_HEAD:
780                         print_otag(h, TAG_DT, "");
781                         break;
782                 case ROFFT_BODY:
783                         print_otag(h, TAG_DD, "");
784                         break;
785                 default:
786                         break;
787                 }
788                 break;
789         case LIST_tag:
790                 switch (n->type) {
791                 case ROFFT_HEAD:
792                         print_otag(h, TAG_DT, "");
793                         break;
794                 case ROFFT_BODY:
795                         if (n->child == NULL) {
796                                 print_otag(h, TAG_DD, "s", "width", "auto");
797                                 print_text(h, "\\ ");
798                         } else
799                                 print_otag(h, TAG_DD, "");
800                         break;
801                 default:
802                         break;
803                 }
804                 break;
805         case LIST_column:
806                 switch (n->type) {
807                 case ROFFT_HEAD:
808                         break;
809                 case ROFFT_BODY:
810                         print_otag(h, TAG_TD, "");
811                         break;
812                 default:
813                         print_otag(h, TAG_TR, "");
814                 }
815         default:
816                 break;
817         }
818
819         return 1;
820 }
821
822 static int
823 mdoc_bl_pre(MDOC_ARGS)
824 {
825         char             cattr[32];
826         struct mdoc_bl  *bl;
827         enum htmltag     elemtype;
828
829         switch (n->type) {
830         case ROFFT_BLOCK:
831                 html_close_paragraph(h);
832                 break;
833         case ROFFT_HEAD:
834                 return 0;
835         case ROFFT_BODY:
836                 return 1;
837         default:
838                 abort();
839         }
840
841         bl = &n->norm->Bl;
842         switch (bl->type) {
843         case LIST_bullet:
844                 elemtype = TAG_UL;
845                 (void)strlcpy(cattr, "Bl-bullet", sizeof(cattr));
846                 break;
847         case LIST_dash:
848         case LIST_hyphen:
849                 elemtype = TAG_UL;
850                 (void)strlcpy(cattr, "Bl-dash", sizeof(cattr));
851                 break;
852         case LIST_item:
853                 elemtype = TAG_UL;
854                 (void)strlcpy(cattr, "Bl-item", sizeof(cattr));
855                 break;
856         case LIST_enum:
857                 elemtype = TAG_OL;
858                 (void)strlcpy(cattr, "Bl-enum", sizeof(cattr));
859                 break;
860         case LIST_diag:
861                 elemtype = TAG_DL;
862                 (void)strlcpy(cattr, "Bl-diag", sizeof(cattr));
863                 break;
864         case LIST_hang:
865                 elemtype = TAG_DL;
866                 (void)strlcpy(cattr, "Bl-hang", sizeof(cattr));
867                 break;
868         case LIST_inset:
869                 elemtype = TAG_DL;
870                 (void)strlcpy(cattr, "Bl-inset", sizeof(cattr));
871                 break;
872         case LIST_ohang:
873                 elemtype = TAG_DL;
874                 (void)strlcpy(cattr, "Bl-ohang", sizeof(cattr));
875                 break;
876         case LIST_tag:
877                 if (bl->offs)
878                         print_otag(h, TAG_DIV, "c", "Bd-indent");
879                 print_otag(h, TAG_DL, "c", bl->comp ?
880                     "Bl-tag Bl-compact" : "Bl-tag");
881                 return 1;
882         case LIST_column:
883                 elemtype = TAG_TABLE;
884                 (void)strlcpy(cattr, "Bl-column", sizeof(cattr));
885                 break;
886         default:
887                 abort();
888         }
889         if (bl->offs != NULL)
890                 (void)strlcat(cattr, " Bd-indent", sizeof(cattr));
891         if (bl->comp)
892                 (void)strlcat(cattr, " Bl-compact", sizeof(cattr));
893         print_otag(h, elemtype, "c", cattr);
894         return 1;
895 }
896
897 static int
898 mdoc_ex_pre(MDOC_ARGS)
899 {
900         if (n->prev)
901                 print_otag(h, TAG_BR, "");
902         return 1;
903 }
904
905 static int
906 mdoc_st_pre(MDOC_ARGS)
907 {
908         print_otag(h, TAG_SPAN, "c", "St");
909         return 1;
910 }
911
912 static int
913 mdoc_em_pre(MDOC_ARGS)
914 {
915         print_otag(h, TAG_I, "c", "Em");
916         return 1;
917 }
918
919 static int
920 mdoc_d1_pre(MDOC_ARGS)
921 {
922         switch (n->type) {
923         case ROFFT_BLOCK:
924                 html_close_paragraph(h);
925                 break;
926         case ROFFT_HEAD:
927                 return 0;
928         case ROFFT_BODY:
929                 return 1;
930         default:
931                 abort();
932         }
933         print_otag(h, TAG_DIV, "c", "Bd Bd-indent");
934         if (n->tok == MDOC_Dl)
935                 print_otag(h, TAG_CODE, "c", "Li");
936         return 1;
937 }
938
939 static int
940 mdoc_sx_pre(MDOC_ARGS)
941 {
942         char    *id;
943
944         id = html_make_id(n, 0);
945         print_otag(h, TAG_A, "chR", "Sx", id);
946         free(id);
947         return 1;
948 }
949
950 static int
951 mdoc_bd_pre(MDOC_ARGS)
952 {
953         char                     buf[16];
954         struct roff_node        *nn;
955         int                      comp;
956
957         switch (n->type) {
958         case ROFFT_BLOCK:
959                 html_close_paragraph(h);
960                 return 1;
961         case ROFFT_HEAD:
962                 return 0;
963         case ROFFT_BODY:
964                 break;
965         default:
966                 abort();
967         }
968
969         /* Handle preceding whitespace. */
970
971         comp = n->norm->Bd.comp;
972         for (nn = n; nn != NULL && comp == 0; nn = nn->parent) {
973                 if (nn->type != ROFFT_BLOCK)
974                         continue;
975                 if (nn->tok == MDOC_Sh || nn->tok == MDOC_Ss)
976                         comp = 1;
977                 if (nn->prev != NULL)
978                         break;
979         }
980         (void)strlcpy(buf, "Bd", sizeof(buf));
981         if (comp == 0)
982                 (void)strlcat(buf, " Pp", sizeof(buf));
983
984         /* Handle the -offset argument. */
985
986         if (n->norm->Bd.offs != NULL &&
987             strcmp(n->norm->Bd.offs, "left") != 0)
988                 (void)strlcat(buf, " Bd-indent", sizeof(buf));
989
990         print_otag(h, TAG_DIV, "c", buf);
991         return 1;
992 }
993
994 static int
995 mdoc_pa_pre(MDOC_ARGS)
996 {
997         print_otag(h, TAG_SPAN, "c", "Pa");
998         return 1;
999 }
1000
1001 static int
1002 mdoc_ad_pre(MDOC_ARGS)
1003 {
1004         print_otag(h, TAG_SPAN, "c", "Ad");
1005         return 1;
1006 }
1007
1008 static int
1009 mdoc_an_pre(MDOC_ARGS)
1010 {
1011         if (n->norm->An.auth == AUTH_split) {
1012                 h->flags &= ~HTML_NOSPLIT;
1013                 h->flags |= HTML_SPLIT;
1014                 return 0;
1015         }
1016         if (n->norm->An.auth == AUTH_nosplit) {
1017                 h->flags &= ~HTML_SPLIT;
1018                 h->flags |= HTML_NOSPLIT;
1019                 return 0;
1020         }
1021
1022         if (h->flags & HTML_SPLIT)
1023                 print_otag(h, TAG_BR, "");
1024
1025         if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1026                 h->flags |= HTML_SPLIT;
1027
1028         print_otag(h, TAG_SPAN, "c", "An");
1029         return 1;
1030 }
1031
1032 static int
1033 mdoc_cd_pre(MDOC_ARGS)
1034 {
1035         synopsis_pre(h, n);
1036         print_otag(h, TAG_CODE, "c", "Cd");
1037         return 1;
1038 }
1039
1040 static int
1041 mdoc_dv_pre(MDOC_ARGS)
1042 {
1043         char    *id;
1044
1045         if ((id = cond_id(n)) != NULL)
1046                 print_otag(h, TAG_A, "chR", "permalink", id);
1047         print_otag(h, TAG_CODE, "ci", "Dv", id);
1048         return 1;
1049 }
1050
1051 static int
1052 mdoc_ev_pre(MDOC_ARGS)
1053 {
1054         char    *id;
1055
1056         if ((id = cond_id(n)) != NULL)
1057                 print_otag(h, TAG_A, "chR", "permalink", id);
1058         print_otag(h, TAG_CODE, "ci", "Ev", id);
1059         return 1;
1060 }
1061
1062 static int
1063 mdoc_er_pre(MDOC_ARGS)
1064 {
1065         char    *id;
1066
1067         id = n->sec == SEC_ERRORS &&
1068             (n->parent->tok == MDOC_It ||
1069              (n->parent->tok == MDOC_Bq &&
1070               n->parent->parent->parent->tok == MDOC_It)) ?
1071             html_make_id(n, 1) : NULL;
1072
1073         if (id != NULL)
1074                 print_otag(h, TAG_A, "chR", "permalink", id);
1075         print_otag(h, TAG_CODE, "ci", "Er", id);
1076         return 1;
1077 }
1078
1079 static int
1080 mdoc_fa_pre(MDOC_ARGS)
1081 {
1082         const struct roff_node  *nn;
1083         struct tag              *t;
1084
1085         if (n->parent->tok != MDOC_Fo) {
1086                 print_otag(h, TAG_VAR, "c", "Fa");
1087                 return 1;
1088         }
1089
1090         for (nn = n->child; nn; nn = nn->next) {
1091                 t = print_otag(h, TAG_VAR, "c", "Fa");
1092                 print_text(h, nn->string);
1093                 print_tagq(h, t);
1094                 if (nn->next) {
1095                         h->flags |= HTML_NOSPACE;
1096                         print_text(h, ",");
1097                 }
1098         }
1099
1100         if (n->child && n->next && n->next->tok == MDOC_Fa) {
1101                 h->flags |= HTML_NOSPACE;
1102                 print_text(h, ",");
1103         }
1104
1105         return 0;
1106 }
1107
1108 static int
1109 mdoc_fd_pre(MDOC_ARGS)
1110 {
1111         struct tag      *t;
1112         char            *buf, *cp;
1113
1114         synopsis_pre(h, n);
1115
1116         if (NULL == (n = n->child))
1117                 return 0;
1118
1119         assert(n->type == ROFFT_TEXT);
1120
1121         if (strcmp(n->string, "#include")) {
1122                 print_otag(h, TAG_CODE, "c", "Fd");
1123                 return 1;
1124         }
1125
1126         print_otag(h, TAG_CODE, "c", "In");
1127         print_text(h, n->string);
1128
1129         if (NULL != (n = n->next)) {
1130                 assert(n->type == ROFFT_TEXT);
1131
1132                 if (h->base_includes) {
1133                         cp = n->string;
1134                         if (*cp == '<' || *cp == '"')
1135                                 cp++;
1136                         buf = mandoc_strdup(cp);
1137                         cp = strchr(buf, '\0') - 1;
1138                         if (cp >= buf && (*cp == '>' || *cp == '"'))
1139                                 *cp = '\0';
1140                         t = print_otag(h, TAG_A, "chI", "In", buf);
1141                         free(buf);
1142                 } else
1143                         t = print_otag(h, TAG_A, "c", "In");
1144
1145                 print_text(h, n->string);
1146                 print_tagq(h, t);
1147
1148                 n = n->next;
1149         }
1150
1151         for ( ; n; n = n->next) {
1152                 assert(n->type == ROFFT_TEXT);
1153                 print_text(h, n->string);
1154         }
1155
1156         return 0;
1157 }
1158
1159 static int
1160 mdoc_vt_pre(MDOC_ARGS)
1161 {
1162         if (n->type == ROFFT_BLOCK) {
1163                 synopsis_pre(h, n);
1164                 return 1;
1165         } else if (n->type == ROFFT_ELEM) {
1166                 synopsis_pre(h, n);
1167         } else if (n->type == ROFFT_HEAD)
1168                 return 0;
1169
1170         print_otag(h, TAG_VAR, "c", "Vt");
1171         return 1;
1172 }
1173
1174 static int
1175 mdoc_ft_pre(MDOC_ARGS)
1176 {
1177         synopsis_pre(h, n);
1178         print_otag(h, TAG_VAR, "c", "Ft");
1179         return 1;
1180 }
1181
1182 static int
1183 mdoc_fn_pre(MDOC_ARGS)
1184 {
1185         struct tag      *t;
1186         char             nbuf[BUFSIZ];
1187         const char      *sp, *ep;
1188         int              sz, pretty;
1189
1190         pretty = NODE_SYNPRETTY & n->flags;
1191         synopsis_pre(h, n);
1192
1193         /* Split apart into type and name. */
1194         assert(n->child->string);
1195         sp = n->child->string;
1196
1197         ep = strchr(sp, ' ');
1198         if (NULL != ep) {
1199                 t = print_otag(h, TAG_VAR, "c", "Ft");
1200
1201                 while (ep) {
1202                         sz = MIN((int)(ep - sp), BUFSIZ - 1);
1203                         (void)memcpy(nbuf, sp, (size_t)sz);
1204                         nbuf[sz] = '\0';
1205                         print_text(h, nbuf);
1206                         sp = ++ep;
1207                         ep = strchr(sp, ' ');
1208                 }
1209                 print_tagq(h, t);
1210         }
1211
1212         t = print_otag(h, TAG_CODE, "c", "Fn");
1213
1214         if (sp)
1215                 print_text(h, sp);
1216
1217         print_tagq(h, t);
1218
1219         h->flags |= HTML_NOSPACE;
1220         print_text(h, "(");
1221         h->flags |= HTML_NOSPACE;
1222
1223         for (n = n->child->next; n; n = n->next) {
1224                 if (NODE_SYNPRETTY & n->flags)
1225                         t = print_otag(h, TAG_VAR, "cs", "Fa",
1226                             "white-space", "nowrap");
1227                 else
1228                         t = print_otag(h, TAG_VAR, "c", "Fa");
1229                 print_text(h, n->string);
1230                 print_tagq(h, t);
1231                 if (n->next) {
1232                         h->flags |= HTML_NOSPACE;
1233                         print_text(h, ",");
1234                 }
1235         }
1236
1237         h->flags |= HTML_NOSPACE;
1238         print_text(h, ")");
1239
1240         if (pretty) {
1241                 h->flags |= HTML_NOSPACE;
1242                 print_text(h, ";");
1243         }
1244
1245         return 0;
1246 }
1247
1248 static int
1249 mdoc_sm_pre(MDOC_ARGS)
1250 {
1251
1252         if (NULL == n->child)
1253                 h->flags ^= HTML_NONOSPACE;
1254         else if (0 == strcmp("on", n->child->string))
1255                 h->flags &= ~HTML_NONOSPACE;
1256         else
1257                 h->flags |= HTML_NONOSPACE;
1258
1259         if ( ! (HTML_NONOSPACE & h->flags))
1260                 h->flags &= ~HTML_NOSPACE;
1261
1262         return 0;
1263 }
1264
1265 static int
1266 mdoc_skip_pre(MDOC_ARGS)
1267 {
1268
1269         return 0;
1270 }
1271
1272 static int
1273 mdoc_pp_pre(MDOC_ARGS)
1274 {
1275         if ((n->flags & NODE_NOFILL) == 0) {
1276                 html_close_paragraph(h);
1277                 print_otag(h, TAG_P, "c", "Pp");
1278         }
1279         return 0;
1280 }
1281
1282 static int
1283 mdoc_lk_pre(MDOC_ARGS)
1284 {
1285         const struct roff_node *link, *descr, *punct;
1286         struct tag      *t;
1287
1288         if ((link = n->child) == NULL)
1289                 return 0;
1290
1291         /* Find beginning of trailing punctuation. */
1292         punct = n->last;
1293         while (punct != link && punct->flags & NODE_DELIMC)
1294                 punct = punct->prev;
1295         punct = punct->next;
1296
1297         /* Link target and link text. */
1298         descr = link->next;
1299         if (descr == punct)
1300                 descr = link;  /* no text */
1301         t = print_otag(h, TAG_A, "ch", "Lk", link->string);
1302         do {
1303                 if (descr->flags & (NODE_DELIMC | NODE_DELIMO))
1304                         h->flags |= HTML_NOSPACE;
1305                 print_text(h, descr->string);
1306                 descr = descr->next;
1307         } while (descr != punct);
1308         print_tagq(h, t);
1309
1310         /* Trailing punctuation. */
1311         while (punct != NULL) {
1312                 h->flags |= HTML_NOSPACE;
1313                 print_text(h, punct->string);
1314                 punct = punct->next;
1315         }
1316         return 0;
1317 }
1318
1319 static int
1320 mdoc_mt_pre(MDOC_ARGS)
1321 {
1322         struct tag      *t;
1323         char            *cp;
1324
1325         for (n = n->child; n; n = n->next) {
1326                 assert(n->type == ROFFT_TEXT);
1327
1328                 mandoc_asprintf(&cp, "mailto:%s", n->string);
1329                 t = print_otag(h, TAG_A, "ch", "Mt", cp);
1330                 print_text(h, n->string);
1331                 print_tagq(h, t);
1332                 free(cp);
1333         }
1334
1335         return 0;
1336 }
1337
1338 static int
1339 mdoc_fo_pre(MDOC_ARGS)
1340 {
1341         struct tag      *t;
1342
1343         if (n->type == ROFFT_BODY) {
1344                 h->flags |= HTML_NOSPACE;
1345                 print_text(h, "(");
1346                 h->flags |= HTML_NOSPACE;
1347                 return 1;
1348         } else if (n->type == ROFFT_BLOCK) {
1349                 synopsis_pre(h, n);
1350                 return 1;
1351         }
1352
1353         if (n->child == NULL)
1354                 return 0;
1355
1356         assert(n->child->string);
1357         t = print_otag(h, TAG_CODE, "c", "Fn");
1358         print_text(h, n->child->string);
1359         print_tagq(h, t);
1360         return 0;
1361 }
1362
1363 static void
1364 mdoc_fo_post(MDOC_ARGS)
1365 {
1366
1367         if (n->type != ROFFT_BODY)
1368                 return;
1369         h->flags |= HTML_NOSPACE;
1370         print_text(h, ")");
1371         h->flags |= HTML_NOSPACE;
1372         print_text(h, ";");
1373 }
1374
1375 static int
1376 mdoc_in_pre(MDOC_ARGS)
1377 {
1378         struct tag      *t;
1379
1380         synopsis_pre(h, n);
1381         print_otag(h, TAG_CODE, "c", "In");
1382
1383         /*
1384          * The first argument of the `In' gets special treatment as
1385          * being a linked value.  Subsequent values are printed
1386          * afterward.  groff does similarly.  This also handles the case
1387          * of no children.
1388          */
1389
1390         if (NODE_SYNPRETTY & n->flags && NODE_LINE & n->flags)
1391                 print_text(h, "#include");
1392
1393         print_text(h, "<");
1394         h->flags |= HTML_NOSPACE;
1395
1396         if (NULL != (n = n->child)) {
1397                 assert(n->type == ROFFT_TEXT);
1398
1399                 if (h->base_includes)
1400                         t = print_otag(h, TAG_A, "chI", "In", n->string);
1401                 else
1402                         t = print_otag(h, TAG_A, "c", "In");
1403                 print_text(h, n->string);
1404                 print_tagq(h, t);
1405
1406                 n = n->next;
1407         }
1408
1409         h->flags |= HTML_NOSPACE;
1410         print_text(h, ">");
1411
1412         for ( ; n; n = n->next) {
1413                 assert(n->type == ROFFT_TEXT);
1414                 print_text(h, n->string);
1415         }
1416
1417         return 0;
1418 }
1419
1420 static int
1421 mdoc_ic_pre(MDOC_ARGS)
1422 {
1423         char    *id;
1424
1425         if ((id = cond_id(n)) != NULL)
1426                 print_otag(h, TAG_A, "chR", "permalink", id);
1427         print_otag(h, TAG_CODE, "ci", "Ic", id);
1428         return 1;
1429 }
1430
1431 static int
1432 mdoc_va_pre(MDOC_ARGS)
1433 {
1434         print_otag(h, TAG_VAR, "c", "Va");
1435         return 1;
1436 }
1437
1438 static int
1439 mdoc_ap_pre(MDOC_ARGS)
1440 {
1441
1442         h->flags |= HTML_NOSPACE;
1443         print_text(h, "\\(aq");
1444         h->flags |= HTML_NOSPACE;
1445         return 1;
1446 }
1447
1448 static int
1449 mdoc_bf_pre(MDOC_ARGS)
1450 {
1451         const char      *cattr;
1452
1453         switch (n->type) {
1454         case ROFFT_BLOCK:
1455                 html_close_paragraph(h);
1456                 return 1;
1457         case ROFFT_HEAD:
1458                 return 0;
1459         case ROFFT_BODY:
1460                 break;
1461         default:
1462                 abort();
1463         }
1464
1465         if (FONT_Em == n->norm->Bf.font)
1466                 cattr = "Bf Em";
1467         else if (FONT_Sy == n->norm->Bf.font)
1468                 cattr = "Bf Sy";
1469         else if (FONT_Li == n->norm->Bf.font)
1470                 cattr = "Bf Li";
1471         else
1472                 cattr = "Bf No";
1473
1474         /* Cannot use TAG_SPAN because it may contain blocks. */
1475         print_otag(h, TAG_DIV, "c", cattr);
1476         return 1;
1477 }
1478
1479 static int
1480 mdoc_ms_pre(MDOC_ARGS)
1481 {
1482         char *id;
1483
1484         if ((id = cond_id(n)) != NULL)
1485                 print_otag(h, TAG_A, "chR", "permalink", id);
1486         print_otag(h, TAG_SPAN, "ci", "Ms", id);
1487         return 1;
1488 }
1489
1490 static int
1491 mdoc_igndelim_pre(MDOC_ARGS)
1492 {
1493
1494         h->flags |= HTML_IGNDELIM;
1495         return 1;
1496 }
1497
1498 static void
1499 mdoc_pf_post(MDOC_ARGS)
1500 {
1501
1502         if ( ! (n->next == NULL || n->next->flags & NODE_LINE))
1503                 h->flags |= HTML_NOSPACE;
1504 }
1505
1506 static int
1507 mdoc_rs_pre(MDOC_ARGS)
1508 {
1509         switch (n->type) {
1510         case ROFFT_BLOCK:
1511                 if (n->sec == SEC_SEE_ALSO)
1512                         html_close_paragraph(h);
1513                 break;
1514         case ROFFT_HEAD:
1515                 return 0;
1516         case ROFFT_BODY:
1517                 if (n->sec == SEC_SEE_ALSO)
1518                         print_otag(h, TAG_P, "c", "Pp");
1519                 print_otag(h, TAG_CITE, "c", "Rs");
1520                 break;
1521         default:
1522                 abort();
1523         }
1524         return 1;
1525 }
1526
1527 static int
1528 mdoc_no_pre(MDOC_ARGS)
1529 {
1530         char *id;
1531
1532         if ((id = cond_id(n)) != NULL)
1533                 print_otag(h, TAG_A, "chR", "permalink", id);
1534         print_otag(h, TAG_SPAN, "ci", "No", id);
1535         return 1;
1536 }
1537
1538 static int
1539 mdoc_li_pre(MDOC_ARGS)
1540 {
1541         char    *id;
1542
1543         if ((id = cond_id(n)) != NULL)
1544                 print_otag(h, TAG_A, "chR", "permalink", id);
1545         print_otag(h, TAG_CODE, "ci", "Li", id);
1546         return 1;
1547 }
1548
1549 static int
1550 mdoc_sy_pre(MDOC_ARGS)
1551 {
1552         print_otag(h, TAG_B, "c", "Sy");
1553         return 1;
1554 }
1555
1556 static int
1557 mdoc_lb_pre(MDOC_ARGS)
1558 {
1559         if (SEC_LIBRARY == n->sec && NODE_LINE & n->flags && n->prev)
1560                 print_otag(h, TAG_BR, "");
1561
1562         print_otag(h, TAG_SPAN, "c", "Lb");
1563         return 1;
1564 }
1565
1566 static int
1567 mdoc__x_pre(MDOC_ARGS)
1568 {
1569         const char      *cattr;
1570         enum htmltag     t;
1571
1572         t = TAG_SPAN;
1573
1574         switch (n->tok) {
1575         case MDOC__A:
1576                 cattr = "RsA";
1577                 if (n->prev && MDOC__A == n->prev->tok)
1578                         if (NULL == n->next || MDOC__A != n->next->tok)
1579                                 print_text(h, "and");
1580                 break;
1581         case MDOC__B:
1582                 t = TAG_I;
1583                 cattr = "RsB";
1584                 break;
1585         case MDOC__C:
1586                 cattr = "RsC";
1587                 break;
1588         case MDOC__D:
1589                 cattr = "RsD";
1590                 break;
1591         case MDOC__I:
1592                 t = TAG_I;
1593                 cattr = "RsI";
1594                 break;
1595         case MDOC__J:
1596                 t = TAG_I;
1597                 cattr = "RsJ";
1598                 break;
1599         case MDOC__N:
1600                 cattr = "RsN";
1601                 break;
1602         case MDOC__O:
1603                 cattr = "RsO";
1604                 break;
1605         case MDOC__P:
1606                 cattr = "RsP";
1607                 break;
1608         case MDOC__Q:
1609                 cattr = "RsQ";
1610                 break;
1611         case MDOC__R:
1612                 cattr = "RsR";
1613                 break;
1614         case MDOC__T:
1615                 cattr = "RsT";
1616                 break;
1617         case MDOC__U:
1618                 print_otag(h, TAG_A, "ch", "RsU", n->child->string);
1619                 return 1;
1620         case MDOC__V:
1621                 cattr = "RsV";
1622                 break;
1623         default:
1624                 abort();
1625         }
1626
1627         print_otag(h, t, "c", cattr);
1628         return 1;
1629 }
1630
1631 static void
1632 mdoc__x_post(MDOC_ARGS)
1633 {
1634
1635         if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
1636                 if (NULL == n->next->next || MDOC__A != n->next->next->tok)
1637                         if (NULL == n->prev || MDOC__A != n->prev->tok)
1638                                 return;
1639
1640         /* TODO: %U */
1641
1642         if (NULL == n->parent || MDOC_Rs != n->parent->tok)
1643                 return;
1644
1645         h->flags |= HTML_NOSPACE;
1646         print_text(h, n->next ? "," : ".");
1647 }
1648
1649 static int
1650 mdoc_bk_pre(MDOC_ARGS)
1651 {
1652
1653         switch (n->type) {
1654         case ROFFT_BLOCK:
1655                 break;
1656         case ROFFT_HEAD:
1657                 return 0;
1658         case ROFFT_BODY:
1659                 if (n->parent->args != NULL || n->prev->child == NULL)
1660                         h->flags |= HTML_PREKEEP;
1661                 break;
1662         default:
1663                 abort();
1664         }
1665
1666         return 1;
1667 }
1668
1669 static void
1670 mdoc_bk_post(MDOC_ARGS)
1671 {
1672
1673         if (n->type == ROFFT_BODY)
1674                 h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
1675 }
1676
1677 static int
1678 mdoc_quote_pre(MDOC_ARGS)
1679 {
1680         if (n->type != ROFFT_BODY)
1681                 return 1;
1682
1683         switch (n->tok) {
1684         case MDOC_Ao:
1685         case MDOC_Aq:
1686                 print_text(h, n->child != NULL && n->child->next == NULL &&
1687                     n->child->tok == MDOC_Mt ?  "<" : "\\(la");
1688                 break;
1689         case MDOC_Bro:
1690         case MDOC_Brq:
1691                 print_text(h, "\\(lC");
1692                 break;
1693         case MDOC_Bo:
1694         case MDOC_Bq:
1695                 print_text(h, "\\(lB");
1696                 break;
1697         case MDOC_Oo:
1698         case MDOC_Op:
1699                 print_text(h, "\\(lB");
1700                 /*
1701                  * Give up on semantic markup for now.
1702                  * We cannot use TAG_SPAN because .Oo may contain blocks.
1703                  * We cannot use TAG_IDIV because we might be in a
1704                  * phrasing context (like .Dl or .Pp); we cannot
1705                  * close out a .Pp at this point either because
1706                  * that would break the line.
1707                  */
1708                 /* XXX print_otag(h, TAG_???, "c", "Op"); */
1709                 break;
1710         case MDOC_En:
1711                 if (NULL == n->norm->Es ||
1712                     NULL == n->norm->Es->child)
1713                         return 1;
1714                 print_text(h, n->norm->Es->child->string);
1715                 break;
1716         case MDOC_Do:
1717         case MDOC_Dq:
1718         case MDOC_Qo:
1719         case MDOC_Qq:
1720                 print_text(h, "\\(lq");
1721                 break;
1722         case MDOC_Po:
1723         case MDOC_Pq:
1724                 print_text(h, "(");
1725                 break;
1726         case MDOC_Ql:
1727                 print_text(h, "\\(oq");
1728                 h->flags |= HTML_NOSPACE;
1729                 print_otag(h, TAG_CODE, "c", "Li");
1730                 break;
1731         case MDOC_So:
1732         case MDOC_Sq:
1733                 print_text(h, "\\(oq");
1734                 break;
1735         default:
1736                 abort();
1737         }
1738
1739         h->flags |= HTML_NOSPACE;
1740         return 1;
1741 }
1742
1743 static void
1744 mdoc_quote_post(MDOC_ARGS)
1745 {
1746
1747         if (n->type != ROFFT_BODY && n->type != ROFFT_ELEM)
1748                 return;
1749
1750         h->flags |= HTML_NOSPACE;
1751
1752         switch (n->tok) {
1753         case MDOC_Ao:
1754         case MDOC_Aq:
1755                 print_text(h, n->child != NULL && n->child->next == NULL &&
1756                     n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
1757                 break;
1758         case MDOC_Bro:
1759         case MDOC_Brq:
1760                 print_text(h, "\\(rC");
1761                 break;
1762         case MDOC_Oo:
1763         case MDOC_Op:
1764         case MDOC_Bo:
1765         case MDOC_Bq:
1766                 print_text(h, "\\(rB");
1767                 break;
1768         case MDOC_En:
1769                 if (n->norm->Es == NULL ||
1770                     n->norm->Es->child == NULL ||
1771                     n->norm->Es->child->next == NULL)
1772                         h->flags &= ~HTML_NOSPACE;
1773                 else
1774                         print_text(h, n->norm->Es->child->next->string);
1775                 break;
1776         case MDOC_Qo:
1777         case MDOC_Qq:
1778         case MDOC_Do:
1779         case MDOC_Dq:
1780                 print_text(h, "\\(rq");
1781                 break;
1782         case MDOC_Po:
1783         case MDOC_Pq:
1784                 print_text(h, ")");
1785                 break;
1786         case MDOC_Ql:
1787         case MDOC_So:
1788         case MDOC_Sq:
1789                 print_text(h, "\\(cq");
1790                 break;
1791         default:
1792                 abort();
1793         }
1794 }
1795
1796 static int
1797 mdoc_eo_pre(MDOC_ARGS)
1798 {
1799
1800         if (n->type != ROFFT_BODY)
1801                 return 1;
1802
1803         if (n->end == ENDBODY_NOT &&
1804             n->parent->head->child == NULL &&
1805             n->child != NULL &&
1806             n->child->end != ENDBODY_NOT)
1807                 print_text(h, "\\&");
1808         else if (n->end != ENDBODY_NOT ? n->child != NULL :
1809             n->parent->head->child != NULL && (n->child != NULL ||
1810             (n->parent->tail != NULL && n->parent->tail->child != NULL)))
1811                 h->flags |= HTML_NOSPACE;
1812         return 1;
1813 }
1814
1815 static void
1816 mdoc_eo_post(MDOC_ARGS)
1817 {
1818         int      body, tail;
1819
1820         if (n->type != ROFFT_BODY)
1821                 return;
1822
1823         if (n->end != ENDBODY_NOT) {
1824                 h->flags &= ~HTML_NOSPACE;
1825                 return;
1826         }
1827
1828         body = n->child != NULL || n->parent->head->child != NULL;
1829         tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
1830
1831         if (body && tail)
1832                 h->flags |= HTML_NOSPACE;
1833         else if ( ! tail)
1834                 h->flags &= ~HTML_NOSPACE;
1835 }
1836
1837 static int
1838 mdoc_abort_pre(MDOC_ARGS)
1839 {
1840         abort();
1841 }