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