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