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