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