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