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