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