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