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