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