]> CyberLeo.Net >> Repos - FreeBSD/FreeBSD.git/blob - contrib/mdocml/mdoc_html.c
Restore packaging subdir to enable running unmodified configure script.
[FreeBSD/FreeBSD.git] / contrib / mdocml / mdoc_html.c
1 /*      $Id: mdoc_html.c,v 1.226 2015/03/03 21:11:34 schwarze Exp $ */
2 /*
3  * Copyright (c) 2008-2011, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
4  * Copyright (c) 2014, 2015 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 AUTHOR DISCLAIMS ALL WARRANTIES
11  * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12  * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 "mdoc.h"
31 #include "out.h"
32 #include "html.h"
33 #include "main.h"
34
35 #define INDENT           5
36
37 #define MDOC_ARGS         const struct mdoc_meta *meta, \
38                           struct mdoc_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  htmlmdoc {
46         int             (*pre)(MDOC_ARGS);
47         void            (*post)(MDOC_ARGS);
48 };
49
50 static  void              print_mdoc(MDOC_ARGS);
51 static  void              print_mdoc_head(MDOC_ARGS);
52 static  void              print_mdoc_node(MDOC_ARGS);
53 static  void              print_mdoc_nodelist(MDOC_ARGS);
54 static  void              synopsis_pre(struct html *,
55                                 const struct mdoc_node *);
56
57 static  void              a2width(const char *, struct roffsu *);
58
59 static  void              mdoc_root_post(MDOC_ARGS);
60 static  int               mdoc_root_pre(MDOC_ARGS);
61
62 static  void              mdoc__x_post(MDOC_ARGS);
63 static  int               mdoc__x_pre(MDOC_ARGS);
64 static  int               mdoc_ad_pre(MDOC_ARGS);
65 static  int               mdoc_an_pre(MDOC_ARGS);
66 static  int               mdoc_ap_pre(MDOC_ARGS);
67 static  int               mdoc_ar_pre(MDOC_ARGS);
68 static  int               mdoc_bd_pre(MDOC_ARGS);
69 static  int               mdoc_bf_pre(MDOC_ARGS);
70 static  void              mdoc_bk_post(MDOC_ARGS);
71 static  int               mdoc_bk_pre(MDOC_ARGS);
72 static  int               mdoc_bl_pre(MDOC_ARGS);
73 static  int               mdoc_bt_pre(MDOC_ARGS);
74 static  int               mdoc_bx_pre(MDOC_ARGS);
75 static  int               mdoc_cd_pre(MDOC_ARGS);
76 static  int               mdoc_d1_pre(MDOC_ARGS);
77 static  int               mdoc_dv_pre(MDOC_ARGS);
78 static  int               mdoc_fa_pre(MDOC_ARGS);
79 static  int               mdoc_fd_pre(MDOC_ARGS);
80 static  int               mdoc_fl_pre(MDOC_ARGS);
81 static  int               mdoc_fn_pre(MDOC_ARGS);
82 static  int               mdoc_ft_pre(MDOC_ARGS);
83 static  int               mdoc_em_pre(MDOC_ARGS);
84 static  void              mdoc_eo_post(MDOC_ARGS);
85 static  int               mdoc_eo_pre(MDOC_ARGS);
86 static  int               mdoc_er_pre(MDOC_ARGS);
87 static  int               mdoc_ev_pre(MDOC_ARGS);
88 static  int               mdoc_ex_pre(MDOC_ARGS);
89 static  void              mdoc_fo_post(MDOC_ARGS);
90 static  int               mdoc_fo_pre(MDOC_ARGS);
91 static  int               mdoc_ic_pre(MDOC_ARGS);
92 static  int               mdoc_igndelim_pre(MDOC_ARGS);
93 static  int               mdoc_in_pre(MDOC_ARGS);
94 static  int               mdoc_it_pre(MDOC_ARGS);
95 static  int               mdoc_lb_pre(MDOC_ARGS);
96 static  int               mdoc_li_pre(MDOC_ARGS);
97 static  int               mdoc_lk_pre(MDOC_ARGS);
98 static  int               mdoc_mt_pre(MDOC_ARGS);
99 static  int               mdoc_ms_pre(MDOC_ARGS);
100 static  int               mdoc_nd_pre(MDOC_ARGS);
101 static  int               mdoc_nm_pre(MDOC_ARGS);
102 static  int               mdoc_no_pre(MDOC_ARGS);
103 static  int               mdoc_ns_pre(MDOC_ARGS);
104 static  int               mdoc_pa_pre(MDOC_ARGS);
105 static  void              mdoc_pf_post(MDOC_ARGS);
106 static  int               mdoc_pp_pre(MDOC_ARGS);
107 static  void              mdoc_quote_post(MDOC_ARGS);
108 static  int               mdoc_quote_pre(MDOC_ARGS);
109 static  int               mdoc_rs_pre(MDOC_ARGS);
110 static  int               mdoc_rv_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_sp_pre(MDOC_ARGS);
115 static  int               mdoc_ss_pre(MDOC_ARGS);
116 static  int               mdoc_sx_pre(MDOC_ARGS);
117 static  int               mdoc_sy_pre(MDOC_ARGS);
118 static  int               mdoc_ud_pre(MDOC_ARGS);
119 static  int               mdoc_va_pre(MDOC_ARGS);
120 static  int               mdoc_vt_pre(MDOC_ARGS);
121 static  int               mdoc_xr_pre(MDOC_ARGS);
122 static  int               mdoc_xx_pre(MDOC_ARGS);
123
124 static  const struct htmlmdoc mdocs[MDOC_MAX] = {
125         {mdoc_ap_pre, NULL}, /* Ap */
126         {NULL, NULL}, /* Dd */
127         {NULL, NULL}, /* Dt */
128         {NULL, NULL}, /* Os */
129         {mdoc_sh_pre, NULL }, /* Sh */
130         {mdoc_ss_pre, NULL }, /* Ss */
131         {mdoc_pp_pre, NULL}, /* Pp */
132         {mdoc_d1_pre, NULL}, /* D1 */
133         {mdoc_d1_pre, NULL}, /* Dl */
134         {mdoc_bd_pre, NULL}, /* Bd */
135         {NULL, NULL}, /* Ed */
136         {mdoc_bl_pre, NULL}, /* Bl */
137         {NULL, NULL}, /* El */
138         {mdoc_it_pre, NULL}, /* It */
139         {mdoc_ad_pre, NULL}, /* Ad */
140         {mdoc_an_pre, NULL}, /* An */
141         {mdoc_ar_pre, NULL}, /* Ar */
142         {mdoc_cd_pre, NULL}, /* Cd */
143         {mdoc_fl_pre, NULL}, /* Cm */
144         {mdoc_dv_pre, NULL}, /* Dv */
145         {mdoc_er_pre, NULL}, /* Er */
146         {mdoc_ev_pre, NULL}, /* Ev */
147         {mdoc_ex_pre, NULL}, /* Ex */
148         {mdoc_fa_pre, NULL}, /* Fa */
149         {mdoc_fd_pre, NULL}, /* Fd */
150         {mdoc_fl_pre, NULL}, /* Fl */
151         {mdoc_fn_pre, NULL}, /* Fn */
152         {mdoc_ft_pre, NULL}, /* Ft */
153         {mdoc_ic_pre, NULL}, /* Ic */
154         {mdoc_in_pre, NULL}, /* In */
155         {mdoc_li_pre, NULL}, /* Li */
156         {mdoc_nd_pre, NULL}, /* Nd */
157         {mdoc_nm_pre, NULL}, /* Nm */
158         {mdoc_quote_pre, mdoc_quote_post}, /* Op */
159         {mdoc_ft_pre, NULL}, /* Ot */
160         {mdoc_pa_pre, NULL}, /* Pa */
161         {mdoc_rv_pre, NULL}, /* Rv */
162         {NULL, NULL}, /* St */
163         {mdoc_va_pre, NULL}, /* Va */
164         {mdoc_vt_pre, NULL}, /* Vt */
165         {mdoc_xr_pre, NULL}, /* Xr */
166         {mdoc__x_pre, mdoc__x_post}, /* %A */
167         {mdoc__x_pre, mdoc__x_post}, /* %B */
168         {mdoc__x_pre, mdoc__x_post}, /* %D */
169         {mdoc__x_pre, mdoc__x_post}, /* %I */
170         {mdoc__x_pre, mdoc__x_post}, /* %J */
171         {mdoc__x_pre, mdoc__x_post}, /* %N */
172         {mdoc__x_pre, mdoc__x_post}, /* %O */
173         {mdoc__x_pre, mdoc__x_post}, /* %P */
174         {mdoc__x_pre, mdoc__x_post}, /* %R */
175         {mdoc__x_pre, mdoc__x_post}, /* %T */
176         {mdoc__x_pre, mdoc__x_post}, /* %V */
177         {NULL, NULL}, /* Ac */
178         {mdoc_quote_pre, mdoc_quote_post}, /* Ao */
179         {mdoc_quote_pre, mdoc_quote_post}, /* Aq */
180         {NULL, NULL}, /* At */
181         {NULL, NULL}, /* Bc */
182         {mdoc_bf_pre, NULL}, /* Bf */
183         {mdoc_quote_pre, mdoc_quote_post}, /* Bo */
184         {mdoc_quote_pre, mdoc_quote_post}, /* Bq */
185         {mdoc_xx_pre, NULL}, /* Bsx */
186         {mdoc_bx_pre, NULL}, /* Bx */
187         {mdoc_skip_pre, NULL}, /* Db */
188         {NULL, NULL}, /* Dc */
189         {mdoc_quote_pre, mdoc_quote_post}, /* Do */
190         {mdoc_quote_pre, mdoc_quote_post}, /* Dq */
191         {NULL, NULL}, /* Ec */ /* FIXME: no space */
192         {NULL, NULL}, /* Ef */
193         {mdoc_em_pre, NULL}, /* Em */
194         {mdoc_eo_pre, mdoc_eo_post}, /* Eo */
195         {mdoc_xx_pre, NULL}, /* Fx */
196         {mdoc_ms_pre, NULL}, /* Ms */
197         {mdoc_no_pre, NULL}, /* No */
198         {mdoc_ns_pre, NULL}, /* Ns */
199         {mdoc_xx_pre, NULL}, /* Nx */
200         {mdoc_xx_pre, NULL}, /* Ox */
201         {NULL, NULL}, /* Pc */
202         {mdoc_igndelim_pre, mdoc_pf_post}, /* Pf */
203         {mdoc_quote_pre, mdoc_quote_post}, /* Po */
204         {mdoc_quote_pre, mdoc_quote_post}, /* Pq */
205         {NULL, NULL}, /* Qc */
206         {mdoc_quote_pre, mdoc_quote_post}, /* Ql */
207         {mdoc_quote_pre, mdoc_quote_post}, /* Qo */
208         {mdoc_quote_pre, mdoc_quote_post}, /* Qq */
209         {NULL, NULL}, /* Re */
210         {mdoc_rs_pre, NULL}, /* Rs */
211         {NULL, NULL}, /* Sc */
212         {mdoc_quote_pre, mdoc_quote_post}, /* So */
213         {mdoc_quote_pre, mdoc_quote_post}, /* Sq */
214         {mdoc_sm_pre, NULL}, /* Sm */
215         {mdoc_sx_pre, NULL}, /* Sx */
216         {mdoc_sy_pre, NULL}, /* Sy */
217         {NULL, NULL}, /* Tn */
218         {mdoc_xx_pre, NULL}, /* Ux */
219         {NULL, NULL}, /* Xc */
220         {NULL, NULL}, /* Xo */
221         {mdoc_fo_pre, mdoc_fo_post}, /* Fo */
222         {NULL, NULL}, /* Fc */
223         {mdoc_quote_pre, mdoc_quote_post}, /* Oo */
224         {NULL, NULL}, /* Oc */
225         {mdoc_bk_pre, mdoc_bk_post}, /* Bk */
226         {NULL, NULL}, /* Ek */
227         {mdoc_bt_pre, NULL}, /* Bt */
228         {NULL, NULL}, /* Hf */
229         {mdoc_em_pre, NULL}, /* Fr */
230         {mdoc_ud_pre, NULL}, /* Ud */
231         {mdoc_lb_pre, NULL}, /* Lb */
232         {mdoc_pp_pre, NULL}, /* Lp */
233         {mdoc_lk_pre, NULL}, /* Lk */
234         {mdoc_mt_pre, NULL}, /* Mt */
235         {mdoc_quote_pre, mdoc_quote_post}, /* Brq */
236         {mdoc_quote_pre, mdoc_quote_post}, /* Bro */
237         {NULL, NULL}, /* Brc */
238         {mdoc__x_pre, mdoc__x_post}, /* %C */
239         {mdoc_skip_pre, NULL}, /* Es */
240         {mdoc_quote_pre, mdoc_quote_post}, /* En */
241         {mdoc_xx_pre, NULL}, /* Dx */
242         {mdoc__x_pre, mdoc__x_post}, /* %Q */
243         {mdoc_sp_pre, NULL}, /* br */
244         {mdoc_sp_pre, NULL}, /* sp */
245         {mdoc__x_pre, mdoc__x_post}, /* %U */
246         {NULL, NULL}, /* Ta */
247         {mdoc_skip_pre, NULL}, /* ll */
248 };
249
250 static  const char * const lists[LIST_MAX] = {
251         NULL,
252         "list-bul",
253         "list-col",
254         "list-dash",
255         "list-diag",
256         "list-enum",
257         "list-hang",
258         "list-hyph",
259         "list-inset",
260         "list-item",
261         "list-ohang",
262         "list-tag"
263 };
264
265
266 void
267 html_mdoc(void *arg, const struct mdoc *mdoc)
268 {
269
270         print_mdoc(mdoc_meta(mdoc), mdoc_node(mdoc)->child,
271             (struct html *)arg);
272         putchar('\n');
273 }
274
275 /*
276  * Calculate the scaling unit passed in a `-width' argument.  This uses
277  * either a native scaling unit (e.g., 1i, 2m) or the string length of
278  * the value.
279  */
280 static void
281 a2width(const char *p, struct roffsu *su)
282 {
283
284         if (a2roffsu(p, su, SCALE_MAX) < 2) {
285                 su->unit = SCALE_EN;
286                 su->scale = html_strlen(p);
287         } else if (su->scale < 0.0)
288                 su->scale = 0.0;
289 }
290
291 /*
292  * See the same function in mdoc_term.c for documentation.
293  */
294 static void
295 synopsis_pre(struct html *h, const struct mdoc_node *n)
296 {
297
298         if (NULL == n->prev || ! (MDOC_SYNPRETTY & n->flags))
299                 return;
300
301         if (n->prev->tok == n->tok &&
302             MDOC_Fo != n->tok &&
303             MDOC_Ft != n->tok &&
304             MDOC_Fn != n->tok) {
305                 print_otag(h, TAG_BR, 0, NULL);
306                 return;
307         }
308
309         switch (n->prev->tok) {
310         case MDOC_Fd:
311                 /* FALLTHROUGH */
312         case MDOC_Fn:
313                 /* FALLTHROUGH */
314         case MDOC_Fo:
315                 /* FALLTHROUGH */
316         case MDOC_In:
317                 /* FALLTHROUGH */
318         case MDOC_Vt:
319                 print_paragraph(h);
320                 break;
321         case MDOC_Ft:
322                 if (MDOC_Fn != n->tok && MDOC_Fo != n->tok) {
323                         print_paragraph(h);
324                         break;
325                 }
326                 /* FALLTHROUGH */
327         default:
328                 print_otag(h, TAG_BR, 0, NULL);
329                 break;
330         }
331 }
332
333 static void
334 print_mdoc(MDOC_ARGS)
335 {
336         struct tag      *t, *tt;
337         struct htmlpair  tag;
338
339         PAIR_CLASS_INIT(&tag, "mandoc");
340
341         if ( ! (HTML_FRAGMENT & h->oflags)) {
342                 print_gen_decls(h);
343                 t = print_otag(h, TAG_HTML, 0, NULL);
344                 tt = print_otag(h, TAG_HEAD, 0, NULL);
345                 print_mdoc_head(meta, n, h);
346                 print_tagq(h, tt);
347                 print_otag(h, TAG_BODY, 0, NULL);
348                 print_otag(h, TAG_DIV, 1, &tag);
349         } else
350                 t = print_otag(h, TAG_DIV, 1, &tag);
351
352         print_mdoc_nodelist(meta, n, h);
353         print_tagq(h, t);
354 }
355
356 static void
357 print_mdoc_head(MDOC_ARGS)
358 {
359
360         print_gen_head(h);
361         bufinit(h);
362         bufcat(h, meta->title);
363         if (meta->msec)
364                 bufcat_fmt(h, "(%s)", meta->msec);
365         if (meta->arch)
366                 bufcat_fmt(h, " (%s)", meta->arch);
367
368         print_otag(h, TAG_TITLE, 0, NULL);
369         print_text(h, h->buf);
370 }
371
372 static void
373 print_mdoc_nodelist(MDOC_ARGS)
374 {
375
376         while (n != NULL) {
377                 print_mdoc_node(meta, n, h);
378                 n = n->next;
379         }
380 }
381
382 static void
383 print_mdoc_node(MDOC_ARGS)
384 {
385         int              child;
386         struct tag      *t;
387
388         child = 1;
389         t = h->tags.head;
390         n->flags &= ~MDOC_ENDED;
391
392         switch (n->type) {
393         case MDOC_ROOT:
394                 child = mdoc_root_pre(meta, n, h);
395                 break;
396         case MDOC_TEXT:
397                 /* No tables in this mode... */
398                 assert(NULL == h->tblt);
399
400                 /*
401                  * Make sure that if we're in a literal mode already
402                  * (i.e., within a <PRE>) don't print the newline.
403                  */
404                 if (' ' == *n->string && MDOC_LINE & n->flags)
405                         if ( ! (HTML_LITERAL & h->flags))
406                                 print_otag(h, TAG_BR, 0, NULL);
407                 if (MDOC_DELIMC & n->flags)
408                         h->flags |= HTML_NOSPACE;
409                 print_text(h, n->string);
410                 if (MDOC_DELIMO & n->flags)
411                         h->flags |= HTML_NOSPACE;
412                 return;
413         case MDOC_EQN:
414                 if (n->flags & MDOC_LINE)
415                         putchar('\n');
416                 print_eqn(h, n->eqn);
417                 break;
418         case MDOC_TBL:
419                 /*
420                  * This will take care of initialising all of the table
421                  * state data for the first table, then tearing it down
422                  * for the last one.
423                  */
424                 print_tbl(h, n->span);
425                 return;
426         default:
427                 /*
428                  * Close out the current table, if it's open, and unset
429                  * the "meta" table state.  This will be reopened on the
430                  * next table element.
431                  */
432                 if (h->tblt != NULL) {
433                         print_tblclose(h);
434                         t = h->tags.head;
435                 }
436                 assert(h->tblt == NULL);
437                 if (mdocs[n->tok].pre && (n->end == ENDBODY_NOT || n->child))
438                         child = (*mdocs[n->tok].pre)(meta, n, h);
439                 break;
440         }
441
442         if (h->flags & HTML_KEEP && n->flags & MDOC_LINE) {
443                 h->flags &= ~HTML_KEEP;
444                 h->flags |= HTML_PREKEEP;
445         }
446
447         if (child && n->child)
448                 print_mdoc_nodelist(meta, n->child, h);
449
450         print_stagq(h, t);
451
452         switch (n->type) {
453         case MDOC_ROOT:
454                 mdoc_root_post(meta, n, h);
455                 break;
456         case MDOC_EQN:
457                 break;
458         default:
459                 if ( ! mdocs[n->tok].post || n->flags & MDOC_ENDED)
460                         break;
461                 (*mdocs[n->tok].post)(meta, n, h);
462                 if (n->end != ENDBODY_NOT)
463                         n->body->flags |= MDOC_ENDED;
464                 if (n->end == ENDBODY_NOSPACE)
465                         h->flags |= HTML_NOSPACE;
466                 break;
467         }
468 }
469
470 static void
471 mdoc_root_post(MDOC_ARGS)
472 {
473         struct htmlpair  tag;
474         struct tag      *t, *tt;
475
476         PAIR_CLASS_INIT(&tag, "foot");
477         t = print_otag(h, TAG_TABLE, 1, &tag);
478
479         print_otag(h, TAG_TBODY, 0, NULL);
480
481         tt = print_otag(h, TAG_TR, 0, NULL);
482
483         PAIR_CLASS_INIT(&tag, "foot-date");
484         print_otag(h, TAG_TD, 1, &tag);
485         print_text(h, meta->date);
486         print_stagq(h, tt);
487
488         PAIR_CLASS_INIT(&tag, "foot-os");
489         print_otag(h, TAG_TD, 1, &tag);
490         print_text(h, meta->os);
491         print_tagq(h, t);
492 }
493
494 static int
495 mdoc_root_pre(MDOC_ARGS)
496 {
497         struct htmlpair  tag;
498         struct tag      *t, *tt;
499         char            *volume, *title;
500
501         if (NULL == meta->arch)
502                 volume = mandoc_strdup(meta->vol);
503         else
504                 mandoc_asprintf(&volume, "%s (%s)",
505                     meta->vol, meta->arch);
506
507         if (NULL == meta->msec)
508                 title = mandoc_strdup(meta->title);
509         else
510                 mandoc_asprintf(&title, "%s(%s)",
511                     meta->title, meta->msec);
512
513         PAIR_CLASS_INIT(&tag, "head");
514         t = print_otag(h, TAG_TABLE, 1, &tag);
515
516         print_otag(h, TAG_TBODY, 0, NULL);
517
518         tt = print_otag(h, TAG_TR, 0, NULL);
519
520         PAIR_CLASS_INIT(&tag, "head-ltitle");
521         print_otag(h, TAG_TD, 1, &tag);
522         print_text(h, title);
523         print_stagq(h, tt);
524
525         PAIR_CLASS_INIT(&tag, "head-vol");
526         print_otag(h, TAG_TD, 1, &tag);
527         print_text(h, volume);
528         print_stagq(h, tt);
529
530         PAIR_CLASS_INIT(&tag, "head-rtitle");
531         print_otag(h, TAG_TD, 1, &tag);
532         print_text(h, title);
533         print_tagq(h, t);
534
535         free(title);
536         free(volume);
537         return(1);
538 }
539
540 static int
541 mdoc_sh_pre(MDOC_ARGS)
542 {
543         struct htmlpair  tag;
544
545         switch (n->type) {
546         case MDOC_BLOCK:
547                 PAIR_CLASS_INIT(&tag, "section");
548                 print_otag(h, TAG_DIV, 1, &tag);
549                 return(1);
550         case MDOC_BODY:
551                 if (n->sec == SEC_AUTHORS)
552                         h->flags &= ~(HTML_SPLIT|HTML_NOSPLIT);
553                 return(1);
554         default:
555                 break;
556         }
557
558         bufinit(h);
559         bufcat(h, "x");
560
561         for (n = n->child; n && MDOC_TEXT == n->type; ) {
562                 bufcat_id(h, n->string);
563                 if (NULL != (n = n->next))
564                         bufcat_id(h, " ");
565         }
566
567         if (NULL == n) {
568                 PAIR_ID_INIT(&tag, h->buf);
569                 print_otag(h, TAG_H1, 1, &tag);
570         } else
571                 print_otag(h, TAG_H1, 0, NULL);
572
573         return(1);
574 }
575
576 static int
577 mdoc_ss_pre(MDOC_ARGS)
578 {
579         struct htmlpair  tag;
580
581         if (MDOC_BLOCK == n->type) {
582                 PAIR_CLASS_INIT(&tag, "subsection");
583                 print_otag(h, TAG_DIV, 1, &tag);
584                 return(1);
585         } else if (MDOC_BODY == n->type)
586                 return(1);
587
588         bufinit(h);
589         bufcat(h, "x");
590
591         for (n = n->child; n && MDOC_TEXT == n->type; ) {
592                 bufcat_id(h, n->string);
593                 if (NULL != (n = n->next))
594                         bufcat_id(h, " ");
595         }
596
597         if (NULL == n) {
598                 PAIR_ID_INIT(&tag, h->buf);
599                 print_otag(h, TAG_H2, 1, &tag);
600         } else
601                 print_otag(h, TAG_H2, 0, NULL);
602
603         return(1);
604 }
605
606 static int
607 mdoc_fl_pre(MDOC_ARGS)
608 {
609         struct htmlpair  tag;
610
611         PAIR_CLASS_INIT(&tag, "flag");
612         print_otag(h, TAG_B, 1, &tag);
613
614         /* `Cm' has no leading hyphen. */
615
616         if (MDOC_Cm == n->tok)
617                 return(1);
618
619         print_text(h, "\\-");
620
621         if ( ! (n->nchild == 0 &&
622             (n->next == NULL ||
623              n->next->type == MDOC_TEXT ||
624              n->next->flags & MDOC_LINE)))
625                 h->flags |= HTML_NOSPACE;
626
627         return(1);
628 }
629
630 static int
631 mdoc_nd_pre(MDOC_ARGS)
632 {
633         struct htmlpair  tag;
634
635         if (MDOC_BODY != n->type)
636                 return(1);
637
638         /* XXX: this tag in theory can contain block elements. */
639
640         print_text(h, "\\(em");
641         PAIR_CLASS_INIT(&tag, "desc");
642         print_otag(h, TAG_SPAN, 1, &tag);
643         return(1);
644 }
645
646 static int
647 mdoc_nm_pre(MDOC_ARGS)
648 {
649         struct htmlpair  tag;
650         struct roffsu    su;
651         int              len;
652
653         switch (n->type) {
654         case MDOC_ELEM:
655                 synopsis_pre(h, n);
656                 PAIR_CLASS_INIT(&tag, "name");
657                 print_otag(h, TAG_B, 1, &tag);
658                 if (NULL == n->child && meta->name)
659                         print_text(h, meta->name);
660                 return(1);
661         case MDOC_HEAD:
662                 print_otag(h, TAG_TD, 0, NULL);
663                 if (NULL == n->child && meta->name)
664                         print_text(h, meta->name);
665                 return(1);
666         case MDOC_BODY:
667                 print_otag(h, TAG_TD, 0, NULL);
668                 return(1);
669         default:
670                 break;
671         }
672
673         synopsis_pre(h, n);
674         PAIR_CLASS_INIT(&tag, "synopsis");
675         print_otag(h, TAG_TABLE, 1, &tag);
676
677         for (len = 0, n = n->child; n; n = n->next)
678                 if (MDOC_TEXT == n->type)
679                         len += html_strlen(n->string);
680
681         if (0 == len && meta->name)
682                 len = html_strlen(meta->name);
683
684         SCALE_HS_INIT(&su, len);
685         bufinit(h);
686         bufcat_su(h, "width", &su);
687         PAIR_STYLE_INIT(&tag, h);
688         print_otag(h, TAG_COL, 1, &tag);
689         print_otag(h, TAG_COL, 0, NULL);
690         print_otag(h, TAG_TBODY, 0, NULL);
691         print_otag(h, TAG_TR, 0, NULL);
692         return(1);
693 }
694
695 static int
696 mdoc_xr_pre(MDOC_ARGS)
697 {
698         struct htmlpair  tag[2];
699
700         if (NULL == n->child)
701                 return(0);
702
703         PAIR_CLASS_INIT(&tag[0], "link-man");
704
705         if (h->base_man) {
706                 buffmt_man(h, n->child->string,
707                     n->child->next ?
708                     n->child->next->string : NULL);
709                 PAIR_HREF_INIT(&tag[1], h->buf);
710                 print_otag(h, TAG_A, 2, tag);
711         } else
712                 print_otag(h, TAG_A, 1, tag);
713
714         n = n->child;
715         print_text(h, n->string);
716
717         if (NULL == (n = n->next))
718                 return(0);
719
720         h->flags |= HTML_NOSPACE;
721         print_text(h, "(");
722         h->flags |= HTML_NOSPACE;
723         print_text(h, n->string);
724         h->flags |= HTML_NOSPACE;
725         print_text(h, ")");
726         return(0);
727 }
728
729 static int
730 mdoc_ns_pre(MDOC_ARGS)
731 {
732
733         if ( ! (MDOC_LINE & n->flags))
734                 h->flags |= HTML_NOSPACE;
735         return(1);
736 }
737
738 static int
739 mdoc_ar_pre(MDOC_ARGS)
740 {
741         struct htmlpair tag;
742
743         PAIR_CLASS_INIT(&tag, "arg");
744         print_otag(h, TAG_I, 1, &tag);
745         return(1);
746 }
747
748 static int
749 mdoc_xx_pre(MDOC_ARGS)
750 {
751         const char      *pp;
752         struct htmlpair  tag;
753         int              flags;
754
755         switch (n->tok) {
756         case MDOC_Bsx:
757                 pp = "BSD/OS";
758                 break;
759         case MDOC_Dx:
760                 pp = "DragonFly";
761                 break;
762         case MDOC_Fx:
763                 pp = "FreeBSD";
764                 break;
765         case MDOC_Nx:
766                 pp = "NetBSD";
767                 break;
768         case MDOC_Ox:
769                 pp = "OpenBSD";
770                 break;
771         case MDOC_Ux:
772                 pp = "UNIX";
773                 break;
774         default:
775                 return(1);
776         }
777
778         PAIR_CLASS_INIT(&tag, "unix");
779         print_otag(h, TAG_SPAN, 1, &tag);
780
781         print_text(h, pp);
782         if (n->child) {
783                 flags = h->flags;
784                 h->flags |= HTML_KEEP;
785                 print_text(h, n->child->string);
786                 h->flags = flags;
787         }
788         return(0);
789 }
790
791 static int
792 mdoc_bx_pre(MDOC_ARGS)
793 {
794         struct htmlpair  tag;
795
796         PAIR_CLASS_INIT(&tag, "unix");
797         print_otag(h, TAG_SPAN, 1, &tag);
798
799         if (NULL != (n = n->child)) {
800                 print_text(h, n->string);
801                 h->flags |= HTML_NOSPACE;
802                 print_text(h, "BSD");
803         } else {
804                 print_text(h, "BSD");
805                 return(0);
806         }
807
808         if (NULL != (n = n->next)) {
809                 h->flags |= HTML_NOSPACE;
810                 print_text(h, "-");
811                 h->flags |= HTML_NOSPACE;
812                 print_text(h, n->string);
813         }
814
815         return(0);
816 }
817
818 static int
819 mdoc_it_pre(MDOC_ARGS)
820 {
821         struct roffsu    su;
822         enum mdoc_list   type;
823         struct htmlpair  tag[2];
824         const struct mdoc_node *bl;
825
826         bl = n->parent;
827         while (bl && MDOC_Bl != bl->tok)
828                 bl = bl->parent;
829
830         assert(bl);
831
832         type = bl->norm->Bl.type;
833
834         assert(lists[type]);
835         PAIR_CLASS_INIT(&tag[0], lists[type]);
836
837         bufinit(h);
838
839         if (MDOC_HEAD == n->type) {
840                 switch (type) {
841                 case LIST_bullet:
842                         /* FALLTHROUGH */
843                 case LIST_dash:
844                         /* FALLTHROUGH */
845                 case LIST_item:
846                         /* FALLTHROUGH */
847                 case LIST_hyphen:
848                         /* FALLTHROUGH */
849                 case LIST_enum:
850                         return(0);
851                 case LIST_diag:
852                         /* FALLTHROUGH */
853                 case LIST_hang:
854                         /* FALLTHROUGH */
855                 case LIST_inset:
856                         /* FALLTHROUGH */
857                 case LIST_ohang:
858                         /* FALLTHROUGH */
859                 case LIST_tag:
860                         SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
861                         bufcat_su(h, "margin-top", &su);
862                         PAIR_STYLE_INIT(&tag[1], h);
863                         print_otag(h, TAG_DT, 2, tag);
864                         if (LIST_diag != type)
865                                 break;
866                         PAIR_CLASS_INIT(&tag[0], "diag");
867                         print_otag(h, TAG_B, 1, tag);
868                         break;
869                 case LIST_column:
870                         break;
871                 default:
872                         break;
873                 }
874         } else if (MDOC_BODY == n->type) {
875                 switch (type) {
876                 case LIST_bullet:
877                         /* FALLTHROUGH */
878                 case LIST_hyphen:
879                         /* FALLTHROUGH */
880                 case LIST_dash:
881                         /* FALLTHROUGH */
882                 case LIST_enum:
883                         /* FALLTHROUGH */
884                 case LIST_item:
885                         SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
886                         bufcat_su(h, "margin-top", &su);
887                         PAIR_STYLE_INIT(&tag[1], h);
888                         print_otag(h, TAG_LI, 2, tag);
889                         break;
890                 case LIST_diag:
891                         /* FALLTHROUGH */
892                 case LIST_hang:
893                         /* FALLTHROUGH */
894                 case LIST_inset:
895                         /* FALLTHROUGH */
896                 case LIST_ohang:
897                         /* FALLTHROUGH */
898                 case LIST_tag:
899                         if (NULL == bl->norm->Bl.width) {
900                                 print_otag(h, TAG_DD, 1, tag);
901                                 break;
902                         }
903                         a2width(bl->norm->Bl.width, &su);
904                         bufcat_su(h, "margin-left", &su);
905                         PAIR_STYLE_INIT(&tag[1], h);
906                         print_otag(h, TAG_DD, 2, tag);
907                         break;
908                 case LIST_column:
909                         SCALE_VS_INIT(&su, ! bl->norm->Bl.comp);
910                         bufcat_su(h, "margin-top", &su);
911                         PAIR_STYLE_INIT(&tag[1], h);
912                         print_otag(h, TAG_TD, 2, tag);
913                         break;
914                 default:
915                         break;
916                 }
917         } else {
918                 switch (type) {
919                 case LIST_column:
920                         print_otag(h, TAG_TR, 1, tag);
921                         break;
922                 default:
923                         break;
924                 }
925         }
926
927         return(1);
928 }
929
930 static int
931 mdoc_bl_pre(MDOC_ARGS)
932 {
933         int              i;
934         struct htmlpair  tag[3];
935         struct roffsu    su;
936         char             buf[BUFSIZ];
937
938         if (MDOC_BODY == n->type) {
939                 if (LIST_column == n->norm->Bl.type)
940                         print_otag(h, TAG_TBODY, 0, NULL);
941                 return(1);
942         }
943
944         if (MDOC_HEAD == n->type) {
945                 if (LIST_column != n->norm->Bl.type)
946                         return(0);
947
948                 /*
949                  * For each column, print out the <COL> tag with our
950                  * suggested width.  The last column gets min-width, as
951                  * in terminal mode it auto-sizes to the width of the
952                  * screen and we want to preserve that behaviour.
953                  */
954
955                 for (i = 0; i < (int)n->norm->Bl.ncols; i++) {
956                         bufinit(h);
957                         a2width(n->norm->Bl.cols[i], &su);
958                         if (i < (int)n->norm->Bl.ncols - 1)
959                                 bufcat_su(h, "width", &su);
960                         else
961                                 bufcat_su(h, "min-width", &su);
962                         PAIR_STYLE_INIT(&tag[0], h);
963                         print_otag(h, TAG_COL, 1, tag);
964                 }
965
966                 return(0);
967         }
968
969         SCALE_VS_INIT(&su, 0);
970         bufinit(h);
971         bufcat_su(h, "margin-top", &su);
972         bufcat_su(h, "margin-bottom", &su);
973         PAIR_STYLE_INIT(&tag[0], h);
974
975         assert(lists[n->norm->Bl.type]);
976         (void)strlcpy(buf, "list ", BUFSIZ);
977         (void)strlcat(buf, lists[n->norm->Bl.type], BUFSIZ);
978         PAIR_INIT(&tag[1], ATTR_CLASS, buf);
979
980         /* Set the block's left-hand margin. */
981
982         if (n->norm->Bl.offs) {
983                 a2width(n->norm->Bl.offs, &su);
984                 bufcat_su(h, "margin-left", &su);
985         }
986
987         switch (n->norm->Bl.type) {
988         case LIST_bullet:
989                 /* FALLTHROUGH */
990         case LIST_dash:
991                 /* FALLTHROUGH */
992         case LIST_hyphen:
993                 /* FALLTHROUGH */
994         case LIST_item:
995                 print_otag(h, TAG_UL, 2, tag);
996                 break;
997         case LIST_enum:
998                 print_otag(h, TAG_OL, 2, tag);
999                 break;
1000         case LIST_diag:
1001                 /* FALLTHROUGH */
1002         case LIST_hang:
1003                 /* FALLTHROUGH */
1004         case LIST_inset:
1005                 /* FALLTHROUGH */
1006         case LIST_ohang:
1007                 /* FALLTHROUGH */
1008         case LIST_tag:
1009                 print_otag(h, TAG_DL, 2, tag);
1010                 break;
1011         case LIST_column:
1012                 print_otag(h, TAG_TABLE, 2, tag);
1013                 break;
1014         default:
1015                 abort();
1016                 /* NOTREACHED */
1017         }
1018
1019         return(1);
1020 }
1021
1022 static int
1023 mdoc_ex_pre(MDOC_ARGS)
1024 {
1025         struct tag      *t;
1026         struct htmlpair  tag;
1027         int              nchild;
1028
1029         if (n->prev)
1030                 print_otag(h, TAG_BR, 0, NULL);
1031
1032         PAIR_CLASS_INIT(&tag, "utility");
1033
1034         print_text(h, "The");
1035
1036         nchild = n->nchild;
1037         for (n = n->child; n; n = n->next) {
1038                 assert(MDOC_TEXT == n->type);
1039
1040                 t = print_otag(h, TAG_B, 1, &tag);
1041                 print_text(h, n->string);
1042                 print_tagq(h, t);
1043
1044                 if (nchild > 2 && n->next) {
1045                         h->flags |= HTML_NOSPACE;
1046                         print_text(h, ",");
1047                 }
1048
1049                 if (n->next && NULL == n->next->next)
1050                         print_text(h, "and");
1051         }
1052
1053         if (nchild > 1)
1054                 print_text(h, "utilities exit\\~0");
1055         else
1056                 print_text(h, "utility exits\\~0");
1057
1058         print_text(h, "on success, and\\~>0 if an error occurs.");
1059         return(0);
1060 }
1061
1062 static int
1063 mdoc_em_pre(MDOC_ARGS)
1064 {
1065         struct htmlpair tag;
1066
1067         PAIR_CLASS_INIT(&tag, "emph");
1068         print_otag(h, TAG_SPAN, 1, &tag);
1069         return(1);
1070 }
1071
1072 static int
1073 mdoc_d1_pre(MDOC_ARGS)
1074 {
1075         struct htmlpair  tag[2];
1076         struct roffsu    su;
1077
1078         if (MDOC_BLOCK != n->type)
1079                 return(1);
1080
1081         SCALE_VS_INIT(&su, 0);
1082         bufinit(h);
1083         bufcat_su(h, "margin-top", &su);
1084         bufcat_su(h, "margin-bottom", &su);
1085         PAIR_STYLE_INIT(&tag[0], h);
1086         print_otag(h, TAG_BLOCKQUOTE, 1, tag);
1087
1088         /* BLOCKQUOTE needs a block body. */
1089
1090         PAIR_CLASS_INIT(&tag[0], "display");
1091         print_otag(h, TAG_DIV, 1, tag);
1092
1093         if (MDOC_Dl == n->tok) {
1094                 PAIR_CLASS_INIT(&tag[0], "lit");
1095                 print_otag(h, TAG_CODE, 1, tag);
1096         }
1097
1098         return(1);
1099 }
1100
1101 static int
1102 mdoc_sx_pre(MDOC_ARGS)
1103 {
1104         struct htmlpair  tag[2];
1105
1106         bufinit(h);
1107         bufcat(h, "#x");
1108
1109         for (n = n->child; n; ) {
1110                 bufcat_id(h, n->string);
1111                 if (NULL != (n = n->next))
1112                         bufcat_id(h, " ");
1113         }
1114
1115         PAIR_CLASS_INIT(&tag[0], "link-sec");
1116         PAIR_HREF_INIT(&tag[1], h->buf);
1117
1118         print_otag(h, TAG_I, 1, tag);
1119         print_otag(h, TAG_A, 2, tag);
1120         return(1);
1121 }
1122
1123 static int
1124 mdoc_bd_pre(MDOC_ARGS)
1125 {
1126         struct htmlpair          tag[2];
1127         int                      comp, sv;
1128         struct mdoc_node        *nn;
1129         struct roffsu            su;
1130
1131         if (MDOC_HEAD == n->type)
1132                 return(0);
1133
1134         if (MDOC_BLOCK == n->type) {
1135                 comp = n->norm->Bd.comp;
1136                 for (nn = n; nn && ! comp; nn = nn->parent) {
1137                         if (MDOC_BLOCK != nn->type)
1138                                 continue;
1139                         if (MDOC_Ss == nn->tok || MDOC_Sh == nn->tok)
1140                                 comp = 1;
1141                         if (nn->prev)
1142                                 break;
1143                 }
1144                 if ( ! comp)
1145                         print_paragraph(h);
1146                 return(1);
1147         }
1148
1149         /* Handle the -offset argument. */
1150
1151         if (n->norm->Bd.offs == NULL ||
1152             ! strcmp(n->norm->Bd.offs, "left"))
1153                 SCALE_HS_INIT(&su, 0);
1154         else if ( ! strcmp(n->norm->Bd.offs, "indent"))
1155                 SCALE_HS_INIT(&su, INDENT);
1156         else if ( ! strcmp(n->norm->Bd.offs, "indent-two"))
1157                 SCALE_HS_INIT(&su, INDENT * 2);
1158         else
1159                 a2width(n->norm->Bd.offs, &su);
1160
1161         bufinit(h);
1162         bufcat_su(h, "margin-left", &su);
1163         PAIR_STYLE_INIT(&tag[0], h);
1164
1165         if (DISP_unfilled != n->norm->Bd.type &&
1166             DISP_literal != n->norm->Bd.type) {
1167                 PAIR_CLASS_INIT(&tag[1], "display");
1168                 print_otag(h, TAG_DIV, 2, tag);
1169                 return(1);
1170         }
1171
1172         PAIR_CLASS_INIT(&tag[1], "lit display");
1173         print_otag(h, TAG_PRE, 2, tag);
1174
1175         /* This can be recursive: save & set our literal state. */
1176
1177         sv = h->flags & HTML_LITERAL;
1178         h->flags |= HTML_LITERAL;
1179
1180         for (nn = n->child; nn; nn = nn->next) {
1181                 print_mdoc_node(meta, nn, h);
1182                 /*
1183                  * If the printed node flushes its own line, then we
1184                  * needn't do it here as well.  This is hacky, but the
1185                  * notion of selective eoln whitespace is pretty dumb
1186                  * anyway, so don't sweat it.
1187                  */
1188                 switch (nn->tok) {
1189                 case MDOC_Sm:
1190                         /* FALLTHROUGH */
1191                 case MDOC_br:
1192                         /* FALLTHROUGH */
1193                 case MDOC_sp:
1194                         /* FALLTHROUGH */
1195                 case MDOC_Bl:
1196                         /* FALLTHROUGH */
1197                 case MDOC_D1:
1198                         /* FALLTHROUGH */
1199                 case MDOC_Dl:
1200                         /* FALLTHROUGH */
1201                 case MDOC_Lp:
1202                         /* FALLTHROUGH */
1203                 case MDOC_Pp:
1204                         continue;
1205                 default:
1206                         break;
1207                 }
1208                 if (h->flags & HTML_NONEWLINE ||
1209                     (nn->next && ! (nn->next->flags & MDOC_LINE)))
1210                         continue;
1211                 else if (nn->next)
1212                         print_text(h, "\n");
1213
1214                 h->flags |= HTML_NOSPACE;
1215         }
1216
1217         if (0 == sv)
1218                 h->flags &= ~HTML_LITERAL;
1219
1220         return(0);
1221 }
1222
1223 static int
1224 mdoc_pa_pre(MDOC_ARGS)
1225 {
1226         struct htmlpair tag;
1227
1228         PAIR_CLASS_INIT(&tag, "file");
1229         print_otag(h, TAG_I, 1, &tag);
1230         return(1);
1231 }
1232
1233 static int
1234 mdoc_ad_pre(MDOC_ARGS)
1235 {
1236         struct htmlpair tag;
1237
1238         PAIR_CLASS_INIT(&tag, "addr");
1239         print_otag(h, TAG_I, 1, &tag);
1240         return(1);
1241 }
1242
1243 static int
1244 mdoc_an_pre(MDOC_ARGS)
1245 {
1246         struct htmlpair tag;
1247
1248         if (n->norm->An.auth == AUTH_split) {
1249                 h->flags &= ~HTML_NOSPLIT;
1250                 h->flags |= HTML_SPLIT;
1251                 return(0);
1252         }
1253         if (n->norm->An.auth == AUTH_nosplit) {
1254                 h->flags &= ~HTML_SPLIT;
1255                 h->flags |= HTML_NOSPLIT;
1256                 return(0);
1257         }
1258
1259         if (h->flags & HTML_SPLIT)
1260                 print_otag(h, TAG_BR, 0, NULL);
1261
1262         if (n->sec == SEC_AUTHORS && ! (h->flags & HTML_NOSPLIT))
1263                 h->flags |= HTML_SPLIT;
1264
1265         PAIR_CLASS_INIT(&tag, "author");
1266         print_otag(h, TAG_SPAN, 1, &tag);
1267         return(1);
1268 }
1269
1270 static int
1271 mdoc_cd_pre(MDOC_ARGS)
1272 {
1273         struct htmlpair tag;
1274
1275         synopsis_pre(h, n);
1276         PAIR_CLASS_INIT(&tag, "config");
1277         print_otag(h, TAG_B, 1, &tag);
1278         return(1);
1279 }
1280
1281 static int
1282 mdoc_dv_pre(MDOC_ARGS)
1283 {
1284         struct htmlpair tag;
1285
1286         PAIR_CLASS_INIT(&tag, "define");
1287         print_otag(h, TAG_SPAN, 1, &tag);
1288         return(1);
1289 }
1290
1291 static int
1292 mdoc_ev_pre(MDOC_ARGS)
1293 {
1294         struct htmlpair tag;
1295
1296         PAIR_CLASS_INIT(&tag, "env");
1297         print_otag(h, TAG_SPAN, 1, &tag);
1298         return(1);
1299 }
1300
1301 static int
1302 mdoc_er_pre(MDOC_ARGS)
1303 {
1304         struct htmlpair tag;
1305
1306         PAIR_CLASS_INIT(&tag, "errno");
1307         print_otag(h, TAG_SPAN, 1, &tag);
1308         return(1);
1309 }
1310
1311 static int
1312 mdoc_fa_pre(MDOC_ARGS)
1313 {
1314         const struct mdoc_node  *nn;
1315         struct htmlpair          tag;
1316         struct tag              *t;
1317
1318         PAIR_CLASS_INIT(&tag, "farg");
1319         if (n->parent->tok != MDOC_Fo) {
1320                 print_otag(h, TAG_I, 1, &tag);
1321                 return(1);
1322         }
1323
1324         for (nn = n->child; nn; nn = nn->next) {
1325                 t = print_otag(h, TAG_I, 1, &tag);
1326                 print_text(h, nn->string);
1327                 print_tagq(h, t);
1328                 if (nn->next) {
1329                         h->flags |= HTML_NOSPACE;
1330                         print_text(h, ",");
1331                 }
1332         }
1333
1334         if (n->child && n->next && n->next->tok == MDOC_Fa) {
1335                 h->flags |= HTML_NOSPACE;
1336                 print_text(h, ",");
1337         }
1338
1339         return(0);
1340 }
1341
1342 static int
1343 mdoc_fd_pre(MDOC_ARGS)
1344 {
1345         struct htmlpair  tag[2];
1346         char             buf[BUFSIZ];
1347         size_t           sz;
1348         int              i;
1349         struct tag      *t;
1350
1351         synopsis_pre(h, n);
1352
1353         if (NULL == (n = n->child))
1354                 return(0);
1355
1356         assert(MDOC_TEXT == n->type);
1357
1358         if (strcmp(n->string, "#include")) {
1359                 PAIR_CLASS_INIT(&tag[0], "macro");
1360                 print_otag(h, TAG_B, 1, tag);
1361                 return(1);
1362         }
1363
1364         PAIR_CLASS_INIT(&tag[0], "includes");
1365         print_otag(h, TAG_B, 1, tag);
1366         print_text(h, n->string);
1367
1368         if (NULL != (n = n->next)) {
1369                 assert(MDOC_TEXT == n->type);
1370
1371                 /*
1372                  * XXX This is broken and not easy to fix.
1373                  * When using -Oincludes, truncation may occur.
1374                  * Dynamic allocation wouldn't help because
1375                  * passing long strings to buffmt_includes()
1376                  * does not work either.
1377                  */
1378
1379                 strlcpy(buf, '<' == *n->string || '"' == *n->string ?
1380                     n->string + 1 : n->string, BUFSIZ);
1381
1382                 sz = strlen(buf);
1383                 if (sz && ('>' == buf[sz - 1] || '"' == buf[sz - 1]))
1384                         buf[sz - 1] = '\0';
1385
1386                 PAIR_CLASS_INIT(&tag[0], "link-includes");
1387
1388                 i = 1;
1389                 if (h->base_includes) {
1390                         buffmt_includes(h, buf);
1391                         PAIR_HREF_INIT(&tag[i], h->buf);
1392                         i++;
1393                 }
1394
1395                 t = print_otag(h, TAG_A, i, tag);
1396                 print_text(h, n->string);
1397                 print_tagq(h, t);
1398
1399                 n = n->next;
1400         }
1401
1402         for ( ; n; n = n->next) {
1403                 assert(MDOC_TEXT == n->type);
1404                 print_text(h, n->string);
1405         }
1406
1407         return(0);
1408 }
1409
1410 static int
1411 mdoc_vt_pre(MDOC_ARGS)
1412 {
1413         struct htmlpair  tag;
1414
1415         if (MDOC_BLOCK == n->type) {
1416                 synopsis_pre(h, n);
1417                 return(1);
1418         } else if (MDOC_ELEM == n->type) {
1419                 synopsis_pre(h, n);
1420         } else if (MDOC_HEAD == n->type)
1421                 return(0);
1422
1423         PAIR_CLASS_INIT(&tag, "type");
1424         print_otag(h, TAG_SPAN, 1, &tag);
1425         return(1);
1426 }
1427
1428 static int
1429 mdoc_ft_pre(MDOC_ARGS)
1430 {
1431         struct htmlpair  tag;
1432
1433         synopsis_pre(h, n);
1434         PAIR_CLASS_INIT(&tag, "ftype");
1435         print_otag(h, TAG_I, 1, &tag);
1436         return(1);
1437 }
1438
1439 static int
1440 mdoc_fn_pre(MDOC_ARGS)
1441 {
1442         struct tag      *t;
1443         struct htmlpair  tag[2];
1444         char             nbuf[BUFSIZ];
1445         const char      *sp, *ep;
1446         int              sz, i, pretty;
1447
1448         pretty = MDOC_SYNPRETTY & n->flags;
1449         synopsis_pre(h, n);
1450
1451         /* Split apart into type and name. */
1452         assert(n->child->string);
1453         sp = n->child->string;
1454
1455         ep = strchr(sp, ' ');
1456         if (NULL != ep) {
1457                 PAIR_CLASS_INIT(&tag[0], "ftype");
1458                 t = print_otag(h, TAG_I, 1, tag);
1459
1460                 while (ep) {
1461                         sz = MIN((int)(ep - sp), BUFSIZ - 1);
1462                         (void)memcpy(nbuf, sp, (size_t)sz);
1463                         nbuf[sz] = '\0';
1464                         print_text(h, nbuf);
1465                         sp = ++ep;
1466                         ep = strchr(sp, ' ');
1467                 }
1468                 print_tagq(h, t);
1469         }
1470
1471         PAIR_CLASS_INIT(&tag[0], "fname");
1472
1473         /*
1474          * FIXME: only refer to IDs that we know exist.
1475          */
1476
1477 #if 0
1478         if (MDOC_SYNPRETTY & n->flags) {
1479                 nbuf[0] = '\0';
1480                 html_idcat(nbuf, sp, BUFSIZ);
1481                 PAIR_ID_INIT(&tag[1], nbuf);
1482         } else {
1483                 strlcpy(nbuf, "#", BUFSIZ);
1484                 html_idcat(nbuf, sp, BUFSIZ);
1485                 PAIR_HREF_INIT(&tag[1], nbuf);
1486         }
1487 #endif
1488
1489         t = print_otag(h, TAG_B, 1, tag);
1490
1491         if (sp)
1492                 print_text(h, sp);
1493
1494         print_tagq(h, t);
1495
1496         h->flags |= HTML_NOSPACE;
1497         print_text(h, "(");
1498         h->flags |= HTML_NOSPACE;
1499
1500         PAIR_CLASS_INIT(&tag[0], "farg");
1501         bufinit(h);
1502         bufcat_style(h, "white-space", "nowrap");
1503         PAIR_STYLE_INIT(&tag[1], h);
1504
1505         for (n = n->child->next; n; n = n->next) {
1506                 i = 1;
1507                 if (MDOC_SYNPRETTY & n->flags)
1508                         i = 2;
1509                 t = print_otag(h, TAG_I, i, tag);
1510                 print_text(h, n->string);
1511                 print_tagq(h, t);
1512                 if (n->next) {
1513                         h->flags |= HTML_NOSPACE;
1514                         print_text(h, ",");
1515                 }
1516         }
1517
1518         h->flags |= HTML_NOSPACE;
1519         print_text(h, ")");
1520
1521         if (pretty) {
1522                 h->flags |= HTML_NOSPACE;
1523                 print_text(h, ";");
1524         }
1525
1526         return(0);
1527 }
1528
1529 static int
1530 mdoc_sm_pre(MDOC_ARGS)
1531 {
1532
1533         if (NULL == n->child)
1534                 h->flags ^= HTML_NONOSPACE;
1535         else if (0 == strcmp("on", n->child->string))
1536                 h->flags &= ~HTML_NONOSPACE;
1537         else
1538                 h->flags |= HTML_NONOSPACE;
1539
1540         if ( ! (HTML_NONOSPACE & h->flags))
1541                 h->flags &= ~HTML_NOSPACE;
1542
1543         return(0);
1544 }
1545
1546 static int
1547 mdoc_skip_pre(MDOC_ARGS)
1548 {
1549
1550         return(0);
1551 }
1552
1553 static int
1554 mdoc_pp_pre(MDOC_ARGS)
1555 {
1556
1557         print_paragraph(h);
1558         return(0);
1559 }
1560
1561 static int
1562 mdoc_sp_pre(MDOC_ARGS)
1563 {
1564         struct roffsu    su;
1565         struct htmlpair  tag;
1566
1567         SCALE_VS_INIT(&su, 1);
1568
1569         if (MDOC_sp == n->tok) {
1570                 if (NULL != (n = n->child)) {
1571                         if ( ! a2roffsu(n->string, &su, SCALE_VS))
1572                                 su.scale = 1.0;
1573                         else if (su.scale < 0.0)
1574                                 su.scale = 0.0;
1575                 }
1576         } else
1577                 su.scale = 0.0;
1578
1579         bufinit(h);
1580         bufcat_su(h, "height", &su);
1581         PAIR_STYLE_INIT(&tag, h);
1582         print_otag(h, TAG_DIV, 1, &tag);
1583
1584         /* So the div isn't empty: */
1585         print_text(h, "\\~");
1586
1587         return(0);
1588
1589 }
1590
1591 static int
1592 mdoc_lk_pre(MDOC_ARGS)
1593 {
1594         struct htmlpair  tag[2];
1595
1596         if (NULL == (n = n->child))
1597                 return(0);
1598
1599         assert(MDOC_TEXT == n->type);
1600
1601         PAIR_CLASS_INIT(&tag[0], "link-ext");
1602         PAIR_HREF_INIT(&tag[1], n->string);
1603
1604         print_otag(h, TAG_A, 2, tag);
1605
1606         if (NULL == n->next)
1607                 print_text(h, n->string);
1608
1609         for (n = n->next; n; n = n->next)
1610                 print_text(h, n->string);
1611
1612         return(0);
1613 }
1614
1615 static int
1616 mdoc_mt_pre(MDOC_ARGS)
1617 {
1618         struct htmlpair  tag[2];
1619         struct tag      *t;
1620
1621         PAIR_CLASS_INIT(&tag[0], "link-mail");
1622
1623         for (n = n->child; n; n = n->next) {
1624                 assert(MDOC_TEXT == n->type);
1625
1626                 bufinit(h);
1627                 bufcat(h, "mailto:");
1628                 bufcat(h, n->string);
1629
1630                 PAIR_HREF_INIT(&tag[1], h->buf);
1631                 t = print_otag(h, TAG_A, 2, tag);
1632                 print_text(h, n->string);
1633                 print_tagq(h, t);
1634         }
1635
1636         return(0);
1637 }
1638
1639 static int
1640 mdoc_fo_pre(MDOC_ARGS)
1641 {
1642         struct htmlpair  tag;
1643         struct tag      *t;
1644
1645         if (MDOC_BODY == n->type) {
1646                 h->flags |= HTML_NOSPACE;
1647                 print_text(h, "(");
1648                 h->flags |= HTML_NOSPACE;
1649                 return(1);
1650         } else if (MDOC_BLOCK == n->type) {
1651                 synopsis_pre(h, n);
1652                 return(1);
1653         }
1654
1655         /* XXX: we drop non-initial arguments as per groff. */
1656
1657         assert(n->child);
1658         assert(n->child->string);
1659
1660         PAIR_CLASS_INIT(&tag, "fname");
1661         t = print_otag(h, TAG_B, 1, &tag);
1662         print_text(h, n->child->string);
1663         print_tagq(h, t);
1664         return(0);
1665 }
1666
1667 static void
1668 mdoc_fo_post(MDOC_ARGS)
1669 {
1670
1671         if (MDOC_BODY != n->type)
1672                 return;
1673         h->flags |= HTML_NOSPACE;
1674         print_text(h, ")");
1675         h->flags |= HTML_NOSPACE;
1676         print_text(h, ";");
1677 }
1678
1679 static int
1680 mdoc_in_pre(MDOC_ARGS)
1681 {
1682         struct tag      *t;
1683         struct htmlpair  tag[2];
1684         int              i;
1685
1686         synopsis_pre(h, n);
1687
1688         PAIR_CLASS_INIT(&tag[0], "includes");
1689         print_otag(h, TAG_B, 1, tag);
1690
1691         /*
1692          * The first argument of the `In' gets special treatment as
1693          * being a linked value.  Subsequent values are printed
1694          * afterward.  groff does similarly.  This also handles the case
1695          * of no children.
1696          */
1697
1698         if (MDOC_SYNPRETTY & n->flags && MDOC_LINE & n->flags)
1699                 print_text(h, "#include");
1700
1701         print_text(h, "<");
1702         h->flags |= HTML_NOSPACE;
1703
1704         if (NULL != (n = n->child)) {
1705                 assert(MDOC_TEXT == n->type);
1706
1707                 PAIR_CLASS_INIT(&tag[0], "link-includes");
1708
1709                 i = 1;
1710                 if (h->base_includes) {
1711                         buffmt_includes(h, n->string);
1712                         PAIR_HREF_INIT(&tag[i], h->buf);
1713                         i++;
1714                 }
1715
1716                 t = print_otag(h, TAG_A, i, tag);
1717                 print_text(h, n->string);
1718                 print_tagq(h, t);
1719
1720                 n = n->next;
1721         }
1722
1723         h->flags |= HTML_NOSPACE;
1724         print_text(h, ">");
1725
1726         for ( ; n; n = n->next) {
1727                 assert(MDOC_TEXT == n->type);
1728                 print_text(h, n->string);
1729         }
1730
1731         return(0);
1732 }
1733
1734 static int
1735 mdoc_ic_pre(MDOC_ARGS)
1736 {
1737         struct htmlpair tag;
1738
1739         PAIR_CLASS_INIT(&tag, "cmd");
1740         print_otag(h, TAG_B, 1, &tag);
1741         return(1);
1742 }
1743
1744 static int
1745 mdoc_rv_pre(MDOC_ARGS)
1746 {
1747         struct htmlpair  tag;
1748         struct tag      *t;
1749         int              nchild;
1750
1751         if (n->prev)
1752                 print_otag(h, TAG_BR, 0, NULL);
1753
1754         PAIR_CLASS_INIT(&tag, "fname");
1755
1756         nchild = n->nchild;
1757         if (nchild > 0) {
1758                 print_text(h, "The");
1759
1760                 for (n = n->child; n; n = n->next) {
1761                         t = print_otag(h, TAG_B, 1, &tag);
1762                         print_text(h, n->string);
1763                         print_tagq(h, t);
1764
1765                         h->flags |= HTML_NOSPACE;
1766                         print_text(h, "()");
1767
1768                         if (n->next == NULL)
1769                                 continue;
1770
1771                         if (nchild > 2) {
1772                                 h->flags |= HTML_NOSPACE;
1773                                 print_text(h, ",");
1774                         }
1775                         if (n->next->next == NULL)
1776                                 print_text(h, "and");
1777                 }
1778
1779                 if (nchild > 1)
1780                         print_text(h, "functions return");
1781                 else
1782                         print_text(h, "function returns");
1783
1784                 print_text(h, "the value\\~0 if successful;");
1785         } else
1786                 print_text(h, "Upon successful completion,"
1787                     " the value\\~0 is returned;");
1788
1789         print_text(h, "otherwise the value\\~\\-1 is returned"
1790            " and the global variable");
1791
1792         PAIR_CLASS_INIT(&tag, "var");
1793         t = print_otag(h, TAG_B, 1, &tag);
1794         print_text(h, "errno");
1795         print_tagq(h, t);
1796         print_text(h, "is set to indicate the error.");
1797         return(0);
1798 }
1799
1800 static int
1801 mdoc_va_pre(MDOC_ARGS)
1802 {
1803         struct htmlpair tag;
1804
1805         PAIR_CLASS_INIT(&tag, "var");
1806         print_otag(h, TAG_B, 1, &tag);
1807         return(1);
1808 }
1809
1810 static int
1811 mdoc_ap_pre(MDOC_ARGS)
1812 {
1813
1814         h->flags |= HTML_NOSPACE;
1815         print_text(h, "\\(aq");
1816         h->flags |= HTML_NOSPACE;
1817         return(1);
1818 }
1819
1820 static int
1821 mdoc_bf_pre(MDOC_ARGS)
1822 {
1823         struct htmlpair  tag[2];
1824         struct roffsu    su;
1825
1826         if (MDOC_HEAD == n->type)
1827                 return(0);
1828         else if (MDOC_BODY != n->type)
1829                 return(1);
1830
1831         if (FONT_Em == n->norm->Bf.font)
1832                 PAIR_CLASS_INIT(&tag[0], "emph");
1833         else if (FONT_Sy == n->norm->Bf.font)
1834                 PAIR_CLASS_INIT(&tag[0], "symb");
1835         else if (FONT_Li == n->norm->Bf.font)
1836                 PAIR_CLASS_INIT(&tag[0], "lit");
1837         else
1838                 PAIR_CLASS_INIT(&tag[0], "none");
1839
1840         /*
1841          * We want this to be inline-formatted, but needs to be div to
1842          * accept block children.
1843          */
1844         bufinit(h);
1845         bufcat_style(h, "display", "inline");
1846         SCALE_HS_INIT(&su, 1);
1847         /* Needs a left-margin for spacing. */
1848         bufcat_su(h, "margin-left", &su);
1849         PAIR_STYLE_INIT(&tag[1], h);
1850         print_otag(h, TAG_DIV, 2, tag);
1851         return(1);
1852 }
1853
1854 static int
1855 mdoc_ms_pre(MDOC_ARGS)
1856 {
1857         struct htmlpair tag;
1858
1859         PAIR_CLASS_INIT(&tag, "symb");
1860         print_otag(h, TAG_SPAN, 1, &tag);
1861         return(1);
1862 }
1863
1864 static int
1865 mdoc_igndelim_pre(MDOC_ARGS)
1866 {
1867
1868         h->flags |= HTML_IGNDELIM;
1869         return(1);
1870 }
1871
1872 static void
1873 mdoc_pf_post(MDOC_ARGS)
1874 {
1875
1876         if ( ! (n->next == NULL || n->next->flags & MDOC_LINE))
1877                 h->flags |= HTML_NOSPACE;
1878 }
1879
1880 static int
1881 mdoc_rs_pre(MDOC_ARGS)
1882 {
1883         struct htmlpair  tag;
1884
1885         if (MDOC_BLOCK != n->type)
1886                 return(1);
1887
1888         if (n->prev && SEC_SEE_ALSO == n->sec)
1889                 print_paragraph(h);
1890
1891         PAIR_CLASS_INIT(&tag, "ref");
1892         print_otag(h, TAG_SPAN, 1, &tag);
1893         return(1);
1894 }
1895
1896 static int
1897 mdoc_no_pre(MDOC_ARGS)
1898 {
1899         struct htmlpair tag;
1900
1901         PAIR_CLASS_INIT(&tag, "none");
1902         print_otag(h, TAG_CODE, 1, &tag);
1903         return(1);
1904 }
1905
1906 static int
1907 mdoc_li_pre(MDOC_ARGS)
1908 {
1909         struct htmlpair tag;
1910
1911         PAIR_CLASS_INIT(&tag, "lit");
1912         print_otag(h, TAG_CODE, 1, &tag);
1913         return(1);
1914 }
1915
1916 static int
1917 mdoc_sy_pre(MDOC_ARGS)
1918 {
1919         struct htmlpair tag;
1920
1921         PAIR_CLASS_INIT(&tag, "symb");
1922         print_otag(h, TAG_SPAN, 1, &tag);
1923         return(1);
1924 }
1925
1926 static int
1927 mdoc_bt_pre(MDOC_ARGS)
1928 {
1929
1930         print_text(h, "is currently in beta test.");
1931         return(0);
1932 }
1933
1934 static int
1935 mdoc_ud_pre(MDOC_ARGS)
1936 {
1937
1938         print_text(h, "currently under development.");
1939         return(0);
1940 }
1941
1942 static int
1943 mdoc_lb_pre(MDOC_ARGS)
1944 {
1945         struct htmlpair tag;
1946
1947         if (SEC_LIBRARY == n->sec && MDOC_LINE & n->flags && n->prev)
1948                 print_otag(h, TAG_BR, 0, NULL);
1949
1950         PAIR_CLASS_INIT(&tag, "lib");
1951         print_otag(h, TAG_SPAN, 1, &tag);
1952         return(1);
1953 }
1954
1955 static int
1956 mdoc__x_pre(MDOC_ARGS)
1957 {
1958         struct htmlpair tag[2];
1959         enum htmltag    t;
1960
1961         t = TAG_SPAN;
1962
1963         switch (n->tok) {
1964         case MDOC__A:
1965                 PAIR_CLASS_INIT(&tag[0], "ref-auth");
1966                 if (n->prev && MDOC__A == n->prev->tok)
1967                         if (NULL == n->next || MDOC__A != n->next->tok)
1968                                 print_text(h, "and");
1969                 break;
1970         case MDOC__B:
1971                 PAIR_CLASS_INIT(&tag[0], "ref-book");
1972                 t = TAG_I;
1973                 break;
1974         case MDOC__C:
1975                 PAIR_CLASS_INIT(&tag[0], "ref-city");
1976                 break;
1977         case MDOC__D:
1978                 PAIR_CLASS_INIT(&tag[0], "ref-date");
1979                 break;
1980         case MDOC__I:
1981                 PAIR_CLASS_INIT(&tag[0], "ref-issue");
1982                 t = TAG_I;
1983                 break;
1984         case MDOC__J:
1985                 PAIR_CLASS_INIT(&tag[0], "ref-jrnl");
1986                 t = TAG_I;
1987                 break;
1988         case MDOC__N:
1989                 PAIR_CLASS_INIT(&tag[0], "ref-num");
1990                 break;
1991         case MDOC__O:
1992                 PAIR_CLASS_INIT(&tag[0], "ref-opt");
1993                 break;
1994         case MDOC__P:
1995                 PAIR_CLASS_INIT(&tag[0], "ref-page");
1996                 break;
1997         case MDOC__Q:
1998                 PAIR_CLASS_INIT(&tag[0], "ref-corp");
1999                 break;
2000         case MDOC__R:
2001                 PAIR_CLASS_INIT(&tag[0], "ref-rep");
2002                 break;
2003         case MDOC__T:
2004                 PAIR_CLASS_INIT(&tag[0], "ref-title");
2005                 break;
2006         case MDOC__U:
2007                 PAIR_CLASS_INIT(&tag[0], "link-ref");
2008                 break;
2009         case MDOC__V:
2010                 PAIR_CLASS_INIT(&tag[0], "ref-vol");
2011                 break;
2012         default:
2013                 abort();
2014                 /* NOTREACHED */
2015         }
2016
2017         if (MDOC__U != n->tok) {
2018                 print_otag(h, t, 1, tag);
2019                 return(1);
2020         }
2021
2022         PAIR_HREF_INIT(&tag[1], n->child->string);
2023         print_otag(h, TAG_A, 2, tag);
2024
2025         return(1);
2026 }
2027
2028 static void
2029 mdoc__x_post(MDOC_ARGS)
2030 {
2031
2032         if (MDOC__A == n->tok && n->next && MDOC__A == n->next->tok)
2033                 if (NULL == n->next->next || MDOC__A != n->next->next->tok)
2034                         if (NULL == n->prev || MDOC__A != n->prev->tok)
2035                                 return;
2036
2037         /* TODO: %U */
2038
2039         if (NULL == n->parent || MDOC_Rs != n->parent->tok)
2040                 return;
2041
2042         h->flags |= HTML_NOSPACE;
2043         print_text(h, n->next ? "," : ".");
2044 }
2045
2046 static int
2047 mdoc_bk_pre(MDOC_ARGS)
2048 {
2049
2050         switch (n->type) {
2051         case MDOC_BLOCK:
2052                 break;
2053         case MDOC_HEAD:
2054                 return(0);
2055         case MDOC_BODY:
2056                 if (n->parent->args || 0 == n->prev->nchild)
2057                         h->flags |= HTML_PREKEEP;
2058                 break;
2059         default:
2060                 abort();
2061                 /* NOTREACHED */
2062         }
2063
2064         return(1);
2065 }
2066
2067 static void
2068 mdoc_bk_post(MDOC_ARGS)
2069 {
2070
2071         if (MDOC_BODY == n->type)
2072                 h->flags &= ~(HTML_KEEP | HTML_PREKEEP);
2073 }
2074
2075 static int
2076 mdoc_quote_pre(MDOC_ARGS)
2077 {
2078         struct htmlpair tag;
2079
2080         if (MDOC_BODY != n->type)
2081                 return(1);
2082
2083         switch (n->tok) {
2084         case MDOC_Ao:
2085                 /* FALLTHROUGH */
2086         case MDOC_Aq:
2087                 print_text(h, n->nchild == 1 &&
2088                     n->child->tok == MDOC_Mt ?  "<" : "\\(la");
2089                 break;
2090         case MDOC_Bro:
2091                 /* FALLTHROUGH */
2092         case MDOC_Brq:
2093                 print_text(h, "\\(lC");
2094                 break;
2095         case MDOC_Bo:
2096                 /* FALLTHROUGH */
2097         case MDOC_Bq:
2098                 print_text(h, "\\(lB");
2099                 break;
2100         case MDOC_Oo:
2101                 /* FALLTHROUGH */
2102         case MDOC_Op:
2103                 print_text(h, "\\(lB");
2104                 h->flags |= HTML_NOSPACE;
2105                 PAIR_CLASS_INIT(&tag, "opt");
2106                 print_otag(h, TAG_SPAN, 1, &tag);
2107                 break;
2108         case MDOC_En:
2109                 if (NULL == n->norm->Es ||
2110                     NULL == n->norm->Es->child)
2111                         return(1);
2112                 print_text(h, n->norm->Es->child->string);
2113                 break;
2114         case MDOC_Do:
2115                 /* FALLTHROUGH */
2116         case MDOC_Dq:
2117                 /* FALLTHROUGH */
2118         case MDOC_Qo:
2119                 /* FALLTHROUGH */
2120         case MDOC_Qq:
2121                 print_text(h, "\\(lq");
2122                 break;
2123         case MDOC_Po:
2124                 /* FALLTHROUGH */
2125         case MDOC_Pq:
2126                 print_text(h, "(");
2127                 break;
2128         case MDOC_Ql:
2129                 print_text(h, "\\(oq");
2130                 h->flags |= HTML_NOSPACE;
2131                 PAIR_CLASS_INIT(&tag, "lit");
2132                 print_otag(h, TAG_CODE, 1, &tag);
2133                 break;
2134         case MDOC_So:
2135                 /* FALLTHROUGH */
2136         case MDOC_Sq:
2137                 print_text(h, "\\(oq");
2138                 break;
2139         default:
2140                 abort();
2141                 /* NOTREACHED */
2142         }
2143
2144         h->flags |= HTML_NOSPACE;
2145         return(1);
2146 }
2147
2148 static void
2149 mdoc_quote_post(MDOC_ARGS)
2150 {
2151
2152         if (n->type != MDOC_BODY && n->type != MDOC_ELEM)
2153                 return;
2154
2155         h->flags |= HTML_NOSPACE;
2156
2157         switch (n->tok) {
2158         case MDOC_Ao:
2159                 /* FALLTHROUGH */
2160         case MDOC_Aq:
2161                 print_text(h, n->nchild == 1 &&
2162                     n->child->tok == MDOC_Mt ?  ">" : "\\(ra");
2163                 break;
2164         case MDOC_Bro:
2165                 /* FALLTHROUGH */
2166         case MDOC_Brq:
2167                 print_text(h, "\\(rC");
2168                 break;
2169         case MDOC_Oo:
2170                 /* FALLTHROUGH */
2171         case MDOC_Op:
2172                 /* FALLTHROUGH */
2173         case MDOC_Bo:
2174                 /* FALLTHROUGH */
2175         case MDOC_Bq:
2176                 print_text(h, "\\(rB");
2177                 break;
2178         case MDOC_En:
2179                 if (n->norm->Es == NULL ||
2180                     n->norm->Es->child == NULL ||
2181                     n->norm->Es->child->next == NULL)
2182                         h->flags &= ~HTML_NOSPACE;
2183                 else
2184                         print_text(h, n->norm->Es->child->next->string);
2185                 break;
2186         case MDOC_Qo:
2187                 /* FALLTHROUGH */
2188         case MDOC_Qq:
2189                 /* FALLTHROUGH */
2190         case MDOC_Do:
2191                 /* FALLTHROUGH */
2192         case MDOC_Dq:
2193                 print_text(h, "\\(rq");
2194                 break;
2195         case MDOC_Po:
2196                 /* FALLTHROUGH */
2197         case MDOC_Pq:
2198                 print_text(h, ")");
2199                 break;
2200         case MDOC_Ql:
2201                 /* FALLTHROUGH */
2202         case MDOC_So:
2203                 /* FALLTHROUGH */
2204         case MDOC_Sq:
2205                 print_text(h, "\\(cq");
2206                 break;
2207         default:
2208                 abort();
2209                 /* NOTREACHED */
2210         }
2211 }
2212
2213 static int
2214 mdoc_eo_pre(MDOC_ARGS)
2215 {
2216
2217         if (n->type != MDOC_BODY)
2218                 return(1);
2219
2220         if (n->end == ENDBODY_NOT &&
2221             n->parent->head->child == NULL &&
2222             n->child != NULL &&
2223             n->child->end != ENDBODY_NOT)
2224                 print_text(h, "\\&");
2225         else if (n->end != ENDBODY_NOT ? n->child != NULL :
2226             n->parent->head->child != NULL && (n->child != NULL ||
2227             (n->parent->tail != NULL && n->parent->tail->child != NULL)))
2228                 h->flags |= HTML_NOSPACE;
2229         return(1);
2230 }
2231
2232 static void
2233 mdoc_eo_post(MDOC_ARGS)
2234 {
2235         int      body, tail;
2236
2237         if (n->type != MDOC_BODY)
2238                 return;
2239
2240         if (n->end != ENDBODY_NOT) {
2241                 h->flags &= ~HTML_NOSPACE;
2242                 return;
2243         }
2244
2245         body = n->child != NULL || n->parent->head->child != NULL;
2246         tail = n->parent->tail != NULL && n->parent->tail->child != NULL;
2247
2248         if (body && tail)
2249                 h->flags |= HTML_NOSPACE;
2250         else if ( ! tail)
2251                 h->flags &= ~HTML_NOSPACE;
2252 }