1 /* $OpenBSD: man_html.c,v 1.110 2018/10/23 17:17:54 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 (want_fillmode == MAN_nf || *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 /* In .nf = <pre>, print even empty lines. */ 341 h->col++; 342 print_endline(h); 343 } 344 } 345 346 /* 347 * MAN_nf switches to no-fill mode, MAN_fi to fill mode. 348 * Other arguments do not switch. 349 * The old mode is returned. 350 */ 351 static int 352 fillmode(struct html *h, int want) 353 { 354 struct tag *pre; 355 int had; 356 357 for (pre = h->tag; pre != NULL; pre = pre->next) 358 if (pre->tag == TAG_PRE) 359 break; 360 361 had = pre == NULL ? MAN_fi : MAN_nf; 362 363 if (want && want != had) { 364 if (want == MAN_nf) 365 print_otag(h, TAG_PRE, ""); 366 else 367 print_tagq(h, pre); 368 } 369 return had; 370 } 371 372 static void 373 man_root_pre(const struct roff_meta *man, struct html *h) 374 { 375 struct tag *t, *tt; 376 char *title; 377 378 assert(man->title); 379 assert(man->msec); 380 mandoc_asprintf(&title, "%s(%s)", man->title, man->msec); 381 382 t = print_otag(h, TAG_TABLE, "c", "head"); 383 tt = print_otag(h, TAG_TR, ""); 384 385 print_otag(h, TAG_TD, "c", "head-ltitle"); 386 print_text(h, title); 387 print_stagq(h, tt); 388 389 print_otag(h, TAG_TD, "c", "head-vol"); 390 if (NULL != man->vol) 391 print_text(h, man->vol); 392 print_stagq(h, tt); 393 394 print_otag(h, TAG_TD, "c", "head-rtitle"); 395 print_text(h, title); 396 print_tagq(h, t); 397 free(title); 398 } 399 400 static void 401 man_root_post(const struct roff_meta *man, struct html *h) 402 { 403 struct tag *t, *tt; 404 405 t = print_otag(h, TAG_TABLE, "c", "foot"); 406 tt = print_otag(h, TAG_TR, ""); 407 408 print_otag(h, TAG_TD, "c", "foot-date"); 409 print_text(h, man->date); 410 print_stagq(h, tt); 411 412 print_otag(h, TAG_TD, "c", "foot-os"); 413 if (man->os) 414 print_text(h, man->os); 415 print_tagq(h, t); 416 } 417 418 static int 419 man_SH_pre(MAN_ARGS) 420 { 421 char *id; 422 423 if (n->type == ROFFT_HEAD) { 424 id = html_make_id(n, 1); 425 print_otag(h, TAG_H1, "cTi", "Sh", id); 426 if (id != NULL) 427 print_otag(h, TAG_A, "chR", "permalink", id); 428 } 429 return 1; 430 } 431 432 static int 433 man_alt_pre(MAN_ARGS) 434 { 435 const struct roff_node *nn; 436 int i; 437 enum htmltag fp; 438 struct tag *t; 439 440 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 441 switch (n->tok) { 442 case MAN_BI: 443 fp = i % 2 ? TAG_I : TAG_B; 444 break; 445 case MAN_IB: 446 fp = i % 2 ? TAG_B : TAG_I; 447 break; 448 case MAN_RI: 449 fp = i % 2 ? TAG_I : TAG_MAX; 450 break; 451 case MAN_IR: 452 fp = i % 2 ? TAG_MAX : TAG_I; 453 break; 454 case MAN_BR: 455 fp = i % 2 ? TAG_MAX : TAG_B; 456 break; 457 case MAN_RB: 458 fp = i % 2 ? TAG_B : TAG_MAX; 459 break; 460 default: 461 abort(); 462 } 463 464 if (i) 465 h->flags |= HTML_NOSPACE; 466 467 if (fp != TAG_MAX) 468 t = print_otag(h, fp, ""); 469 470 print_text(h, nn->string); 471 472 if (fp != TAG_MAX) 473 print_tagq(h, t); 474 } 475 return 0; 476 } 477 478 static int 479 man_SM_pre(MAN_ARGS) 480 { 481 print_otag(h, TAG_SMALL, ""); 482 if (MAN_SB == n->tok) 483 print_otag(h, TAG_B, ""); 484 return 1; 485 } 486 487 static int 488 man_SS_pre(MAN_ARGS) 489 { 490 char *id; 491 492 if (n->type == ROFFT_HEAD) { 493 id = html_make_id(n, 1); 494 print_otag(h, TAG_H2, "cTi", "Ss", id); 495 if (id != NULL) 496 print_otag(h, TAG_A, "chR", "permalink", 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, ""); 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 print_otag(h, TAG_DT, ""); 527 528 switch(n->tok) { 529 case MAN_IP: /* Only print the first header element. */ 530 if (n->child != NULL) 531 print_man_node(man, n->child, h); 532 break; 533 case MAN_TP: /* Only print next-line header elements. */ 534 case MAN_TQ: 535 nn = n->child; 536 while (nn != NULL && (NODE_LINE & nn->flags) == 0) 537 nn = nn->next; 538 while (nn != NULL) { 539 print_man_node(man, nn, h); 540 nn = nn->next; 541 } 542 break; 543 default: 544 abort(); 545 } 546 547 return 0; 548 } 549 550 static int 551 man_HP_pre(MAN_ARGS) 552 { 553 if (n->type == ROFFT_HEAD) 554 return 0; 555 556 if (n->type == ROFFT_BLOCK) { 557 print_bvspace(h, n); 558 print_otag(h, TAG_DIV, "c", "HP"); 559 } 560 return 1; 561 } 562 563 static int 564 man_OP_pre(MAN_ARGS) 565 { 566 struct tag *tt; 567 568 print_text(h, "["); 569 h->flags |= HTML_NOSPACE; 570 tt = print_otag(h, TAG_SPAN, "c", "Op"); 571 572 if (NULL != (n = n->child)) { 573 print_otag(h, TAG_B, ""); 574 print_text(h, n->string); 575 } 576 577 print_stagq(h, tt); 578 579 if (NULL != n && NULL != n->next) { 580 print_otag(h, TAG_I, ""); 581 print_text(h, n->next->string); 582 } 583 584 print_stagq(h, tt); 585 h->flags |= HTML_NOSPACE; 586 print_text(h, "]"); 587 return 0; 588 } 589 590 static int 591 man_B_pre(MAN_ARGS) 592 { 593 print_otag(h, TAG_B, ""); 594 return 1; 595 } 596 597 static int 598 man_I_pre(MAN_ARGS) 599 { 600 print_otag(h, TAG_I, ""); 601 return 1; 602 } 603 604 static int 605 man_in_pre(MAN_ARGS) 606 { 607 print_otag(h, TAG_BR, ""); 608 return 0; 609 } 610 611 static int 612 man_ign_pre(MAN_ARGS) 613 { 614 615 return 0; 616 } 617 618 static int 619 man_RS_pre(MAN_ARGS) 620 { 621 if (n->type == ROFFT_HEAD) 622 return 0; 623 if (n->type == ROFFT_BLOCK) 624 print_otag(h, TAG_DIV, "c", "Bd-indent"); 625 return 1; 626 } 627 628 static int 629 man_SY_pre(MAN_ARGS) 630 { 631 switch (n->type) { 632 case ROFFT_BLOCK: 633 print_otag(h, TAG_TABLE, "c", "Nm"); 634 print_otag(h, TAG_TR, ""); 635 break; 636 case ROFFT_HEAD: 637 print_otag(h, TAG_TD, ""); 638 print_otag(h, TAG_CODE, "cT", "Nm"); 639 break; 640 case ROFFT_BODY: 641 print_otag(h, TAG_TD, ""); 642 break; 643 default: 644 abort(); 645 } 646 return 1; 647 } 648 649 static int 650 man_UR_pre(MAN_ARGS) 651 { 652 char *cp; 653 n = n->child; 654 assert(n->type == ROFFT_HEAD); 655 if (n->child != NULL) { 656 assert(n->child->type == ROFFT_TEXT); 657 if (n->tok == MAN_MT) { 658 mandoc_asprintf(&cp, "mailto:%s", n->child->string); 659 print_otag(h, TAG_A, "cTh", "Mt", cp); 660 free(cp); 661 } else 662 print_otag(h, TAG_A, "cTh", "Lk", n->child->string); 663 } 664 665 assert(n->next->type == ROFFT_BODY); 666 if (n->next->child != NULL) 667 n = n->next; 668 669 print_man_nodelist(man, n->child, h); 670 671 return 0; 672 } 673