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