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