1/* $Id: man_html.c,v 1.174 2019/04/30 15:53:00 schwarze Exp $ */ 2/* 3 * Copyright (c) 2008-2012, 2014 Kristaps Dzonsons <kristaps@bsd.lv> 4 * Copyright (c) 2013-2015, 2017-2019 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 "config.h" 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 const struct roff_node *n, \ 38 struct html *h 39 40struct man_html_act { 41 int (*pre)(MAN_ARGS); 42 int (*post)(MAN_ARGS); 43}; 44 45static void print_man_head(const struct roff_meta *, 46 struct html *); 47static void print_man_nodelist(MAN_ARGS); 48static void print_man_node(MAN_ARGS); 49static char list_continues(const struct roff_node *, 50 const struct roff_node *); 51static int man_B_pre(MAN_ARGS); 52static int man_IP_pre(MAN_ARGS); 53static int man_I_pre(MAN_ARGS); 54static int man_OP_pre(MAN_ARGS); 55static int man_PP_pre(MAN_ARGS); 56static int man_RS_pre(MAN_ARGS); 57static int man_SH_pre(MAN_ARGS); 58static int man_SM_pre(MAN_ARGS); 59static int man_SY_pre(MAN_ARGS); 60static int man_UR_pre(MAN_ARGS); 61static int man_abort_pre(MAN_ARGS); 62static int man_alt_pre(MAN_ARGS); 63static int man_ign_pre(MAN_ARGS); 64static int man_in_pre(MAN_ARGS); 65static void man_root_post(const struct roff_meta *, 66 struct html *); 67static void man_root_pre(const struct roff_meta *, 68 struct html *); 69 70static 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 111void 112html_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 if (n != NULL && n->type == ROFFT_COMMENT) 125 print_gen_comment(h, n); 126 t = print_otag(h, TAG_HEAD, ""); 127 print_man_head(man, h); 128 print_tagq(h, t); 129 print_otag(h, TAG_BODY, ""); 130 } 131 132 man_root_pre(man, h); 133 t = print_otag(h, TAG_DIV, "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 140static void 141print_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 152static void 153print_man_nodelist(MAN_ARGS) 154{ 155 while (n != NULL) { 156 print_man_node(man, n, h); 157 n = n->next; 158 } 159} 160 161static void 162print_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 html_fillmode(h, n->flags & NODE_NOFILL ? ROFF_nf : ROFF_fi); 171 172 child = 1; 173 switch (n->type) { 174 case ROFFT_TEXT: 175 if (*n->string == '\0') { 176 print_endline(h); 177 return; 178 } 179 if (*n->string == ' ' && n->flags & NODE_LINE && 180 (h->flags & HTML_NONEWLINE) == 0) 181 print_endline(h); 182 else if (n->flags & NODE_DELIMC) 183 h->flags |= HTML_NOSPACE; 184 t = h->tag; 185 t->refcnt++; 186 print_text(h, n->string); 187 break; 188 case ROFFT_EQN: 189 t = h->tag; 190 t->refcnt++; 191 print_eqn(h, n->eqn); 192 break; 193 case ROFFT_TBL: 194 /* 195 * This will take care of initialising all of the table 196 * state data for the first table, then tearing it down 197 * for the last one. 198 */ 199 print_tbl(h, n->span); 200 return; 201 default: 202 /* 203 * Close out scope of font prior to opening a macro 204 * scope. 205 */ 206 if (h->metac != ESCAPE_FONTROMAN) { 207 h->metal = h->metac; 208 h->metac = ESCAPE_FONTROMAN; 209 } 210 211 /* 212 * Close out the current table, if it's open, and unset 213 * the "meta" table state. This will be reopened on the 214 * next table element. 215 */ 216 if (h->tblt != NULL) 217 print_tblclose(h); 218 t = h->tag; 219 t->refcnt++; 220 if (n->tok < ROFF_MAX) { 221 roff_html_pre(h, n); 222 t->refcnt--; 223 print_stagq(h, t); 224 return; 225 } 226 assert(n->tok >= MAN_TH && n->tok < MAN_MAX); 227 if (man_html_acts[n->tok - MAN_TH].pre != NULL) 228 child = (*man_html_acts[n->tok - MAN_TH].pre)(man, 229 n, h); 230 break; 231 } 232 233 if (child && n->child != NULL) 234 print_man_nodelist(man, n->child, h); 235 236 /* This will automatically close out any font scope. */ 237 t->refcnt--; 238 if (n->type == ROFFT_BLOCK && 239 (n->tok == MAN_IP || n->tok == MAN_TP || n->tok == MAN_TQ)) { 240 t = h->tag; 241 while (t->tag != TAG_DL && t->tag != TAG_UL) 242 t = t->next; 243 /* 244 * Close the list if no further item of the same type 245 * follows; otherwise, close the item only. 246 */ 247 if (list_continues(n, n->next) == '\0') { 248 print_tagq(h, t); 249 t = NULL; 250 } 251 } 252 if (t != NULL) 253 print_stagq(h, t); 254 255 if (n->flags & NODE_NOFILL && n->tok != MAN_YS && 256 (n->next != NULL && n->next->flags & NODE_LINE)) { 257 /* In .nf = <pre>, print even empty lines. */ 258 h->col++; 259 print_endline(h); 260 } 261} 262 263static void 264man_root_pre(const struct roff_meta *man, struct html *h) 265{ 266 struct tag *t, *tt; 267 char *title; 268 269 assert(man->title); 270 assert(man->msec); 271 mandoc_asprintf(&title, "%s(%s)", man->title, man->msec); 272 273 t = print_otag(h, TAG_TABLE, "c", "head"); 274 tt = print_otag(h, TAG_TR, ""); 275 276 print_otag(h, TAG_TD, "c", "head-ltitle"); 277 print_text(h, title); 278 print_stagq(h, tt); 279 280 print_otag(h, TAG_TD, "c", "head-vol"); 281 if (man->vol != NULL) 282 print_text(h, man->vol); 283 print_stagq(h, tt); 284 285 print_otag(h, TAG_TD, "c", "head-rtitle"); 286 print_text(h, title); 287 print_tagq(h, t); 288 free(title); 289} 290 291static void 292man_root_post(const struct roff_meta *man, struct html *h) 293{ 294 struct tag *t, *tt; 295 296 t = print_otag(h, TAG_TABLE, "c", "foot"); 297 tt = print_otag(h, TAG_TR, ""); 298 299 print_otag(h, TAG_TD, "c", "foot-date"); 300 print_text(h, man->date); 301 print_stagq(h, tt); 302 303 print_otag(h, TAG_TD, "c", "foot-os"); 304 if (man->os != NULL) 305 print_text(h, man->os); 306 print_tagq(h, t); 307} 308 309static int 310man_SH_pre(MAN_ARGS) 311{ 312 const char *class; 313 char *id; 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 id = html_make_id(n, 1); 330 print_otag(h, tag, "ci", class, id); 331 if (id != NULL) 332 print_otag(h, TAG_A, "chR", "permalink", id); 333 break; 334 case ROFFT_BODY: 335 break; 336 default: 337 abort(); 338 } 339 return 1; 340} 341 342static int 343man_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 388static int 389man_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 397static int 398man_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_PP ? "Pp" : "Pp HP"); 411 break; 412 default: 413 abort(); 414 } 415 return 1; 416} 417 418static char 419list_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 c2 = strcmp(s2, "*") == 0 ? '*' : 440 strcmp(s2, "\\-") == 0 ? '-' : 441 strcmp(s2, "\\(bu") == 0 ? 'b' : ' '; 442 return c1 != c2 ? '\0' : c1 == 'b' ? '*' : c1; 443} 444 445static int 446man_IP_pre(MAN_ARGS) 447{ 448 const struct roff_node *nn; 449 const char *list_class; 450 enum htmltag list_elem, body_elem; 451 char list_type; 452 453 nn = n->type == ROFFT_BLOCK ? n : n->parent; 454 if ((list_type = list_continues(nn->prev, nn)) == '\0') { 455 /* Start a new list. */ 456 if ((list_type = list_continues(nn, nn->next)) == '\0') 457 list_type = ' '; 458 switch (list_type) { 459 case ' ': 460 list_class = "Bl-tag"; 461 list_elem = TAG_DL; 462 break; 463 case '*': 464 list_class = "Bl-bullet"; 465 list_elem = TAG_UL; 466 break; 467 case '-': 468 list_class = "Bl-dash"; 469 list_elem = TAG_UL; 470 break; 471 default: 472 abort(); 473 } 474 } else { 475 /* Continue a list that was started earlier. */ 476 list_class = NULL; 477 list_elem = TAG_MAX; 478 } 479 body_elem = list_type == ' ' ? TAG_DD : TAG_LI; 480 481 switch (n->type) { 482 case ROFFT_BLOCK: 483 html_close_paragraph(h); 484 if (list_elem != TAG_MAX) 485 print_otag(h, list_elem, "c", list_class); 486 return 1; 487 case ROFFT_HEAD: 488 if (body_elem == TAG_LI) 489 return 0; 490 print_otag(h, TAG_DT, ""); 491 break; 492 case ROFFT_BODY: 493 print_otag(h, body_elem, ""); 494 return 1; 495 default: 496 abort(); 497 } 498 499 switch(n->tok) { 500 case MAN_IP: /* Only print the first header element. */ 501 if (n->child != NULL) 502 print_man_node(man, n->child, h); 503 break; 504 case MAN_TP: /* Only print next-line header elements. */ 505 case MAN_TQ: 506 nn = n->child; 507 while (nn != NULL && (NODE_LINE & nn->flags) == 0) 508 nn = nn->next; 509 while (nn != NULL) { 510 print_man_node(man, nn, h); 511 nn = nn->next; 512 } 513 break; 514 default: 515 abort(); 516 } 517 return 0; 518} 519 520static int 521man_OP_pre(MAN_ARGS) 522{ 523 struct tag *tt; 524 525 print_text(h, "["); 526 h->flags |= HTML_NOSPACE; 527 tt = print_otag(h, TAG_SPAN, "c", "Op"); 528 529 if ((n = n->child) != NULL) { 530 print_otag(h, TAG_B, ""); 531 print_text(h, n->string); 532 } 533 534 print_stagq(h, tt); 535 536 if (n != NULL && n->next != NULL) { 537 print_otag(h, TAG_I, ""); 538 print_text(h, n->next->string); 539 } 540 541 print_stagq(h, tt); 542 h->flags |= HTML_NOSPACE; 543 print_text(h, "]"); 544 return 0; 545} 546 547static int 548man_B_pre(MAN_ARGS) 549{ 550 print_otag(h, TAG_B, ""); 551 return 1; 552} 553 554static int 555man_I_pre(MAN_ARGS) 556{ 557 print_otag(h, TAG_I, ""); 558 return 1; 559} 560 561static int 562man_in_pre(MAN_ARGS) 563{ 564 print_otag(h, TAG_BR, ""); 565 return 0; 566} 567 568static int 569man_ign_pre(MAN_ARGS) 570{ 571 return 0; 572} 573 574static int 575man_RS_pre(MAN_ARGS) 576{ 577 switch (n->type) { 578 case ROFFT_BLOCK: 579 html_close_paragraph(h); 580 break; 581 case ROFFT_HEAD: 582 return 0; 583 case ROFFT_BODY: 584 print_otag(h, TAG_DIV, "c", "Bd-indent"); 585 break; 586 default: 587 abort(); 588 } 589 return 1; 590} 591 592static int 593man_SY_pre(MAN_ARGS) 594{ 595 switch (n->type) { 596 case ROFFT_BLOCK: 597 html_close_paragraph(h); 598 print_otag(h, TAG_TABLE, "c", "Nm"); 599 print_otag(h, TAG_TR, ""); 600 break; 601 case ROFFT_HEAD: 602 print_otag(h, TAG_TD, ""); 603 print_otag(h, TAG_CODE, "c", "Nm"); 604 break; 605 case ROFFT_BODY: 606 print_otag(h, TAG_TD, ""); 607 break; 608 default: 609 abort(); 610 } 611 return 1; 612} 613 614static int 615man_UR_pre(MAN_ARGS) 616{ 617 char *cp; 618 619 n = n->child; 620 assert(n->type == ROFFT_HEAD); 621 if (n->child != NULL) { 622 assert(n->child->type == ROFFT_TEXT); 623 if (n->tok == MAN_MT) { 624 mandoc_asprintf(&cp, "mailto:%s", n->child->string); 625 print_otag(h, TAG_A, "ch", "Mt", cp); 626 free(cp); 627 } else 628 print_otag(h, TAG_A, "ch", "Lk", n->child->string); 629 } 630 631 assert(n->next->type == ROFFT_BODY); 632 if (n->next->child != NULL) 633 n = n->next; 634 635 print_man_nodelist(man, n->child, h); 636 return 0; 637} 638 639static int 640man_abort_pre(MAN_ARGS) 641{ 642 abort(); 643} 644