1 /*      $Id: man_html.c,v 1.153 2018/07/27 17:49:31 schwarze Exp $ */
   2 /*
   3  * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv>
   4  * Copyright (c) 2013,2014,2015,2017,2018 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 
  28 #include "mandoc_aux.h"
  29 #include "mandoc.h"
  30 #include "roff.h"
  31 #include "man.h"
  32 #include "out.h"
  33 #include "html.h"
  34 #include "main.h"
  35 
  36 /* FIXME: have PD set the default vspace width. */
  37 
  38 #define MAN_ARGS          const struct roff_meta *man, \
  39                           const struct roff_node *n, \
  40                           struct html *h
  41 
  42 struct  htmlman {
  43         int             (*pre)(MAN_ARGS);
  44         int             (*post)(MAN_ARGS);
  45 };
  46 
  47 static  void              print_bvspace(struct html *,
  48                                 const struct roff_node *);
  49 static  void              print_man_head(const struct roff_meta *,
  50                                 struct html *);
  51 static  void              print_man_nodelist(MAN_ARGS);
  52 static  void              print_man_node(MAN_ARGS);
  53 static  int               fillmode(struct html *, int);
  54 static  int               man_B_pre(MAN_ARGS);
  55 static  int               man_HP_pre(MAN_ARGS);
  56 static  int               man_IP_pre(MAN_ARGS);
  57 static  int               man_I_pre(MAN_ARGS);
  58 static  int               man_OP_pre(MAN_ARGS);
  59 static  int               man_PP_pre(MAN_ARGS);
  60 static  int               man_RS_pre(MAN_ARGS);
  61 static  int               man_SH_pre(MAN_ARGS);
  62 static  int               man_SM_pre(MAN_ARGS);
  63 static  int               man_SS_pre(MAN_ARGS);
  64 static  int               man_UR_pre(MAN_ARGS);
  65 static  int               man_alt_pre(MAN_ARGS);
  66 static  int               man_ign_pre(MAN_ARGS);
  67 static  int               man_in_pre(MAN_ARGS);
  68 static  void              man_root_post(const struct roff_meta *,
  69                                 struct html *);
  70 static  void              man_root_pre(const struct roff_meta *,
  71                                 struct html *);
  72 
  73 static  const struct htmlman __mans[MAN_MAX - MAN_TH] = {
  74         { NULL, NULL }, /* TH */
  75         { man_SH_pre, NULL }, /* SH */
  76         { man_SS_pre, NULL }, /* SS */
  77         { man_IP_pre, NULL }, /* TP */
  78         { man_PP_pre, NULL }, /* LP */
  79         { man_PP_pre, NULL }, /* PP */
  80         { man_PP_pre, NULL }, /* P */
  81         { man_IP_pre, NULL }, /* IP */
  82         { man_HP_pre, NULL }, /* HP */
  83         { man_SM_pre, NULL }, /* SM */
  84         { man_SM_pre, NULL }, /* SB */
  85         { man_alt_pre, NULL }, /* BI */
  86         { man_alt_pre, NULL }, /* IB */
  87         { man_alt_pre, NULL }, /* BR */
  88         { man_alt_pre, NULL }, /* RB */
  89         { NULL, NULL }, /* R */
  90         { man_B_pre, NULL }, /* B */
  91         { man_I_pre, NULL }, /* I */
  92         { man_alt_pre, NULL }, /* IR */
  93         { man_alt_pre, NULL }, /* RI */
  94         { NULL, NULL }, /* nf */
  95         { NULL, NULL }, /* fi */
  96         { NULL, NULL }, /* RE */
  97         { man_RS_pre, NULL }, /* RS */
  98         { man_ign_pre, NULL }, /* DT */
  99         { man_ign_pre, NULL }, /* UC */
 100         { man_ign_pre, NULL }, /* PD */
 101         { man_ign_pre, NULL }, /* AT */
 102         { man_in_pre, NULL }, /* in */
 103         { man_OP_pre, NULL }, /* OP */
 104         { NULL, NULL }, /* EX */
 105         { NULL, NULL }, /* EE */
 106         { man_UR_pre, NULL }, /* UR */
 107         { NULL, NULL }, /* UE */
 108         { man_UR_pre, NULL }, /* MT */
 109         { NULL, NULL }, /* ME */
 110 };
 111 static  const struct htmlman *const mans = __mans - MAN_TH;
 112 
 113 
 114 /*
 115  * Printing leading vertical space before a block.
 116  * This is used for the paragraph macros.
 117  * The rules are pretty simple, since there's very little nesting going
 118  * on here.  Basically, if we're the first within another block (SS/SH),
 119  * then don't emit vertical space.  If we are (RS), then do.  If not the
 120  * first, print it.
 121  */
 122 static void
 123 print_bvspace(struct html *h, const struct roff_node *n)
 124 {
 125 
 126         if (n->body && n->body->child)
 127                 if (n->body->child->type == ROFFT_TBL)
 128                         return;
 129 
 130         if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS)
 131                 if (NULL == n->prev)
 132                         return;
 133 
 134         print_paragraph(h);
 135 }
 136 
 137 void
 138 html_man(void *arg, const struct roff_man *man)
 139 {
 140         struct html             *h;
 141         struct roff_node        *n;
 142         struct tag              *t;
 143 
 144         h = (struct html *)arg;
 145         n = man->first->child;
 146 
 147         if ((h->oflags & HTML_FRAGMENT) == 0) {
 148                 print_gen_decls(h);
 149                 print_otag(h, TAG_HTML, "");
 150                 if (n->type == ROFFT_COMMENT)
 151                         print_gen_comment(h, n);
 152                 t = print_otag(h, TAG_HEAD, "");
 153                 print_man_head(&man->meta, h);
 154                 print_tagq(h, t);
 155                 print_otag(h, TAG_BODY, "");
 156         }
 157 
 158         man_root_pre(&man->meta, h);
 159         t = print_otag(h, TAG_DIV, "c", "manual-text");
 160         print_man_nodelist(&man->meta, n, h);
 161         print_tagq(h, t);
 162         man_root_post(&man->meta, h);
 163         print_tagq(h, NULL);
 164 }
 165 
 166 static void
 167 print_man_head(const struct roff_meta *man, struct html *h)
 168 {
 169         char    *cp;
 170 
 171         print_gen_head(h);
 172         mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec);
 173         print_otag(h, TAG_TITLE, "");
 174         print_text(h, cp);
 175         free(cp);
 176 }
 177 
 178 static void
 179 print_man_nodelist(MAN_ARGS)
 180 {
 181 
 182         while (n != NULL) {
 183                 print_man_node(man, n, h);
 184                 n = n->next;
 185         }
 186 }
 187 
 188 static void
 189 print_man_node(MAN_ARGS)
 190 {
 191         static int       want_fillmode = MAN_fi;
 192         static int       save_fillmode;
 193 
 194         struct tag      *t;
 195         int              child;
 196 
 197         /*
 198          * Handle fill mode switch requests up front,
 199          * they would just cause trouble in the subsequent code.
 200          */
 201 
 202         switch (n->tok) {
 203         case MAN_nf:
 204         case MAN_EX:
 205                 want_fillmode = MAN_nf;
 206                 return;
 207         case MAN_fi:
 208         case MAN_EE:
 209                 want_fillmode = MAN_fi;
 210                 if (fillmode(h, 0) == MAN_fi)
 211                         print_otag(h, TAG_BR, "");
 212                 return;
 213         default:
 214                 break;
 215         }
 216 
 217         /* Set up fill mode for the upcoming node. */
 218 
 219         switch (n->type) {
 220         case ROFFT_BLOCK:
 221                 save_fillmode = 0;
 222                 /* Some block macros suspend or cancel .nf. */
 223                 switch (n->tok) {
 224                 case MAN_TP:  /* Tagged paragraphs              */
 225                 case MAN_IP:  /* temporarily disable .nf        */
 226                 case MAN_HP:  /* for the head.                  */
 227                         save_fillmode = want_fillmode;
 228                         /* FALLTHROUGH */
 229                 case MAN_SH:  /* Section headers                */
 230                 case MAN_SS:  /* permanently cancel .nf.        */
 231                         want_fillmode = MAN_fi;
 232                         /* FALLTHROUGH */
 233                 case MAN_PP:  /* These have no head.            */
 234                 case MAN_LP:  /* They will simply               */
 235                 case MAN_P:   /* reopen .nf in the body.        */
 236                 case MAN_RS:
 237                 case MAN_UR:
 238                 case MAN_MT:
 239                         fillmode(h, MAN_fi);
 240                         break;
 241                 default:
 242                         break;
 243                 }
 244                 break;
 245         case ROFFT_TBL:
 246                 fillmode(h, MAN_fi);
 247                 break;
 248         case ROFFT_ELEM:
 249                 /*
 250                  * Some in-line macros produce tags and/or text
 251                  * in the handler, so they require fill mode to be
 252                  * configured up front just like for text nodes.
 253                  * For the others, keep the traditional approach
 254                  * of doing the same, for now.
 255                  */
 256                 fillmode(h, want_fillmode);
 257                 break;
 258         case ROFFT_TEXT:
 259                 if (fillmode(h, want_fillmode) == MAN_fi &&
 260                     want_fillmode == MAN_fi &&
 261                     n->flags & NODE_LINE && *n->string == ' ' &&
 262                     (h->flags & HTML_NONEWLINE) == 0)
 263                         print_otag(h, TAG_BR, "");
 264                 if (*n->string != '\0')
 265                         break;
 266                 print_paragraph(h);
 267                 return;
 268         case ROFFT_COMMENT:
 269                 return;
 270         default:
 271                 break;
 272         }
 273 
 274         /* Produce output for this node. */
 275 
 276         child = 1;
 277         switch (n->type) {
 278         case ROFFT_TEXT:
 279                 t = h->tag;
 280                 print_text(h, n->string);
 281                 break;
 282         case ROFFT_EQN:
 283                 t = h->tag;
 284                 print_eqn(h, n->eqn);
 285                 break;
 286         case ROFFT_TBL:
 287                 /*
 288                  * This will take care of initialising all of the table
 289                  * state data for the first table, then tearing it down
 290                  * for the last one.
 291                  */
 292                 print_tbl(h, n->span);
 293                 return;
 294         default:
 295                 /*
 296                  * Close out scope of font prior to opening a macro
 297                  * scope.
 298                  */
 299                 if (HTMLFONT_NONE != h->metac) {
 300                         h->metal = h->metac;
 301                         h->metac = HTMLFONT_NONE;
 302                 }
 303 
 304                 /*
 305                  * Close out the current table, if it's open, and unset
 306                  * the "meta" table state.  This will be reopened on the
 307                  * next table element.
 308                  */
 309                 if (h->tblt)
 310                         print_tblclose(h);
 311 
 312                 t = h->tag;
 313                 if (n->tok < ROFF_MAX) {
 314                         roff_html_pre(h, n);
 315                         child = 0;
 316                         break;
 317                 }
 318 
 319                 assert(n->tok >= MAN_TH && n->tok < MAN_MAX);
 320                 if (mans[n->tok].pre)
 321                         child = (*mans[n->tok].pre)(man, n, h);
 322 
 323                 /* Some block macros resume .nf in the body. */
 324                 if (save_fillmode && n->type == ROFFT_BODY)
 325                         want_fillmode = save_fillmode;
 326 
 327                 break;
 328         }
 329 
 330         if (child && n->child)
 331                 print_man_nodelist(man, n->child, h);
 332 
 333         /* This will automatically close out any font scope. */
 334         print_stagq(h, t);
 335 
 336         if (fillmode(h, 0) == MAN_nf &&
 337             n->next != NULL && n->next->flags & NODE_LINE)
 338                 print_endline(h);
 339 }
 340 
 341 /*
 342  * MAN_nf switches to no-fill mode, MAN_fi to fill mode.
 343  * Other arguments do not switch.
 344  * The old mode is returned.
 345  */
 346 static int
 347 fillmode(struct html *h, int want)
 348 {
 349         struct tag      *pre;
 350         int              had;
 351 
 352         for (pre = h->tag; pre != NULL; pre = pre->next)
 353                 if (pre->tag == TAG_PRE)
 354                         break;
 355 
 356         had = pre == NULL ? MAN_fi : MAN_nf;
 357 
 358         if (want && want != had) {
 359                 if (want == MAN_nf)
 360                         print_otag(h, TAG_PRE, "");
 361                 else
 362                         print_tagq(h, pre);
 363         }
 364         return had;
 365 }
 366 
 367 static void
 368 man_root_pre(const struct roff_meta *man, struct html *h)
 369 {
 370         struct tag      *t, *tt;
 371         char            *title;
 372 
 373         assert(man->title);
 374         assert(man->msec);
 375         mandoc_asprintf(&title, "%s(%s)", man->title, man->msec);
 376 
 377         t = print_otag(h, TAG_TABLE, "c", "head");
 378         tt = print_otag(h, TAG_TR, "");
 379 
 380         print_otag(h, TAG_TD, "c", "head-ltitle");
 381         print_text(h, title);
 382         print_stagq(h, tt);
 383 
 384         print_otag(h, TAG_TD, "c", "head-vol");
 385         if (NULL != man->vol)
 386                 print_text(h, man->vol);
 387         print_stagq(h, tt);
 388 
 389         print_otag(h, TAG_TD, "c", "head-rtitle");
 390         print_text(h, title);
 391         print_tagq(h, t);
 392         free(title);
 393 }
 394 
 395 static void
 396 man_root_post(const struct roff_meta *man, struct html *h)
 397 {
 398         struct tag      *t, *tt;
 399 
 400         t = print_otag(h, TAG_TABLE, "c", "foot");
 401         tt = print_otag(h, TAG_TR, "");
 402 
 403         print_otag(h, TAG_TD, "c", "foot-date");
 404         print_text(h, man->date);
 405         print_stagq(h, tt);
 406 
 407         print_otag(h, TAG_TD, "c", "foot-os");
 408         if (man->os)
 409                 print_text(h, man->os);
 410         print_tagq(h, t);
 411 }
 412 
 413 static int
 414 man_SH_pre(MAN_ARGS)
 415 {
 416         char    *id;
 417 
 418         if (n->type == ROFFT_HEAD) {
 419                 id = html_make_id(n, 1);
 420                 print_otag(h, TAG_H1, "cTi", "Sh", id);
 421                 if (id != NULL)
 422                         print_otag(h, TAG_A, "chR", "permalink", id);
 423         }
 424         return 1;
 425 }
 426 
 427 static int
 428 man_alt_pre(MAN_ARGS)
 429 {
 430         const struct roff_node  *nn;
 431         int              i;
 432         enum htmltag     fp;
 433         struct tag      *t;
 434 
 435         for (i = 0, nn = n->child; nn; nn = nn->next, i++) {
 436                 switch (n->tok) {
 437                 case MAN_BI:
 438                         fp = i % 2 ? TAG_I : TAG_B;
 439                         break;
 440                 case MAN_IB:
 441                         fp = i % 2 ? TAG_B : TAG_I;
 442                         break;
 443                 case MAN_RI:
 444                         fp = i % 2 ? TAG_I : TAG_MAX;
 445                         break;
 446                 case MAN_IR:
 447                         fp = i % 2 ? TAG_MAX : TAG_I;
 448                         break;
 449                 case MAN_BR:
 450                         fp = i % 2 ? TAG_MAX : TAG_B;
 451                         break;
 452                 case MAN_RB:
 453                         fp = i % 2 ? TAG_B : TAG_MAX;
 454                         break;
 455                 default:
 456                         abort();
 457                 }
 458 
 459                 if (i)
 460                         h->flags |= HTML_NOSPACE;
 461 
 462                 if (fp != TAG_MAX)
 463                         t = print_otag(h, fp, "");
 464 
 465                 print_text(h, nn->string);
 466 
 467                 if (fp != TAG_MAX)
 468                         print_tagq(h, t);
 469         }
 470         return 0;
 471 }
 472 
 473 static int
 474 man_SM_pre(MAN_ARGS)
 475 {
 476         print_otag(h, TAG_SMALL, "");
 477         if (MAN_SB == n->tok)
 478                 print_otag(h, TAG_B, "");
 479         return 1;
 480 }
 481 
 482 static int
 483 man_SS_pre(MAN_ARGS)
 484 {
 485         char    *id;
 486 
 487         if (n->type == ROFFT_HEAD) {
 488                 id = html_make_id(n, 1);
 489                 print_otag(h, TAG_H2, "cTi", "Ss", id);
 490                 if (id != NULL)
 491                         print_otag(h, TAG_A, "chR", "permalink", id);
 492         }
 493         return 1;
 494 }
 495 
 496 static int
 497 man_PP_pre(MAN_ARGS)
 498 {
 499 
 500         if (n->type == ROFFT_HEAD)
 501                 return 0;
 502         else if (n->type == ROFFT_BLOCK)
 503                 print_bvspace(h, n);
 504 
 505         return 1;
 506 }
 507 
 508 static int
 509 man_IP_pre(MAN_ARGS)
 510 {
 511         const struct roff_node  *nn;
 512 
 513         if (n->type == ROFFT_BODY) {
 514                 print_otag(h, TAG_DD, "");
 515                 return 1;
 516         } else if (n->type != ROFFT_HEAD) {
 517                 print_otag(h, TAG_DL, "c", "Bl-tag");
 518                 return 1;
 519         }
 520 
 521         /* FIXME: width specification. */
 522 
 523         print_otag(h, TAG_DT, "");
 524 
 525         /* For IP, only print the first header element. */
 526 
 527         if (MAN_IP == n->tok && n->child)
 528                 print_man_node(man, n->child, h);
 529 
 530         /* For TP, only print next-line header elements. */
 531 
 532         if (MAN_TP == n->tok) {
 533                 nn = n->child;
 534                 while (NULL != nn && 0 == (NODE_LINE & nn->flags))
 535                         nn = nn->next;
 536                 while (NULL != nn) {
 537                         print_man_node(man, nn, h);
 538                         nn = nn->next;
 539                 }
 540         }
 541 
 542         return 0;
 543 }
 544 
 545 static int
 546 man_HP_pre(MAN_ARGS)
 547 {
 548         if (n->type == ROFFT_HEAD)
 549                 return 0;
 550 
 551         if (n->type == ROFFT_BLOCK) {
 552                 print_bvspace(h, n);
 553                 print_otag(h, TAG_DIV, "c", "HP");
 554         }
 555         return 1;
 556 }
 557 
 558 static int
 559 man_OP_pre(MAN_ARGS)
 560 {
 561         struct tag      *tt;
 562 
 563         print_text(h, "[");
 564         h->flags |= HTML_NOSPACE;
 565         tt = print_otag(h, TAG_SPAN, "c", "Op");
 566 
 567         if (NULL != (n = n->child)) {
 568                 print_otag(h, TAG_B, "");
 569                 print_text(h, n->string);
 570         }
 571 
 572         print_stagq(h, tt);
 573 
 574         if (NULL != n && NULL != n->next) {
 575                 print_otag(h, TAG_I, "");
 576                 print_text(h, n->next->string);
 577         }
 578 
 579         print_stagq(h, tt);
 580         h->flags |= HTML_NOSPACE;
 581         print_text(h, "]");
 582         return 0;
 583 }
 584 
 585 static int
 586 man_B_pre(MAN_ARGS)
 587 {
 588         print_otag(h, TAG_B, "");
 589         return 1;
 590 }
 591 
 592 static int
 593 man_I_pre(MAN_ARGS)
 594 {
 595         print_otag(h, TAG_I, "");
 596         return 1;
 597 }
 598 
 599 static int
 600 man_in_pre(MAN_ARGS)
 601 {
 602         print_otag(h, TAG_BR, "");
 603         return 0;
 604 }
 605 
 606 static int
 607 man_ign_pre(MAN_ARGS)
 608 {
 609 
 610         return 0;
 611 }
 612 
 613 static int
 614 man_RS_pre(MAN_ARGS)
 615 {
 616         if (n->type == ROFFT_HEAD)
 617                 return 0;
 618         if (n->type == ROFFT_BLOCK)
 619                 print_otag(h, TAG_DIV, "c", "Bd-indent");
 620         return 1;
 621 }
 622 
 623 static int
 624 man_UR_pre(MAN_ARGS)
 625 {
 626         char *cp;
 627         n = n->child;
 628         assert(n->type == ROFFT_HEAD);
 629         if (n->child != NULL) {
 630                 assert(n->child->type == ROFFT_TEXT);
 631                 if (n->tok == MAN_MT) {
 632                         mandoc_asprintf(&cp, "mailto:%s", n->child->string);
 633                         print_otag(h, TAG_A, "cTh", "Mt", cp);
 634                         free(cp);
 635                 } else
 636                         print_otag(h, TAG_A, "cTh", "Lk", n->child->string);
 637         }
 638 
 639         assert(n->next->type == ROFFT_BODY);
 640         if (n->next->child != NULL)
 641                 n = n->next;
 642 
 643         print_man_nodelist(man, n->child, h);
 644 
 645         return 0;
 646 }