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