1 /* $OpenBSD: man_html.c,v 1.86 2017/02/05 18:13:28 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 if (n->type == ROFFT_HEAD) 437 print_otag(h, TAG_H1, "c", "Sh"); 438 return 1; 439 } 440 441 static int 442 man_alt_pre(MAN_ARGS) 443 { 444 const struct roff_node *nn; 445 int i; 446 enum htmltag fp; 447 struct tag *t; 448 449 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 450 switch (n->tok) { 451 case MAN_BI: 452 fp = i % 2 ? TAG_I : TAG_B; 453 break; 454 case MAN_IB: 455 fp = i % 2 ? TAG_B : TAG_I; 456 break; 457 case MAN_RI: 458 fp = i % 2 ? TAG_I : TAG_MAX; 459 break; 460 case MAN_IR: 461 fp = i % 2 ? TAG_MAX : TAG_I; 462 break; 463 case MAN_BR: 464 fp = i % 2 ? TAG_MAX : TAG_B; 465 break; 466 case MAN_RB: 467 fp = i % 2 ? TAG_B : TAG_MAX; 468 break; 469 default: 470 abort(); 471 } 472 473 if (i) 474 h->flags |= HTML_NOSPACE; 475 476 if (fp != TAG_MAX) 477 t = print_otag(h, fp, ""); 478 479 print_text(h, nn->string); 480 481 if (fp != TAG_MAX) 482 print_tagq(h, t); 483 } 484 return 0; 485 } 486 487 static int 488 man_SM_pre(MAN_ARGS) 489 { 490 print_otag(h, TAG_SMALL, ""); 491 if (MAN_SB == n->tok) 492 print_otag(h, TAG_B, ""); 493 return 1; 494 } 495 496 static int 497 man_SS_pre(MAN_ARGS) 498 { 499 if (n->type == ROFFT_HEAD) 500 print_otag(h, TAG_H2, "c", "Ss"); 501 return 1; 502 } 503 504 static int 505 man_PP_pre(MAN_ARGS) 506 { 507 508 if (n->type == ROFFT_HEAD) 509 return 0; 510 else if (n->type == ROFFT_BLOCK) 511 print_bvspace(h, n); 512 513 return 1; 514 } 515 516 static int 517 man_IP_pre(MAN_ARGS) 518 { 519 const struct roff_node *nn; 520 521 if (n->type == ROFFT_BODY) { 522 print_otag(h, TAG_DD, "c", "It-tag"); 523 return 1; 524 } else if (n->type != ROFFT_HEAD) { 525 print_otag(h, TAG_DL, "c", "Bl-tag"); 526 return 1; 527 } 528 529 /* FIXME: width specification. */ 530 531 print_otag(h, TAG_DT, "c", "It-tag"); 532 533 /* For IP, only print the first header element. */ 534 535 if (MAN_IP == n->tok && n->child) 536 print_man_node(man, n->child, h); 537 538 /* For TP, only print next-line header elements. */ 539 540 if (MAN_TP == n->tok) { 541 nn = n->child; 542 while (NULL != nn && 0 == (NODE_LINE & nn->flags)) 543 nn = nn->next; 544 while (NULL != nn) { 545 print_man_node(man, nn, h); 546 nn = nn->next; 547 } 548 } 549 550 return 0; 551 } 552 553 static int 554 man_HP_pre(MAN_ARGS) 555 { 556 struct roffsu sum, sui; 557 const struct roff_node *np; 558 559 if (n->type == ROFFT_HEAD) 560 return 0; 561 else if (n->type != ROFFT_BLOCK) 562 return 1; 563 564 np = n->head->child; 565 566 if (np == NULL || !a2width(np, &sum)) 567 SCALE_HS_INIT(&sum, INDENT); 568 569 sui.unit = sum.unit; 570 sui.scale = -sum.scale; 571 572 print_bvspace(h, n); 573 print_otag(h, TAG_DIV, "csului", "Pp", &sum, &sui); 574 return 1; 575 } 576 577 static int 578 man_OP_pre(MAN_ARGS) 579 { 580 struct tag *tt; 581 582 print_text(h, "["); 583 h->flags |= HTML_NOSPACE; 584 tt = print_otag(h, TAG_SPAN, "c", "Op"); 585 586 if (NULL != (n = n->child)) { 587 print_otag(h, TAG_B, ""); 588 print_text(h, n->string); 589 } 590 591 print_stagq(h, tt); 592 593 if (NULL != n && NULL != n->next) { 594 print_otag(h, TAG_I, ""); 595 print_text(h, n->next->string); 596 } 597 598 print_stagq(h, tt); 599 h->flags |= HTML_NOSPACE; 600 print_text(h, "]"); 601 return 0; 602 } 603 604 static int 605 man_B_pre(MAN_ARGS) 606 { 607 print_otag(h, TAG_B, ""); 608 return 1; 609 } 610 611 static int 612 man_I_pre(MAN_ARGS) 613 { 614 print_otag(h, TAG_I, ""); 615 return 1; 616 } 617 618 static int 619 man_in_pre(MAN_ARGS) 620 { 621 print_otag(h, TAG_BR, ""); 622 return 0; 623 } 624 625 static int 626 man_ign_pre(MAN_ARGS) 627 { 628 629 return 0; 630 } 631 632 static int 633 man_RS_pre(MAN_ARGS) 634 { 635 struct roffsu su; 636 637 if (n->type == ROFFT_HEAD) 638 return 0; 639 else if (n->type == ROFFT_BODY) 640 return 1; 641 642 SCALE_HS_INIT(&su, INDENT); 643 if (n->head->child) 644 a2width(n->head->child, &su); 645 646 print_otag(h, TAG_DIV, "sul", &su); 647 return 1; 648 } 649 650 static int 651 man_UR_pre(MAN_ARGS) 652 { 653 n = n->child; 654 assert(n->type == ROFFT_HEAD); 655 if (n->child != NULL) { 656 assert(n->child->type == ROFFT_TEXT); 657 print_otag(h, TAG_A, "ch", "Lk", n->child->string); 658 } 659 660 assert(n->next->type == ROFFT_BODY); 661 if (n->next->child != NULL) 662 n = n->next; 663 664 print_man_nodelist(man, n->child, h); 665 666 return 0; 667 } 668