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 }