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