1 /* $OpenBSD: man_html.c,v 1.96 2017/06/08 12:54:40 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2013, 2014, 2015, 2017 Ingo Schwarze <schwarze@openbsd.org> 5 * 6 * Permission to use, copy, modify, and distribute this software for any 7 * purpose with or without fee is hereby granted, provided that the above 8 * copyright notice and this permission notice appear in all copies. 9 * 10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES 11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF 12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR 13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES 14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN 15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF 16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. 17 */ 18 #include <sys/types.h> 19 20 #include <assert.h> 21 #include <ctype.h> 22 #include <stdio.h> 23 #include <stdlib.h> 24 #include <string.h> 25 26 #include "mandoc_aux.h" 27 #include "roff.h" 28 #include "man.h" 29 #include "out.h" 30 #include "html.h" 31 #include "main.h" 32 33 /* FIXME: have PD set the default vspace width. */ 34 35 #define INDENT 5 36 37 #define MAN_ARGS const struct roff_meta *man, \ 38 const struct roff_node *n, \ 39 struct html *h 40 41 struct htmlman { 42 int (*pre)(MAN_ARGS); 43 int (*post)(MAN_ARGS); 44 }; 45 46 static void print_bvspace(struct html *, 47 const struct roff_node *); 48 static void print_man_head(MAN_ARGS); 49 static void print_man_nodelist(MAN_ARGS); 50 static void print_man_node(MAN_ARGS); 51 static int fillmode(struct html *, int); 52 static int a2width(const struct roff_node *, 53 struct roffsu *); 54 static int man_B_pre(MAN_ARGS); 55 static int man_HP_pre(MAN_ARGS); 56 static int man_IP_pre(MAN_ARGS); 57 static int man_I_pre(MAN_ARGS); 58 static int man_OP_pre(MAN_ARGS); 59 static int man_PP_pre(MAN_ARGS); 60 static int man_RS_pre(MAN_ARGS); 61 static int man_SH_pre(MAN_ARGS); 62 static int man_SM_pre(MAN_ARGS); 63 static int man_SS_pre(MAN_ARGS); 64 static int man_UR_pre(MAN_ARGS); 65 static int man_alt_pre(MAN_ARGS); 66 static int man_ign_pre(MAN_ARGS); 67 static int man_in_pre(MAN_ARGS); 68 static void man_root_post(MAN_ARGS); 69 static void man_root_pre(MAN_ARGS); 70 71 static const struct htmlman __mans[MAN_MAX - MAN_TH] = { 72 { NULL, NULL }, /* TH */ 73 { man_SH_pre, NULL }, /* SH */ 74 { man_SS_pre, NULL }, /* SS */ 75 { man_IP_pre, NULL }, /* TP */ 76 { man_PP_pre, NULL }, /* LP */ 77 { man_PP_pre, NULL }, /* PP */ 78 { man_PP_pre, NULL }, /* P */ 79 { man_IP_pre, NULL }, /* IP */ 80 { man_HP_pre, NULL }, /* HP */ 81 { man_SM_pre, NULL }, /* SM */ 82 { man_SM_pre, NULL }, /* SB */ 83 { man_alt_pre, NULL }, /* BI */ 84 { man_alt_pre, NULL }, /* IB */ 85 { man_alt_pre, NULL }, /* BR */ 86 { man_alt_pre, NULL }, /* RB */ 87 { NULL, NULL }, /* R */ 88 { man_B_pre, NULL }, /* B */ 89 { man_I_pre, NULL }, /* I */ 90 { man_alt_pre, NULL }, /* IR */ 91 { man_alt_pre, NULL }, /* RI */ 92 { NULL, NULL }, /* nf */ 93 { NULL, NULL }, /* fi */ 94 { NULL, NULL }, /* RE */ 95 { man_RS_pre, NULL }, /* RS */ 96 { man_ign_pre, NULL }, /* DT */ 97 { man_ign_pre, NULL }, /* UC */ 98 { man_ign_pre, NULL }, /* PD */ 99 { man_ign_pre, NULL }, /* AT */ 100 { man_in_pre, NULL }, /* in */ 101 { man_OP_pre, NULL }, /* OP */ 102 { NULL, NULL }, /* EX */ 103 { NULL, NULL }, /* EE */ 104 { man_UR_pre, NULL }, /* UR */ 105 { NULL, NULL }, /* UE */ 106 }; 107 static const struct htmlman *const mans = __mans - MAN_TH; 108 109 110 /* 111 * Printing leading vertical space before a block. 112 * This is used for the paragraph macros. 113 * The rules are pretty simple, since there's very little nesting going 114 * on here. Basically, if we're the first within another block (SS/SH), 115 * then don't emit vertical space. If we are (RS), then do. If not the 116 * first, print it. 117 */ 118 static void 119 print_bvspace(struct html *h, const struct roff_node *n) 120 { 121 122 if (n->body && n->body->child) 123 if (n->body->child->type == ROFFT_TBL) 124 return; 125 126 if (n->parent->type == ROFFT_ROOT || n->parent->tok != MAN_RS) 127 if (NULL == n->prev) 128 return; 129 130 print_paragraph(h); 131 } 132 133 void 134 html_man(void *arg, const struct roff_man *man) 135 { 136 struct html *h; 137 struct tag *t; 138 139 h = (struct html *)arg; 140 141 if ((h->oflags & HTML_FRAGMENT) == 0) { 142 print_gen_decls(h); 143 print_otag(h, TAG_HTML, ""); 144 t = print_otag(h, TAG_HEAD, ""); 145 print_man_head(&man->meta, man->first, h); 146 print_tagq(h, t); 147 print_otag(h, TAG_BODY, ""); 148 } 149 150 man_root_pre(&man->meta, man->first, h); 151 t = print_otag(h, TAG_DIV, "c", "manual-text"); 152 print_man_nodelist(&man->meta, man->first->child, h); 153 print_tagq(h, t); 154 man_root_post(&man->meta, man->first, h); 155 print_tagq(h, NULL); 156 } 157 158 static void 159 print_man_head(MAN_ARGS) 160 { 161 char *cp; 162 163 print_gen_head(h); 164 mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec); 165 print_otag(h, TAG_TITLE, ""); 166 print_text(h, cp); 167 free(cp); 168 } 169 170 static void 171 print_man_nodelist(MAN_ARGS) 172 { 173 174 while (n != NULL) { 175 print_man_node(man, n, h); 176 n = n->next; 177 } 178 } 179 180 static void 181 print_man_node(MAN_ARGS) 182 { 183 static int want_fillmode = MAN_fi; 184 static int save_fillmode; 185 186 struct tag *t; 187 int child; 188 189 /* 190 * Handle fill mode switch requests up front, 191 * they would just cause trouble in the subsequent code. 192 */ 193 194 switch (n->tok) { 195 case MAN_nf: 196 case MAN_EX: 197 want_fillmode = MAN_nf; 198 return; 199 case MAN_fi: 200 case MAN_EE: 201 want_fillmode = MAN_fi; 202 if (fillmode(h, 0) == MAN_fi) 203 print_otag(h, TAG_BR, ""); 204 return; 205 default: 206 break; 207 } 208 209 /* Set up fill mode for the upcoming node. */ 210 211 switch (n->type) { 212 case ROFFT_BLOCK: 213 save_fillmode = 0; 214 /* Some block macros suspend or cancel .nf. */ 215 switch (n->tok) { 216 case MAN_TP: /* Tagged paragraphs */ 217 case MAN_IP: /* temporarily disable .nf */ 218 case MAN_HP: /* for the head. */ 219 save_fillmode = want_fillmode; 220 /* FALLTHROUGH */ 221 case MAN_SH: /* Section headers */ 222 case MAN_SS: /* permanently cancel .nf. */ 223 want_fillmode = MAN_fi; 224 /* FALLTHROUGH */ 225 case MAN_PP: /* These have no head. */ 226 case MAN_LP: /* They will simply */ 227 case MAN_P: /* reopen .nf in the body. */ 228 case MAN_RS: 229 case MAN_UR: 230 fillmode(h, MAN_fi); 231 break; 232 default: 233 break; 234 } 235 break; 236 case ROFFT_TBL: 237 fillmode(h, MAN_fi); 238 break; 239 case ROFFT_ELEM: 240 /* 241 * Some in-line macros produce tags and/or text 242 * in the handler, so they require fill mode to be 243 * configured up front just like for text nodes. 244 * For the others, keep the traditional approach 245 * of doing the same, for now. 246 */ 247 fillmode(h, want_fillmode); 248 break; 249 case ROFFT_TEXT: 250 if (fillmode(h, want_fillmode) == MAN_fi && 251 want_fillmode == MAN_fi && 252 n->flags & NODE_LINE && *n->string == ' ' && 253 (h->flags & HTML_NONEWLINE) == 0) 254 print_otag(h, TAG_BR, ""); 255 if (*n->string != '\0') 256 break; 257 print_paragraph(h); 258 return; 259 default: 260 break; 261 } 262 263 /* Produce output for this node. */ 264 265 child = 1; 266 switch (n->type) { 267 case ROFFT_TEXT: 268 t = h->tag; 269 print_text(h, n->string); 270 break; 271 case ROFFT_EQN: 272 t = h->tag; 273 print_eqn(h, n->eqn); 274 break; 275 case ROFFT_TBL: 276 /* 277 * This will take care of initialising all of the table 278 * state data for the first table, then tearing it down 279 * for the last one. 280 */ 281 print_tbl(h, n->span); 282 return; 283 default: 284 /* 285 * Close out scope of font prior to opening a macro 286 * scope. 287 */ 288 if (HTMLFONT_NONE != h->metac) { 289 h->metal = h->metac; 290 h->metac = HTMLFONT_NONE; 291 } 292 293 /* 294 * Close out the current table, if it's open, and unset 295 * the "meta" table state. This will be reopened on the 296 * next table element. 297 */ 298 if (h->tblt) 299 print_tblclose(h); 300 301 t = h->tag; 302 if (n->tok < ROFF_MAX) { 303 roff_html_pre(h, n); 304 child = 0; 305 break; 306 } 307 308 assert(n->tok >= MAN_TH && n->tok < MAN_MAX); 309 if (mans[n->tok].pre) 310 child = (*mans[n->tok].pre)(man, n, h); 311 312 /* Some block macros resume .nf in the body. */ 313 if (save_fillmode && n->type == ROFFT_BODY) 314 want_fillmode = save_fillmode; 315 316 break; 317 } 318 319 if (child && n->child) 320 print_man_nodelist(man, n->child, h); 321 322 /* This will automatically close out any font scope. */ 323 print_stagq(h, t); 324 325 if (fillmode(h, 0) == MAN_nf && 326 n->next != NULL && n->next->flags & NODE_LINE) 327 print_endline(h); 328 } 329 330 /* 331 * MAN_nf switches to no-fill mode, MAN_fi to fill mode. 332 * Other arguments do not switch. 333 * The old mode is returned. 334 */ 335 static int 336 fillmode(struct html *h, int want) 337 { 338 struct tag *pre; 339 int had; 340 341 for (pre = h->tag; pre != NULL; pre = pre->next) 342 if (pre->tag == TAG_PRE) 343 break; 344 345 had = pre == NULL ? MAN_fi : MAN_nf; 346 347 if (want && want != had) { 348 if (want == MAN_nf) 349 print_otag(h, TAG_PRE, ""); 350 else 351 print_tagq(h, pre); 352 } 353 return had; 354 } 355 356 static int 357 a2width(const struct roff_node *n, struct roffsu *su) 358 { 359 if (n->type != ROFFT_TEXT) 360 return 0; 361 return a2roffsu(n->string, su, SCALE_EN) != NULL; 362 } 363 364 static void 365 man_root_pre(MAN_ARGS) 366 { 367 struct tag *t, *tt; 368 char *title; 369 370 assert(man->title); 371 assert(man->msec); 372 mandoc_asprintf(&title, "%s(%s)", man->title, man->msec); 373 374 t = print_otag(h, TAG_TABLE, "c", "head"); 375 tt = print_otag(h, TAG_TR, ""); 376 377 print_otag(h, TAG_TD, "c", "head-ltitle"); 378 print_text(h, title); 379 print_stagq(h, tt); 380 381 print_otag(h, TAG_TD, "c", "head-vol"); 382 if (NULL != man->vol) 383 print_text(h, man->vol); 384 print_stagq(h, tt); 385 386 print_otag(h, TAG_TD, "c", "head-rtitle"); 387 print_text(h, title); 388 print_tagq(h, t); 389 free(title); 390 } 391 392 static void 393 man_root_post(MAN_ARGS) 394 { 395 struct tag *t, *tt; 396 397 t = print_otag(h, TAG_TABLE, "c", "foot"); 398 tt = print_otag(h, TAG_TR, ""); 399 400 print_otag(h, TAG_TD, "c", "foot-date"); 401 print_text(h, man->date); 402 print_stagq(h, tt); 403 404 print_otag(h, TAG_TD, "c", "foot-os"); 405 if (man->os) 406 print_text(h, man->os); 407 print_tagq(h, t); 408 } 409 410 static int 411 man_SH_pre(MAN_ARGS) 412 { 413 char *id; 414 415 if (n->type == ROFFT_HEAD) { 416 id = html_make_id(n); 417 print_otag(h, TAG_H1, "cTi", "Sh", id); 418 if (id != NULL) 419 print_otag(h, TAG_A, "chR", "selflink", id); 420 free(id); 421 } 422 return 1; 423 } 424 425 static int 426 man_alt_pre(MAN_ARGS) 427 { 428 const struct roff_node *nn; 429 int i; 430 enum htmltag fp; 431 struct tag *t; 432 433 for (i = 0, nn = n->child; nn; nn = nn->next, i++) { 434 switch (n->tok) { 435 case MAN_BI: 436 fp = i % 2 ? TAG_I : TAG_B; 437 break; 438 case MAN_IB: 439 fp = i % 2 ? TAG_B : TAG_I; 440 break; 441 case MAN_RI: 442 fp = i % 2 ? TAG_I : TAG_MAX; 443 break; 444 case MAN_IR: 445 fp = i % 2 ? TAG_MAX : TAG_I; 446 break; 447 case MAN_BR: 448 fp = i % 2 ? TAG_MAX : TAG_B; 449 break; 450 case MAN_RB: 451 fp = i % 2 ? TAG_B : TAG_MAX; 452 break; 453 default: 454 abort(); 455 } 456 457 if (i) 458 h->flags |= HTML_NOSPACE; 459 460 if (fp != TAG_MAX) 461 t = print_otag(h, fp, ""); 462 463 print_text(h, nn->string); 464 465 if (fp != TAG_MAX) 466 print_tagq(h, t); 467 } 468 return 0; 469 } 470 471 static int 472 man_SM_pre(MAN_ARGS) 473 { 474 print_otag(h, TAG_SMALL, ""); 475 if (MAN_SB == n->tok) 476 print_otag(h, TAG_B, ""); 477 return 1; 478 } 479 480 static int 481 man_SS_pre(MAN_ARGS) 482 { 483 char *id; 484 485 if (n->type == ROFFT_HEAD) { 486 id = html_make_id(n); 487 print_otag(h, TAG_H2, "cTi", "Ss", id); 488 if (id != NULL) 489 print_otag(h, TAG_A, "chR", "selflink", id); 490 free(id); 491 } 492 return 1; 493 } 494 495 static int 496 man_PP_pre(MAN_ARGS) 497 { 498 499 if (n->type == ROFFT_HEAD) 500 return 0; 501 else if (n->type == ROFFT_BLOCK) 502 print_bvspace(h, n); 503 504 return 1; 505 } 506 507 static int 508 man_IP_pre(MAN_ARGS) 509 { 510 const struct roff_node *nn; 511 512 if (n->type == ROFFT_BODY) { 513 print_otag(h, TAG_DD, "c", "It-tag"); 514 return 1; 515 } else if (n->type != ROFFT_HEAD) { 516 print_otag(h, TAG_DL, "c", "Bl-tag"); 517 return 1; 518 } 519 520 /* FIXME: width specification. */ 521 522 print_otag(h, TAG_DT, "c", "It-tag"); 523 524 /* For IP, only print the first header element. */ 525 526 if (MAN_IP == n->tok && n->child) 527 print_man_node(man, n->child, h); 528 529 /* For TP, only print next-line header elements. */ 530 531 if (MAN_TP == n->tok) { 532 nn = n->child; 533 while (NULL != nn && 0 == (NODE_LINE & nn->flags)) 534 nn = nn->next; 535 while (NULL != nn) { 536 print_man_node(man, nn, h); 537 nn = nn->next; 538 } 539 } 540 541 return 0; 542 } 543 544 static int 545 man_HP_pre(MAN_ARGS) 546 { 547 struct roffsu sum, sui; 548 const struct roff_node *np; 549 550 if (n->type == ROFFT_HEAD) 551 return 0; 552 else if (n->type != ROFFT_BLOCK) 553 return 1; 554 555 np = n->head->child; 556 557 if (np == NULL || !a2width(np, &sum)) 558 SCALE_HS_INIT(&sum, INDENT); 559 560 sui.unit = sum.unit; 561 sui.scale = -sum.scale; 562 563 print_bvspace(h, n); 564 print_otag(h, TAG_DIV, "csului", "Pp", &sum, &sui); 565 return 1; 566 } 567 568 static int 569 man_OP_pre(MAN_ARGS) 570 { 571 struct tag *tt; 572 573 print_text(h, "["); 574 h->flags |= HTML_NOSPACE; 575 tt = print_otag(h, TAG_SPAN, "c", "Op"); 576 577 if (NULL != (n = n->child)) { 578 print_otag(h, TAG_B, ""); 579 print_text(h, n->string); 580 } 581 582 print_stagq(h, tt); 583 584 if (NULL != n && NULL != n->next) { 585 print_otag(h, TAG_I, ""); 586 print_text(h, n->next->string); 587 } 588 589 print_stagq(h, tt); 590 h->flags |= HTML_NOSPACE; 591 print_text(h, "]"); 592 return 0; 593 } 594 595 static int 596 man_B_pre(MAN_ARGS) 597 { 598 print_otag(h, TAG_B, ""); 599 return 1; 600 } 601 602 static int 603 man_I_pre(MAN_ARGS) 604 { 605 print_otag(h, TAG_I, ""); 606 return 1; 607 } 608 609 static int 610 man_in_pre(MAN_ARGS) 611 { 612 print_otag(h, TAG_BR, ""); 613 return 0; 614 } 615 616 static int 617 man_ign_pre(MAN_ARGS) 618 { 619 620 return 0; 621 } 622 623 static int 624 man_RS_pre(MAN_ARGS) 625 { 626 struct roffsu su; 627 628 if (n->type == ROFFT_HEAD) 629 return 0; 630 else if (n->type == ROFFT_BODY) 631 return 1; 632 633 SCALE_HS_INIT(&su, INDENT); 634 if (n->head->child) 635 a2width(n->head->child, &su); 636 637 print_otag(h, TAG_DIV, "sul", &su); 638 return 1; 639 } 640 641 static int 642 man_UR_pre(MAN_ARGS) 643 { 644 n = n->child; 645 assert(n->type == ROFFT_HEAD); 646 if (n->child != NULL) { 647 assert(n->child->type == ROFFT_TEXT); 648 print_otag(h, TAG_A, "cTh", "Lk", n->child->string); 649 } 650 651 assert(n->next->type == ROFFT_BODY); 652 if (n->next->child != NULL) 653 n = n->next; 654 655 print_man_nodelist(man, n->child, h); 656 657 return 0; 658 } 659