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