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