1 /* $Id: man_html.c,v 1.187 2023/10/24 20:53:12 schwarze Exp $ */ 2 /* 3 * Copyright (c) 2013-15,2017-20,2022-23 Ingo Schwarze <schwarze@openbsd.org> 4 * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 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 * HTML formatter for man(7) used by mandoc(1). 19 */ 20 #include "config.h" 21 22 #include <sys/types.h> 23 24 #include <assert.h> 25 #include <ctype.h> 26 #include <stdio.h> 27 #include <stdlib.h> 28 #include <string.h> 29 30 #include "mandoc_aux.h" 31 #include "mandoc.h" 32 #include "roff.h" 33 #include "man.h" 34 #include "out.h" 35 #include "html.h" 36 #include "main.h" 37 38 #define MAN_ARGS const struct roff_meta *man, \ 39 struct roff_node *n, \ 40 struct html *h 41 42 struct man_html_act { 43 int (*pre)(MAN_ARGS); 44 int (*post)(MAN_ARGS); 45 }; 46 47 static void print_man_head(const struct roff_meta *, 48 struct html *); 49 static void print_man_nodelist(MAN_ARGS); 50 static void print_man_node(MAN_ARGS); 51 static char list_continues(const struct roff_node *, 52 const struct roff_node *); 53 static int man_B_pre(MAN_ARGS); 54 static int man_IP_pre(MAN_ARGS); 55 static int man_I_pre(MAN_ARGS); 56 static int man_MR_pre(MAN_ARGS); 57 static int man_OP_pre(MAN_ARGS); 58 static int man_PP_pre(MAN_ARGS); 59 static int man_RS_pre(MAN_ARGS); 60 static int man_SH_pre(MAN_ARGS); 61 static int man_SM_pre(MAN_ARGS); 62 static int man_SY_pre(MAN_ARGS); 63 static int man_UR_pre(MAN_ARGS); 64 static int man_alt_pre(MAN_ARGS); 65 static int man_ign_pre(MAN_ARGS); 66 static int man_in_pre(MAN_ARGS); 67 static void man_root_post(const struct roff_meta *, 68 struct html *); 69 static void man_root_pre(const struct roff_meta *, 70 struct html *); 71 72 static const struct man_html_act man_html_acts[MAN_MAX - MAN_TH] = { 73 { NULL, NULL }, /* TH */ 74 { man_SH_pre, NULL }, /* SH */ 75 { man_SH_pre, NULL }, /* SS */ 76 { man_IP_pre, NULL }, /* TP */ 77 { man_IP_pre, NULL }, /* TQ */ 78 { man_PP_pre, NULL }, /* LP */ 79 { man_PP_pre, NULL }, /* PP */ 80 { man_PP_pre, NULL }, /* P */ 81 { man_IP_pre, NULL }, /* IP */ 82 { man_PP_pre, NULL }, /* HP */ 83 { man_SM_pre, NULL }, /* SM */ 84 { man_SM_pre, NULL }, /* SB */ 85 { man_alt_pre, NULL }, /* BI */ 86 { man_alt_pre, NULL }, /* IB */ 87 { man_alt_pre, NULL }, /* BR */ 88 { man_alt_pre, NULL }, /* RB */ 89 { NULL, NULL }, /* R */ 90 { man_B_pre, NULL }, /* B */ 91 { man_I_pre, NULL }, /* I */ 92 { man_alt_pre, NULL }, /* IR */ 93 { man_alt_pre, NULL }, /* RI */ 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_SY_pre, NULL }, /* SY */ 102 { NULL, NULL }, /* YS */ 103 { man_OP_pre, NULL }, /* OP */ 104 { NULL, NULL }, /* EX */ 105 { NULL, NULL }, /* EE */ 106 { man_UR_pre, NULL }, /* UR */ 107 { NULL, NULL }, /* UE */ 108 { man_UR_pre, NULL }, /* MT */ 109 { NULL, NULL }, /* ME */ 110 { man_MR_pre, NULL }, /* MR */ 111 }; 112 113 114 void 115 html_man(void *arg, const struct roff_meta *man) 116 { 117 struct html *h; 118 struct roff_node *n; 119 struct tag *t; 120 121 h = (struct html *)arg; 122 n = man->first->child; 123 124 if ((h->oflags & HTML_FRAGMENT) == 0) { 125 print_gen_decls(h); 126 print_otag(h, TAG_HTML, ""); 127 t = print_otag(h, TAG_HEAD, ""); 128 print_man_head(man, h); 129 print_tagq(h, t); 130 if (n != NULL && n->type == ROFFT_COMMENT) 131 print_gen_comment(h, n); 132 print_otag(h, TAG_BODY, ""); 133 } 134 135 man_root_pre(man, h); 136 t = print_otag(h, TAG_MAIN, "c", "manual-text"); 137 print_man_nodelist(man, n, h); 138 print_tagq(h, t); 139 man_root_post(man, h); 140 print_tagq(h, NULL); 141 } 142 143 static void 144 print_man_head(const struct roff_meta *man, struct html *h) 145 { 146 char *cp; 147 148 print_gen_head(h); 149 mandoc_asprintf(&cp, "%s(%s)", man->title, man->msec); 150 print_otag(h, TAG_TITLE, ""); 151 print_text(h, cp); 152 free(cp); 153 } 154 155 static void 156 print_man_nodelist(MAN_ARGS) 157 { 158 while (n != NULL) { 159 print_man_node(man, n, h); 160 n = n->next; 161 } 162 } 163 164 static void 165 print_man_node(MAN_ARGS) 166 { 167 struct tag *t; 168 int child; 169 170 if (n->type == ROFFT_COMMENT || n->flags & NODE_NOPRT) 171 return; 172 173 if ((n->flags & NODE_NOFILL) == 0) 174 html_fillmode(h, ROFF_fi); 175 else if (html_fillmode(h, ROFF_nf) == ROFF_nf && 176 n->tok != ROFF_fi && n->flags & NODE_LINE && 177 (n->prev == NULL || n->prev->tok != MAN_YS)) 178 print_endline(h); 179 180 child = 1; 181 switch (n->type) { 182 case ROFFT_TEXT: 183 if (*n->string == '\0') { 184 print_endline(h); 185 return; 186 } 187 if (*n->string == ' ' && n->flags & NODE_LINE && 188 (h->flags & HTML_NONEWLINE) == 0) 189 print_otag(h, TAG_BR, ""); 190 else if (n->flags & NODE_DELIMC) 191 h->flags |= HTML_NOSPACE; 192 t = h->tag; 193 t->refcnt++; 194 print_text(h, n->string); 195 break; 196 case ROFFT_EQN: 197 t = h->tag; 198 t->refcnt++; 199 print_eqn(h, n->eqn); 200 break; 201 case ROFFT_TBL: 202 /* 203 * This will take care of initialising all of the table 204 * state data for the first table, then tearing it down 205 * for the last one. 206 */ 207 print_tbl(h, n->span); 208 return; 209 default: 210 /* 211 * Close out scope of font prior to opening a macro 212 * scope. 213 */ 214 if (h->metac != ESCAPE_FONTROMAN) { 215 h->metal = h->metac; 216 h->metac = ESCAPE_FONTROMAN; 217 } 218 219 /* 220 * Close out the current table, if it's open, and unset 221 * the "meta" table state. This will be reopened on the 222 * next table element. 223 */ 224 if (h->tblt != NULL) 225 print_tblclose(h); 226 t = h->tag; 227 t->refcnt++; 228 if (n->tok < ROFF_MAX) { 229 roff_html_pre(h, n); 230 t->refcnt--; 231 print_stagq(h, t); 232 return; 233 } 234 assert(n->tok >= MAN_TH && n->tok < MAN_MAX); 235 if (man_html_acts[n->tok - MAN_TH].pre != NULL) 236 child = (*man_html_acts[n->tok - MAN_TH].pre)(man, 237 n, h); 238 break; 239 } 240 241 if (child && n->child != NULL) 242 print_man_nodelist(man, n->child, h); 243 244 /* This will automatically close out any font scope. */ 245 t->refcnt--; 246 if (n->type == ROFFT_BLOCK && 247 (n->tok == MAN_IP || n->tok == MAN_TP || n->tok == MAN_TQ)) { 248 t = h->tag; 249 while (t->tag != TAG_DL && t->tag != TAG_UL) 250 t = t->next; 251 /* 252 * Close the list if no further item of the same type 253 * follows; otherwise, close the item only. 254 */ 255 if (list_continues(n, roff_node_next(n)) == '\0') { 256 print_tagq(h, t); 257 t = NULL; 258 } 259 } 260 if (t != NULL) 261 print_stagq(h, t); 262 } 263 264 static void 265 man_root_pre(const struct roff_meta *man, struct html *h) 266 { 267 struct tag *t; 268 char *title; 269 270 assert(man->title); 271 assert(man->msec); 272 mandoc_asprintf(&title, "%s(%s)", man->title, man->msec); 273 274 t = print_otag(h, TAG_DIV, "cr?", "head", "doc-pageheader", 275 "aria-label", "Manual header line"); 276 277 print_otag(h, TAG_SPAN, "c", "head-ltitle"); 278 print_text(h, title); 279 print_stagq(h, t); 280 281 print_otag(h, TAG_SPAN, "c", "head-vol"); 282 if (man->vol != NULL) 283 print_text(h, man->vol); 284 print_stagq(h, t); 285 286 print_otag(h, TAG_SPAN, "c", "head-rtitle"); 287 print_text(h, title); 288 print_tagq(h, t); 289 free(title); 290 } 291 292 static void 293 man_root_post(const struct roff_meta *man, struct html *h) 294 { 295 struct tag *t; 296 297 t = print_otag(h, TAG_DIV, "cr?", "foot", "doc-pagefooter", 298 "aria-label", "Manual footer line"); 299 300 print_otag(h, TAG_SPAN, "c", "foot-left"); 301 print_stagq(h, t); 302 303 print_otag(h, TAG_SPAN, "c", "foot-date"); 304 print_text(h, man->date); 305 print_stagq(h, t); 306 307 print_otag(h, TAG_SPAN, "c", "foot-os"); 308 if (man->os != NULL) 309 print_text(h, man->os); 310 print_tagq(h, t); 311 } 312 313 static int 314 man_SH_pre(MAN_ARGS) 315 { 316 const char *class; 317 enum htmltag tag; 318 319 if (n->tok == MAN_SH) { 320 tag = TAG_H2; 321 class = "Sh"; 322 } else { 323 tag = TAG_H3; 324 class = "Ss"; 325 } 326 switch (n->type) { 327 case ROFFT_BLOCK: 328 html_close_paragraph(h); 329 print_otag(h, TAG_SECTION, "c", class); 330 break; 331 case ROFFT_HEAD: 332 print_otag_id(h, tag, class, n); 333 break; 334 case ROFFT_BODY: 335 break; 336 default: 337 abort(); 338 } 339 return 1; 340 } 341 342 static int 343 man_alt_pre(MAN_ARGS) 344 { 345 const struct roff_node *nn; 346 struct tag *t; 347 int i; 348 enum htmltag fp; 349 350 for (i = 0, nn = n->child; nn != NULL; nn = nn->next, i++) { 351 switch (n->tok) { 352 case MAN_BI: 353 fp = i % 2 ? TAG_I : TAG_B; 354 break; 355 case MAN_IB: 356 fp = i % 2 ? TAG_B : TAG_I; 357 break; 358 case MAN_RI: 359 fp = i % 2 ? TAG_I : TAG_MAX; 360 break; 361 case MAN_IR: 362 fp = i % 2 ? TAG_MAX : TAG_I; 363 break; 364 case MAN_BR: 365 fp = i % 2 ? TAG_MAX : TAG_B; 366 break; 367 case MAN_RB: 368 fp = i % 2 ? TAG_B : TAG_MAX; 369 break; 370 default: 371 abort(); 372 } 373 374 if (i) 375 h->flags |= HTML_NOSPACE; 376 377 if (fp != TAG_MAX) 378 t = print_otag(h, fp, ""); 379 380 print_text(h, nn->string); 381 382 if (fp != TAG_MAX) 383 print_tagq(h, t); 384 } 385 return 0; 386 } 387 388 static int 389 man_SM_pre(MAN_ARGS) 390 { 391 print_otag(h, TAG_SMALL, ""); 392 if (n->tok == MAN_SB) 393 print_otag(h, TAG_B, ""); 394 return 1; 395 } 396 397 static int 398 man_PP_pre(MAN_ARGS) 399 { 400 switch (n->type) { 401 case ROFFT_BLOCK: 402 html_close_paragraph(h); 403 break; 404 case ROFFT_HEAD: 405 return 0; 406 case ROFFT_BODY: 407 if (n->child != NULL && 408 (n->child->flags & NODE_NOFILL) == 0) 409 print_otag(h, TAG_P, "c", 410 n->tok == MAN_HP ? "Pp HP" : "Pp"); 411 break; 412 default: 413 abort(); 414 } 415 return 1; 416 } 417 418 static char 419 list_continues(const struct roff_node *n1, const struct roff_node *n2) 420 { 421 const char *s1, *s2; 422 char c1, c2; 423 424 if (n1 == NULL || n1->type != ROFFT_BLOCK || 425 n2 == NULL || n2->type != ROFFT_BLOCK) 426 return '\0'; 427 if ((n1->tok == MAN_TP || n1->tok == MAN_TQ) && 428 (n2->tok == MAN_TP || n2->tok == MAN_TQ)) 429 return ' '; 430 if (n1->tok != MAN_IP || n2->tok != MAN_IP) 431 return '\0'; 432 n1 = n1->head->child; 433 n2 = n2->head->child; 434 s1 = n1 == NULL ? "" : n1->string; 435 s2 = n2 == NULL ? "" : n2->string; 436 c1 = strcmp(s1, "*") == 0 ? '*' : 437 strcmp(s1, "\\-") == 0 ? '-' : 438 strcmp(s1, "\\(bu") == 0 ? 'b' : 439 strcmp(s1, "\\[bu]") == 0 ? 'b' : ' '; 440 c2 = strcmp(s2, "*") == 0 ? '*' : 441 strcmp(s2, "\\-") == 0 ? '-' : 442 strcmp(s2, "\\(bu") == 0 ? 'b' : 443 strcmp(s2, "\\[bu]") == 0 ? 'b' : ' '; 444 return c1 != c2 ? '\0' : c1 == 'b' ? '*' : c1; 445 } 446 447 static int 448 man_IP_pre(MAN_ARGS) 449 { 450 struct roff_node *nn; 451 const char *list_class; 452 enum htmltag list_elem, body_elem; 453 char list_type; 454 455 nn = n->type == ROFFT_BLOCK ? n : n->parent; 456 list_type = list_continues(roff_node_prev(nn), nn); 457 if (list_type == '\0') { 458 /* Start a new list. */ 459 list_type = list_continues(nn, roff_node_next(nn)); 460 if (list_type == '\0') 461 list_type = ' '; 462 switch (list_type) { 463 case ' ': 464 list_class = "Bl-tag"; 465 list_elem = TAG_DL; 466 break; 467 case '*': 468 list_class = "Bl-bullet"; 469 list_elem = TAG_UL; 470 break; 471 case '-': 472 list_class = "Bl-dash"; 473 list_elem = TAG_UL; 474 break; 475 default: 476 abort(); 477 } 478 } else { 479 /* Continue a list that was started earlier. */ 480 list_class = NULL; 481 list_elem = TAG_MAX; 482 } 483 body_elem = list_type == ' ' ? TAG_DD : TAG_LI; 484 485 switch (n->type) { 486 case ROFFT_BLOCK: 487 html_close_paragraph(h); 488 if (list_elem != TAG_MAX) 489 print_otag(h, list_elem, "c", list_class); 490 return 1; 491 case ROFFT_HEAD: 492 if (body_elem == TAG_LI) 493 return 0; 494 print_otag_id(h, TAG_DT, NULL, n); 495 break; 496 case ROFFT_BODY: 497 print_otag(h, body_elem, ""); 498 return 1; 499 default: 500 abort(); 501 } 502 switch(n->tok) { 503 case MAN_IP: /* Only print the first header element. */ 504 if (n->child != NULL) 505 print_man_node(man, n->child, h); 506 break; 507 case MAN_TP: /* Only print next-line header elements. */ 508 case MAN_TQ: 509 nn = n->child; 510 while (nn != NULL && (NODE_LINE & nn->flags) == 0) 511 nn = nn->next; 512 while (nn != NULL) { 513 print_man_node(man, nn, h); 514 nn = nn->next; 515 } 516 break; 517 default: 518 abort(); 519 } 520 return 0; 521 } 522 523 static int 524 man_MR_pre(MAN_ARGS) 525 { 526 struct tag *t; 527 const char *name, *section, *suffix; 528 char *label; 529 530 html_setfont(h, ESCAPE_FONTROMAN); 531 name = section = suffix = label = NULL; 532 if (n->child != NULL) { 533 name = n->child->string; 534 if (n->child->next != NULL) { 535 section = n->child->next->string; 536 mandoc_asprintf(&label, 537 "%s, section %s", name, section); 538 if (n->child->next->next != NULL) 539 suffix = n->child->next->next->string; 540 } 541 } 542 543 if (name != NULL && section != NULL && h->base_man1 != NULL) 544 t = print_otag(h, TAG_A, "chM?", "Xr", 545 name, section, "aria-label", label); 546 else 547 t = print_otag(h, TAG_A, "c?", "Xr", "aria-label", label); 548 549 free(label); 550 if (name != NULL) { 551 print_text(h, name); 552 h->flags |= HTML_NOSPACE; 553 } 554 print_text(h, "("); 555 h->flags |= HTML_NOSPACE; 556 if (section != NULL) { 557 print_text(h, section); 558 h->flags |= HTML_NOSPACE; 559 } 560 print_text(h, ")"); 561 print_tagq(h, t); 562 if (suffix != NULL) { 563 h->flags |= HTML_NOSPACE; 564 print_text(h, suffix); 565 } 566 return 0; 567 } 568 569 static int 570 man_OP_pre(MAN_ARGS) 571 { 572 struct tag *tt; 573 574 print_text(h, "["); 575 h->flags |= HTML_NOSPACE; 576 tt = print_otag(h, TAG_SPAN, "c", "Op"); 577 578 if ((n = n->child) != NULL) { 579 print_otag(h, TAG_B, ""); 580 print_text(h, n->string); 581 } 582 583 print_stagq(h, tt); 584 585 if (n != NULL && n->next != NULL) { 586 print_otag(h, TAG_I, ""); 587 print_text(h, n->next->string); 588 } 589 590 print_stagq(h, tt); 591 h->flags |= HTML_NOSPACE; 592 print_text(h, "]"); 593 return 0; 594 } 595 596 static int 597 man_B_pre(MAN_ARGS) 598 { 599 print_otag(h, TAG_B, ""); 600 return 1; 601 } 602 603 static int 604 man_I_pre(MAN_ARGS) 605 { 606 print_otag(h, TAG_I, ""); 607 return 1; 608 } 609 610 static int 611 man_in_pre(MAN_ARGS) 612 { 613 print_otag(h, TAG_BR, ""); 614 return 0; 615 } 616 617 static int 618 man_ign_pre(MAN_ARGS) 619 { 620 return 0; 621 } 622 623 static int 624 man_RS_pre(MAN_ARGS) 625 { 626 switch (n->type) { 627 case ROFFT_BLOCK: 628 html_close_paragraph(h); 629 break; 630 case ROFFT_HEAD: 631 return 0; 632 case ROFFT_BODY: 633 print_otag(h, TAG_DIV, "c", "Bd-indent"); 634 break; 635 default: 636 abort(); 637 } 638 return 1; 639 } 640 641 static int 642 man_SY_pre(MAN_ARGS) 643 { 644 switch (n->type) { 645 case ROFFT_BLOCK: 646 html_close_paragraph(h); 647 print_otag(h, TAG_TABLE, "c", "Nm"); 648 print_otag(h, TAG_TR, ""); 649 break; 650 case ROFFT_HEAD: 651 print_otag(h, TAG_TD, ""); 652 print_otag(h, TAG_CODE, "c", "Nm"); 653 break; 654 case ROFFT_BODY: 655 print_otag(h, TAG_TD, ""); 656 break; 657 default: 658 abort(); 659 } 660 return 1; 661 } 662 663 static int 664 man_UR_pre(MAN_ARGS) 665 { 666 char *cp; 667 668 n = n->child; 669 assert(n->type == ROFFT_HEAD); 670 if (n->child != NULL) { 671 assert(n->child->type == ROFFT_TEXT); 672 if (n->tok == MAN_MT) { 673 mandoc_asprintf(&cp, "mailto:%s", n->child->string); 674 print_otag(h, TAG_A, "ch", "Mt", cp); 675 free(cp); 676 } else 677 print_otag(h, TAG_A, "ch", "Lk", n->child->string); 678 } 679 680 assert(n->next->type == ROFFT_BODY); 681 if (n->next->child != NULL) 682 n = n->next; 683 684 print_man_nodelist(man, n->child, h); 685 return 0; 686 } 687