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