1 /* $Id: man_html.c,v 1.90 2013/10/17 20:54:58 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2013 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 AUTHOR DISCLAIMS ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR 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 #ifdef HAVE_CONFIG_H 19 #include "config.h" 20 #endif 21 22 #include <sys/types.h> 23 24 #include <assert.h> 25 #include <ctype.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 30 #include "mandoc.h" 31 #include "out.h" 32 #include "html.h" 33 #include "man.h" 34 #include "main.h" 35 36 /* TODO: preserve ident widths. */ 37 /* FIXME: have PD set the default vspace width. */ 38 39 #define INDENT 5 40 41 #define MAN_ARGS const struct man_meta *man, \ 42 const struct man_node *n, \ 43 struct mhtml *mh, \ 44 struct html *h 45 46 struct mhtml { 47 int fl; 48 #define MANH_LITERAL (1 << 0) /* literal context */ 49 }; 50 51 struct htmlman { 52 int (*pre)(MAN_ARGS); 53 int (*post)(MAN_ARGS); 54 }; 55 56 static void print_bvspace(struct html *, 57 const struct man_node *); 58 static void print_man(MAN_ARGS); 59 static void print_man_head(MAN_ARGS); 60 static void print_man_nodelist(MAN_ARGS); 61 static void print_man_node(MAN_ARGS); 62 static int a2width(const struct man_node *, 63 struct roffsu *); 64 static int man_B_pre(MAN_ARGS); 65 static int man_HP_pre(MAN_ARGS); 66 static int man_IP_pre(MAN_ARGS); 67 static int man_I_pre(MAN_ARGS); 68 static int man_OP_pre(MAN_ARGS); 69 static int man_PP_pre(MAN_ARGS); 70 static int man_RS_pre(MAN_ARGS); 71 static int man_SH_pre(MAN_ARGS); 72 static int man_SM_pre(MAN_ARGS); 73 static int man_SS_pre(MAN_ARGS); 74 static int man_UR_pre(MAN_ARGS); 75 static int man_alt_pre(MAN_ARGS); 76 static int man_br_pre(MAN_ARGS); 77 static int man_ign_pre(MAN_ARGS); 78 static int man_in_pre(MAN_ARGS); 79 static int man_literal_pre(MAN_ARGS); 80 static void man_root_post(MAN_ARGS); 81 static void man_root_pre(MAN_ARGS); 82 83 static const struct htmlman mans[MAN_MAX] = { 84 { man_br_pre, NULL }, /* br */ 85 { NULL, NULL }, /* TH */ 86 { man_SH_pre, NULL }, /* SH */ 87 { man_SS_pre, NULL }, /* SS */ 88 { man_IP_pre, NULL }, /* TP */ 89 { man_PP_pre, NULL }, /* LP */ 90 { man_PP_pre, NULL }, /* PP */ 91 { man_PP_pre, NULL }, /* P */ 92 { man_IP_pre, NULL }, /* IP */ 93 { man_HP_pre, NULL }, /* HP */ 94 { man_SM_pre, NULL }, /* SM */ 95 { man_SM_pre, NULL }, /* SB */ 96 { man_alt_pre, NULL }, /* BI */ 97 { man_alt_pre, NULL }, /* IB */ 98 { man_alt_pre, NULL }, /* BR */ 99 { man_alt_pre, NULL }, /* RB */ 100 { NULL, NULL }, /* R */ 101 { man_B_pre, NULL }, /* B */ 102 { man_I_pre, NULL }, /* I */ 103 { man_alt_pre, NULL }, /* IR */ 104 { man_alt_pre, NULL }, /* RI */ 105 { man_ign_pre, NULL }, /* na */ 106 { man_br_pre, NULL }, /* sp */ 107 { man_literal_pre, NULL }, /* nf */ 108 { man_literal_pre, NULL }, /* fi */ 109 { NULL, NULL }, /* RE */ 110 { man_RS_pre, NULL }, /* RS */ 111 { man_ign_pre, NULL }, /* DT */ 112 { man_ign_pre, NULL }, /* UC */ 113 { man_ign_pre, NULL }, /* PD */ 114 { man_ign_pre, NULL }, /* AT */ 115 { man_in_pre, NULL }, /* in */ 116 { man_ign_pre, NULL }, /* ft */ 117 { man_OP_pre, NULL }, /* OP */ 118 { man_literal_pre, NULL }, /* EX */ 119 { man_literal_pre, NULL }, /* EE */ 120 { man_UR_pre, NULL }, /* UR */ 121 { NULL, NULL }, /* UE */ 122 }; 123 124 /* 125 * Printing leading vertical space before a block. 126 * This is used for the paragraph macros. 127 * The rules are pretty simple, since there's very little nesting going 128 * on here. Basically, if we're the first within another block (SS/SH), 129 * then don't emit vertical space. If we are (RS), then do. If not the 130 * first, print it. 131 */ 132 static void 133 print_bvspace(struct html *h, const struct man_node *n) 134 { 135 136 if (n->body && n->body->child) 137 if (MAN_TBL == n->body->child->type) 138 return; 139 140 if (MAN_ROOT == n->parent->type || MAN_RS != n->parent->tok) 141 if (NULL == n->prev) 142 return; 143 144 print_otag(h, TAG_P, 0, NULL); 145 } 146 147 void 148 html_man(void *arg, const struct man *man) 149 { 150 struct mhtml mh; 151 152 memset(&mh, 0, sizeof(struct mhtml)); 153 print_man(man_meta(man), man_node(man), &mh, (struct html *)arg); 154 putchar('\n'); 155 } 156 157 static void 158 print_man(MAN_ARGS) 159 { 160 struct tag *t, *tt; 161 struct htmlpair tag; 162 163 PAIR_CLASS_INIT(&tag, "mandoc"); 164 165 if ( ! (HTML_FRAGMENT & h->oflags)) { 166 print_gen_decls(h); 167 t = print_otag(h, TAG_HTML, 0, NULL); 168 tt = print_otag(h, TAG_HEAD, 0, NULL); 169 print_man_head(man, n, mh, h); 170 print_tagq(h, tt); 171 print_otag(h, TAG_BODY, 0, NULL); 172 print_otag(h, TAG_DIV, 1, &tag); 173 } else 174 t = print_otag(h, TAG_DIV, 1, &tag); 175 176 print_man_nodelist(man, n, mh, h); 177 print_tagq(h, t); 178 } 179 180 181 /* ARGSUSED */ 182 static void 183 print_man_head(MAN_ARGS) 184 { 185 186 print_gen_head(h); 187 assert(man->title); 188 assert(man->msec); 189 bufcat_fmt(h, "%s(%s)", man->title, man->msec); 190 print_otag(h, TAG_TITLE, 0, NULL); 191 print_text(h, h->buf); 192 } 193 194 195 static void 196 print_man_nodelist(MAN_ARGS) 197 { 198 199 print_man_node(man, n, mh, h); 200 if (n->next) 201 print_man_nodelist(man, n->next, mh, h); 202 } 203 204 205 static void 206 print_man_node(MAN_ARGS) 207 { 208 int child; 209 struct tag *t; 210 211 child = 1; 212 t = h->tags.head; 213 214 switch (n->type) { 215 case (MAN_ROOT): 216 man_root_pre(man, n, mh, h); 217 break; 218 case (MAN_TEXT): 219 /* 220 * If we have a blank line, output a vertical space. 221 * If we have a space as the first character, break 222 * before printing the line's data. 223 */ 224 if ('\0' == *n->string) { 225 print_otag(h, TAG_P, 0, NULL); 226 return; 227 } 228 229 if (' ' == *n->string && MAN_LINE & n->flags) 230 print_otag(h, TAG_BR, 0, NULL); 231 else if (MANH_LITERAL & mh->fl && n->prev) 232 print_otag(h, TAG_BR, 0, NULL); 233 234 print_text(h, n->string); 235 return; 236 case (MAN_EQN): 237 print_eqn(h, n->eqn); 238 break; 239 case (MAN_TBL): 240 /* 241 * This will take care of initialising all of the table 242 * state data for the first table, then tearing it down 243 * for the last one. 244 */ 245 print_tbl(h, n->span); 246 return; 247 default: 248 /* 249 * Close out scope of font prior to opening a macro 250 * scope. 251 */ 252 if (HTMLFONT_NONE != h->metac) { 253 h->metal = h->metac; 254 h->metac = HTMLFONT_NONE; 255 } 256 257 /* 258 * Close out the current table, if it's open, and unset 259 * the "meta" table state. This will be reopened on the 260 * next table element. 261 */ 262 if (h->tblt) { 263 print_tblclose(h); 264 t = h->tags.head; 265 } 266 if (mans[n->tok].pre) 267 child = (*mans[n->tok].pre)(man, n, mh, h); 268 break; 269 } 270 271 if (child && n->child) 272 print_man_nodelist(man, n->child, mh, h); 273 274 /* This will automatically close out any font scope. */ 275 print_stagq(h, t); 276 277 switch (n->type) { 278 case (MAN_ROOT): 279 man_root_post(man, n, mh, h); 280 break; 281 case (MAN_EQN): 282 break; 283 default: 284 if (mans[n->tok].post) 285 (*mans[n->tok].post)(man, n, mh, h); 286 break; 287 } 288 } 289 290 291 static int 292 a2width(const struct man_node *n, struct roffsu *su) 293 { 294 295 if (MAN_TEXT != n->type) 296 return(0); 297 if (a2roffsu(n->string, su, SCALE_BU)) 298 return(1); 299 300 return(0); 301 } 302 303 304 /* ARGSUSED */ 305 static void 306 man_root_pre(MAN_ARGS) 307 { 308 struct htmlpair tag[3]; 309 struct tag *t, *tt; 310 char b[BUFSIZ], title[BUFSIZ]; 311 312 b[0] = 0; 313 if (man->vol) 314 (void)strlcat(b, man->vol, BUFSIZ); 315 316 assert(man->title); 317 assert(man->msec); 318 snprintf(title, BUFSIZ - 1, "%s(%s)", man->title, man->msec); 319 320 PAIR_SUMMARY_INIT(&tag[0], "Document Header"); 321 PAIR_CLASS_INIT(&tag[1], "head"); 322 PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); 323 t = print_otag(h, TAG_TABLE, 3, tag); 324 PAIR_INIT(&tag[0], ATTR_WIDTH, "30%"); 325 print_otag(h, TAG_COL, 1, tag); 326 print_otag(h, TAG_COL, 1, tag); 327 print_otag(h, TAG_COL, 1, tag); 328 329 print_otag(h, TAG_TBODY, 0, NULL); 330 331 tt = print_otag(h, TAG_TR, 0, NULL); 332 333 PAIR_CLASS_INIT(&tag[0], "head-ltitle"); 334 print_otag(h, TAG_TD, 1, tag); 335 print_text(h, title); 336 print_stagq(h, tt); 337 338 PAIR_CLASS_INIT(&tag[0], "head-vol"); 339 PAIR_INIT(&tag[1], ATTR_ALIGN, "center"); 340 print_otag(h, TAG_TD, 2, tag); 341 print_text(h, b); 342 print_stagq(h, tt); 343 344 PAIR_CLASS_INIT(&tag[0], "head-rtitle"); 345 PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); 346 print_otag(h, TAG_TD, 2, tag); 347 print_text(h, title); 348 print_tagq(h, t); 349 } 350 351 352 /* ARGSUSED */ 353 static void 354 man_root_post(MAN_ARGS) 355 { 356 struct htmlpair tag[3]; 357 struct tag *t, *tt; 358 359 PAIR_SUMMARY_INIT(&tag[0], "Document Footer"); 360 PAIR_CLASS_INIT(&tag[1], "foot"); 361 PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); 362 t = print_otag(h, TAG_TABLE, 3, tag); 363 PAIR_INIT(&tag[0], ATTR_WIDTH, "50%"); 364 print_otag(h, TAG_COL, 1, tag); 365 print_otag(h, TAG_COL, 1, tag); 366 367 tt = print_otag(h, TAG_TR, 0, NULL); 368 369 PAIR_CLASS_INIT(&tag[0], "foot-date"); 370 print_otag(h, TAG_TD, 1, tag); 371 372 assert(man->date); 373 print_text(h, man->date); 374 print_stagq(h, tt); 375 376 PAIR_CLASS_INIT(&tag[0], "foot-os"); 377 PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); 378 print_otag(h, TAG_TD, 2, tag); 379 380 if (man->source) 381 print_text(h, man->source); 382 print_tagq(h, t); 383 } 384 385 386 /* ARGSUSED */ 387 static int 388 man_br_pre(MAN_ARGS) 389 { 390 struct roffsu su; 391 struct htmlpair tag; 392 393 SCALE_VS_INIT(&su, 1); 394 395 if (MAN_sp == n->tok) { 396 if (NULL != (n = n->child)) 397 if ( ! a2roffsu(n->string, &su, SCALE_VS)) 398 SCALE_VS_INIT(&su, atoi(n->string)); 399 } else 400 su.scale = 0; 401 402 bufinit(h); 403 bufcat_su(h, "height", &su); 404 PAIR_STYLE_INIT(&tag, h); 405 print_otag(h, TAG_DIV, 1, &tag); 406 407 /* So the div isn't empty: */ 408 print_text(h, "\\~"); 409 410 return(0); 411 } 412 413 /* ARGSUSED */ 414 static int 415 man_SH_pre(MAN_ARGS) 416 { 417 struct htmlpair tag; 418 419 if (MAN_BLOCK == n->type) { 420 mh->fl &= ~MANH_LITERAL; 421 PAIR_CLASS_INIT(&tag, "section"); 422 print_otag(h, TAG_DIV, 1, &tag); 423 return(1); 424 } else if (MAN_BODY == n->type) 425 return(1); 426 427 print_otag(h, TAG_H1, 0, NULL); 428 return(1); 429 } 430 431 /* ARGSUSED */ 432 static int 433 man_alt_pre(MAN_ARGS) 434 { 435 const struct man_node *nn; 436 int i, savelit; 437 enum htmltag fp; 438 struct tag *t; 439 440 if ((savelit = mh->fl & MANH_LITERAL)) 441 print_otag(h, TAG_BR, 0, NULL); 442 443 mh->fl &= ~MANH_LITERAL; 444 445 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 446 t = NULL; 447 switch (n->tok) { 448 case (MAN_BI): 449 fp = i % 2 ? TAG_I : TAG_B; 450 break; 451 case (MAN_IB): 452 fp = i % 2 ? TAG_B : TAG_I; 453 break; 454 case (MAN_RI): 455 fp = i % 2 ? TAG_I : TAG_MAX; 456 break; 457 case (MAN_IR): 458 fp = i % 2 ? TAG_MAX : TAG_I; 459 break; 460 case (MAN_BR): 461 fp = i % 2 ? TAG_MAX : TAG_B; 462 break; 463 case (MAN_RB): 464 fp = i % 2 ? TAG_B : TAG_MAX; 465 break; 466 default: 467 abort(); 468 /* NOTREACHED */ 469 } 470 471 if (i) 472 h->flags |= HTML_NOSPACE; 473 474 if (TAG_MAX != fp) 475 t = print_otag(h, fp, 0, NULL); 476 477 print_man_node(man, nn, mh, h); 478 479 if (t) 480 print_tagq(h, t); 481 } 482 483 if (savelit) 484 mh->fl |= MANH_LITERAL; 485 486 return(0); 487 } 488 489 /* ARGSUSED */ 490 static int 491 man_SM_pre(MAN_ARGS) 492 { 493 494 print_otag(h, TAG_SMALL, 0, NULL); 495 if (MAN_SB == n->tok) 496 print_otag(h, TAG_B, 0, NULL); 497 return(1); 498 } 499 500 /* ARGSUSED */ 501 static int 502 man_SS_pre(MAN_ARGS) 503 { 504 struct htmlpair tag; 505 506 if (MAN_BLOCK == n->type) { 507 mh->fl &= ~MANH_LITERAL; 508 PAIR_CLASS_INIT(&tag, "subsection"); 509 print_otag(h, TAG_DIV, 1, &tag); 510 return(1); 511 } else if (MAN_BODY == n->type) 512 return(1); 513 514 print_otag(h, TAG_H2, 0, NULL); 515 return(1); 516 } 517 518 /* ARGSUSED */ 519 static int 520 man_PP_pre(MAN_ARGS) 521 { 522 523 if (MAN_HEAD == n->type) 524 return(0); 525 else if (MAN_BLOCK == n->type) 526 print_bvspace(h, n); 527 528 return(1); 529 } 530 531 /* ARGSUSED */ 532 static int 533 man_IP_pre(MAN_ARGS) 534 { 535 const struct man_node *nn; 536 537 if (MAN_BODY == n->type) { 538 print_otag(h, TAG_DD, 0, NULL); 539 return(1); 540 } else if (MAN_HEAD != n->type) { 541 print_otag(h, TAG_DL, 0, NULL); 542 return(1); 543 } 544 545 /* FIXME: width specification. */ 546 547 print_otag(h, TAG_DT, 0, NULL); 548 549 /* For IP, only print the first header element. */ 550 551 if (MAN_IP == n->tok && n->child) 552 print_man_node(man, n->child, mh, h); 553 554 /* For TP, only print next-line header elements. */ 555 556 if (MAN_TP == n->tok) 557 for (nn = n->child; nn; nn = nn->next) 558 if (nn->line > n->line) 559 print_man_node(man, nn, mh, h); 560 561 return(0); 562 } 563 564 /* ARGSUSED */ 565 static int 566 man_HP_pre(MAN_ARGS) 567 { 568 struct htmlpair tag; 569 struct roffsu su; 570 const struct man_node *np; 571 572 if (MAN_HEAD == n->type) 573 return(0); 574 else if (MAN_BLOCK != n->type) 575 return(1); 576 577 np = n->head->child; 578 579 if (NULL == np || ! a2width(np, &su)) 580 SCALE_HS_INIT(&su, INDENT); 581 582 bufinit(h); 583 584 print_bvspace(h, n); 585 bufcat_su(h, "margin-left", &su); 586 su.scale = -su.scale; 587 bufcat_su(h, "text-indent", &su); 588 PAIR_STYLE_INIT(&tag, h); 589 print_otag(h, TAG_P, 1, &tag); 590 return(1); 591 } 592 593 /* ARGSUSED */ 594 static int 595 man_OP_pre(MAN_ARGS) 596 { 597 struct tag *tt; 598 struct htmlpair tag; 599 600 print_text(h, "["); 601 h->flags |= HTML_NOSPACE; 602 PAIR_CLASS_INIT(&tag, "opt"); 603 tt = print_otag(h, TAG_SPAN, 1, &tag); 604 605 if (NULL != (n = n->child)) { 606 print_otag(h, TAG_B, 0, NULL); 607 print_text(h, n->string); 608 } 609 610 print_stagq(h, tt); 611 612 if (NULL != n && NULL != n->next) { 613 print_otag(h, TAG_I, 0, NULL); 614 print_text(h, n->next->string); 615 } 616 617 print_stagq(h, tt); 618 h->flags |= HTML_NOSPACE; 619 print_text(h, "]"); 620 return(0); 621 } 622 623 624 /* ARGSUSED */ 625 static int 626 man_B_pre(MAN_ARGS) 627 { 628 629 print_otag(h, TAG_B, 0, NULL); 630 return(1); 631 } 632 633 /* ARGSUSED */ 634 static int 635 man_I_pre(MAN_ARGS) 636 { 637 638 print_otag(h, TAG_I, 0, NULL); 639 return(1); 640 } 641 642 /* ARGSUSED */ 643 static int 644 man_literal_pre(MAN_ARGS) 645 { 646 647 if (MAN_fi == n->tok || MAN_EE == n->tok) { 648 print_otag(h, TAG_BR, 0, NULL); 649 mh->fl &= ~MANH_LITERAL; 650 } else 651 mh->fl |= MANH_LITERAL; 652 653 return(0); 654 } 655 656 /* ARGSUSED */ 657 static int 658 man_in_pre(MAN_ARGS) 659 { 660 661 print_otag(h, TAG_BR, 0, NULL); 662 return(0); 663 } 664 665 /* ARGSUSED */ 666 static int 667 man_ign_pre(MAN_ARGS) 668 { 669 670 return(0); 671 } 672 673 /* ARGSUSED */ 674 static int 675 man_RS_pre(MAN_ARGS) 676 { 677 struct htmlpair tag; 678 struct roffsu su; 679 680 if (MAN_HEAD == n->type) 681 return(0); 682 else if (MAN_BODY == n->type) 683 return(1); 684 685 SCALE_HS_INIT(&su, INDENT); 686 if (n->head->child) 687 a2width(n->head->child, &su); 688 689 bufinit(h); 690 bufcat_su(h, "margin-left", &su); 691 PAIR_STYLE_INIT(&tag, h); 692 print_otag(h, TAG_DIV, 1, &tag); 693 return(1); 694 } 695 696 /* ARGSUSED */ 697 static int 698 man_UR_pre(MAN_ARGS) 699 { 700 struct htmlpair tag[2]; 701 702 n = n->child; 703 assert(MAN_HEAD == n->type); 704 if (n->nchild) { 705 assert(MAN_TEXT == n->child->type); 706 PAIR_CLASS_INIT(&tag[0], "link-ext"); 707 PAIR_HREF_INIT(&tag[1], n->child->string); 708 print_otag(h, TAG_A, 2, tag); 709 } 710 711 assert(MAN_BODY == n->next->type); 712 if (n->next->nchild) 713 n = n->next; 714 715 print_man_nodelist(man, n->child, mh, h); 716 717 return(0); 718 } 719