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