1 /* $Vendor-Id: man_html.c,v 1.82 2011/10/05 21:35:17 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 62 static int a2width(const struct man_node *, 63 struct roffsu *); 64 65 static int man_alt_pre(MAN_ARGS); 66 static int man_br_pre(MAN_ARGS); 67 static int man_ign_pre(MAN_ARGS); 68 static int man_in_pre(MAN_ARGS); 69 static int man_literal_pre(MAN_ARGS); 70 static void man_root_post(MAN_ARGS); 71 static void man_root_pre(MAN_ARGS); 72 static int man_B_pre(MAN_ARGS); 73 static int man_HP_pre(MAN_ARGS); 74 static int man_I_pre(MAN_ARGS); 75 static int man_IP_pre(MAN_ARGS); 76 static int man_PP_pre(MAN_ARGS); 77 static int man_RS_pre(MAN_ARGS); 78 static int man_SH_pre(MAN_ARGS); 79 static int man_SM_pre(MAN_ARGS); 80 static int man_SS_pre(MAN_ARGS); 81 82 static const struct htmlman mans[MAN_MAX] = { 83 { man_br_pre, NULL }, /* br */ 84 { NULL, NULL }, /* TH */ 85 { man_SH_pre, NULL }, /* SH */ 86 { man_SS_pre, NULL }, /* SS */ 87 { man_IP_pre, NULL }, /* TP */ 88 { man_PP_pre, NULL }, /* LP */ 89 { man_PP_pre, NULL }, /* PP */ 90 { man_PP_pre, NULL }, /* P */ 91 { man_IP_pre, NULL }, /* IP */ 92 { man_HP_pre, NULL }, /* HP */ 93 { man_SM_pre, NULL }, /* SM */ 94 { man_SM_pre, NULL }, /* SB */ 95 { man_alt_pre, NULL }, /* BI */ 96 { man_alt_pre, NULL }, /* IB */ 97 { man_alt_pre, NULL }, /* BR */ 98 { man_alt_pre, NULL }, /* RB */ 99 { NULL, NULL }, /* R */ 100 { man_B_pre, NULL }, /* B */ 101 { man_I_pre, NULL }, /* I */ 102 { man_alt_pre, NULL }, /* IR */ 103 { man_alt_pre, NULL }, /* RI */ 104 { man_ign_pre, NULL }, /* na */ 105 { man_br_pre, NULL }, /* sp */ 106 { man_literal_pre, NULL }, /* nf */ 107 { man_literal_pre, NULL }, /* fi */ 108 { NULL, NULL }, /* RE */ 109 { man_RS_pre, NULL }, /* RS */ 110 { man_ign_pre, NULL }, /* DT */ 111 { man_ign_pre, NULL }, /* UC */ 112 { man_ign_pre, NULL }, /* PD */ 113 { man_ign_pre, NULL }, /* AT */ 114 { man_in_pre, NULL }, /* in */ 115 { man_ign_pre, NULL }, /* ft */ 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 bufcat_fmt(h, "%s(%s)", m->title, m->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(m, n, mh, h); 192 if (n->next) 193 print_man_nodelist(m, 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(m, 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)(m, n, mh, h); 260 break; 261 } 262 263 if (child && n->child) 264 print_man_nodelist(m, 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(m, n, mh, h); 272 break; 273 case (MAN_EQN): 274 break; 275 default: 276 if (mans[n->tok].post) 277 (*mans[n->tok].post)(m, 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 (m->vol) 306 (void)strlcat(b, m->vol, BUFSIZ); 307 308 snprintf(title, BUFSIZ - 1, "%s(%s)", m->title, m->msec); 309 310 PAIR_SUMMARY_INIT(&tag[0], "Document Header"); 311 PAIR_CLASS_INIT(&tag[1], "head"); 312 if (NULL == h->style) { 313 PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); 314 t = print_otag(h, TAG_TABLE, 3, tag); 315 PAIR_INIT(&tag[0], ATTR_WIDTH, "30%"); 316 print_otag(h, TAG_COL, 1, tag); 317 print_otag(h, TAG_COL, 1, tag); 318 print_otag(h, TAG_COL, 1, tag); 319 } else 320 t = print_otag(h, TAG_TABLE, 2, tag); 321 322 print_otag(h, TAG_TBODY, 0, NULL); 323 324 tt = print_otag(h, TAG_TR, 0, NULL); 325 326 PAIR_CLASS_INIT(&tag[0], "head-ltitle"); 327 print_otag(h, TAG_TD, 1, tag); 328 329 print_text(h, title); 330 print_stagq(h, tt); 331 332 PAIR_CLASS_INIT(&tag[0], "head-vol"); 333 if (NULL == h->style) { 334 PAIR_INIT(&tag[1], ATTR_ALIGN, "center"); 335 print_otag(h, TAG_TD, 2, tag); 336 } else 337 print_otag(h, TAG_TD, 1, tag); 338 339 print_text(h, b); 340 print_stagq(h, tt); 341 342 PAIR_CLASS_INIT(&tag[0], "head-rtitle"); 343 if (NULL == h->style) { 344 PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); 345 print_otag(h, TAG_TD, 2, tag); 346 } else 347 print_otag(h, TAG_TD, 1, tag); 348 349 print_text(h, title); 350 print_tagq(h, t); 351 } 352 353 354 /* ARGSUSED */ 355 static void 356 man_root_post(MAN_ARGS) 357 { 358 struct htmlpair tag[3]; 359 struct tag *t, *tt; 360 361 PAIR_SUMMARY_INIT(&tag[0], "Document Footer"); 362 PAIR_CLASS_INIT(&tag[1], "foot"); 363 if (NULL == h->style) { 364 PAIR_INIT(&tag[2], ATTR_WIDTH, "100%"); 365 t = print_otag(h, TAG_TABLE, 3, tag); 366 PAIR_INIT(&tag[0], ATTR_WIDTH, "50%"); 367 print_otag(h, TAG_COL, 1, tag); 368 print_otag(h, TAG_COL, 1, tag); 369 } else 370 t = print_otag(h, TAG_TABLE, 2, tag); 371 372 tt = print_otag(h, TAG_TR, 0, NULL); 373 374 PAIR_CLASS_INIT(&tag[0], "foot-date"); 375 print_otag(h, TAG_TD, 1, tag); 376 377 print_text(h, m->date); 378 print_stagq(h, tt); 379 380 PAIR_CLASS_INIT(&tag[0], "foot-os"); 381 if (NULL == h->style) { 382 PAIR_INIT(&tag[1], ATTR_ALIGN, "right"); 383 print_otag(h, TAG_TD, 2, tag); 384 } else 385 print_otag(h, TAG_TD, 1, tag); 386 387 if (m->source) 388 print_text(h, m->source); 389 print_tagq(h, t); 390 } 391 392 393 /* ARGSUSED */ 394 static int 395 man_br_pre(MAN_ARGS) 396 { 397 struct roffsu su; 398 struct htmlpair tag; 399 400 SCALE_VS_INIT(&su, 1); 401 402 if (MAN_sp == n->tok) { 403 if (NULL != (n = n->child)) 404 if ( ! a2roffsu(n->string, &su, SCALE_VS)) 405 SCALE_VS_INIT(&su, atoi(n->string)); 406 } else 407 su.scale = 0; 408 409 bufinit(h); 410 bufcat_su(h, "height", &su); 411 PAIR_STYLE_INIT(&tag, h); 412 print_otag(h, TAG_DIV, 1, &tag); 413 414 /* So the div isn't empty: */ 415 print_text(h, "\\~"); 416 417 return(0); 418 } 419 420 /* ARGSUSED */ 421 static int 422 man_SH_pre(MAN_ARGS) 423 { 424 struct htmlpair tag; 425 426 if (MAN_BLOCK == n->type) { 427 mh->fl &= ~MANH_LITERAL; 428 PAIR_CLASS_INIT(&tag, "section"); 429 print_otag(h, TAG_DIV, 1, &tag); 430 return(1); 431 } else if (MAN_BODY == n->type) 432 return(1); 433 434 print_otag(h, TAG_H1, 0, NULL); 435 return(1); 436 } 437 438 /* ARGSUSED */ 439 static int 440 man_alt_pre(MAN_ARGS) 441 { 442 const struct man_node *nn; 443 int i, savelit; 444 enum htmltag fp; 445 struct tag *t; 446 447 if ((savelit = mh->fl & MANH_LITERAL)) 448 print_otag(h, TAG_BR, 0, NULL); 449 450 mh->fl &= ~MANH_LITERAL; 451 452 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 453 t = NULL; 454 switch (n->tok) { 455 case (MAN_BI): 456 fp = i % 2 ? TAG_I : TAG_B; 457 break; 458 case (MAN_IB): 459 fp = i % 2 ? TAG_B : TAG_I; 460 break; 461 case (MAN_RI): 462 fp = i % 2 ? TAG_I : TAG_MAX; 463 break; 464 case (MAN_IR): 465 fp = i % 2 ? TAG_MAX : TAG_I; 466 break; 467 case (MAN_BR): 468 fp = i % 2 ? TAG_MAX : TAG_B; 469 break; 470 case (MAN_RB): 471 fp = i % 2 ? TAG_B : TAG_MAX; 472 break; 473 default: 474 abort(); 475 /* NOTREACHED */ 476 } 477 478 if (i) 479 h->flags |= HTML_NOSPACE; 480 481 if (TAG_MAX != fp) 482 t = print_otag(h, fp, 0, NULL); 483 484 print_man_node(m, nn, mh, h); 485 486 if (t) 487 print_tagq(h, t); 488 } 489 490 if (savelit) 491 mh->fl |= MANH_LITERAL; 492 493 return(0); 494 } 495 496 /* ARGSUSED */ 497 static int 498 man_SM_pre(MAN_ARGS) 499 { 500 501 print_otag(h, TAG_SMALL, 0, NULL); 502 if (MAN_SB == n->tok) 503 print_otag(h, TAG_B, 0, NULL); 504 return(1); 505 } 506 507 /* ARGSUSED */ 508 static int 509 man_SS_pre(MAN_ARGS) 510 { 511 struct htmlpair tag; 512 513 if (MAN_BLOCK == n->type) { 514 mh->fl &= ~MANH_LITERAL; 515 PAIR_CLASS_INIT(&tag, "subsection"); 516 print_otag(h, TAG_DIV, 1, &tag); 517 return(1); 518 } else if (MAN_BODY == n->type) 519 return(1); 520 521 print_otag(h, TAG_H2, 0, NULL); 522 return(1); 523 } 524 525 /* ARGSUSED */ 526 static int 527 man_PP_pre(MAN_ARGS) 528 { 529 530 if (MAN_HEAD == n->type) 531 return(0); 532 else if (MAN_BLOCK == n->type) 533 print_bvspace(h, n); 534 535 return(1); 536 } 537 538 /* ARGSUSED */ 539 static int 540 man_IP_pre(MAN_ARGS) 541 { 542 const struct man_node *nn; 543 544 if (MAN_BODY == n->type) { 545 print_otag(h, TAG_DD, 0, NULL); 546 return(1); 547 } else if (MAN_HEAD != n->type) { 548 print_otag(h, TAG_DL, 0, NULL); 549 return(1); 550 } 551 552 /* FIXME: width specification. */ 553 554 print_otag(h, TAG_DT, 0, NULL); 555 556 /* For IP, only print the first header element. */ 557 558 if (MAN_IP == n->tok && n->child) 559 print_man_node(m, n->child, mh, h); 560 561 /* For TP, only print next-line header elements. */ 562 563 if (MAN_TP == n->tok) 564 for (nn = n->child; nn; nn = nn->next) 565 if (nn->line > n->line) 566 print_man_node(m, nn, mh, h); 567 568 return(0); 569 } 570 571 /* ARGSUSED */ 572 static int 573 man_HP_pre(MAN_ARGS) 574 { 575 struct htmlpair tag; 576 struct roffsu su; 577 const struct man_node *np; 578 579 if (MAN_HEAD == n->type) 580 return(0); 581 else if (MAN_BLOCK != n->type) 582 return(1); 583 584 np = n->head->child; 585 586 if (NULL == np || ! a2width(np, &su)) 587 SCALE_HS_INIT(&su, INDENT); 588 589 bufinit(h); 590 591 print_bvspace(h, n); 592 bufcat_su(h, "margin-left", &su); 593 su.scale = -su.scale; 594 bufcat_su(h, "text-indent", &su); 595 PAIR_STYLE_INIT(&tag, h); 596 print_otag(h, TAG_P, 1, &tag); 597 return(1); 598 } 599 600 /* ARGSUSED */ 601 static int 602 man_B_pre(MAN_ARGS) 603 { 604 605 print_otag(h, TAG_B, 0, NULL); 606 return(1); 607 } 608 609 /* ARGSUSED */ 610 static int 611 man_I_pre(MAN_ARGS) 612 { 613 614 print_otag(h, TAG_I, 0, NULL); 615 return(1); 616 } 617 618 /* ARGSUSED */ 619 static int 620 man_literal_pre(MAN_ARGS) 621 { 622 623 if (MAN_nf != n->tok) { 624 print_otag(h, TAG_BR, 0, NULL); 625 mh->fl &= ~MANH_LITERAL; 626 } else 627 mh->fl |= MANH_LITERAL; 628 629 return(0); 630 } 631 632 /* ARGSUSED */ 633 static int 634 man_in_pre(MAN_ARGS) 635 { 636 637 print_otag(h, TAG_BR, 0, NULL); 638 return(0); 639 } 640 641 /* ARGSUSED */ 642 static int 643 man_ign_pre(MAN_ARGS) 644 { 645 646 return(0); 647 } 648 649 /* ARGSUSED */ 650 static int 651 man_RS_pre(MAN_ARGS) 652 { 653 struct htmlpair tag; 654 struct roffsu su; 655 656 if (MAN_HEAD == n->type) 657 return(0); 658 else if (MAN_BODY == n->type) 659 return(1); 660 661 SCALE_HS_INIT(&su, INDENT); 662 if (n->head->child) 663 a2width(n->head->child, &su); 664 665 bufinit(h); 666 bufcat_su(h, "margin-left", &su); 667 PAIR_STYLE_INIT(&tag, h); 668 print_otag(h, TAG_DIV, 1, &tag); 669 return(1); 670 } 671