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